using Microsoft.AspNetCore.Hosting; namespace MyWeb { class Program { static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() // 指定WebServer为Kestrel .UseStartup<StartUpB>() // 配置WebHost .Build(); host.Run(); // 启动WebHost } } }
框架接入的关键代码是WebHostBuilder.UseStartup方法,我们去看一下框架源码:
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) { var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) .ConfigureServices(services => { if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) { services.AddSingleton(typeof(IStartup), startupType); } else { services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); }); } }); }
首先这是IWebHostBuilder接口的扩展类,这里有两个分支
1、如果StartUp从IStartup继承,则直接以单例的方式加入插件服务框架中。
2、如果不是从IStartup继承,则包装为IStartup后,再以单例的方式加入插件服务框架中。
源码证实了ConventionBasedStartup类正是继承了IStartup。
public class ConventionBasedStartup : IStartup { private readonly StartupMethods _methods; public ConventionBasedStartup(StartupMethods methods) { _methods = methods; } public void Configure(IApplicationBuilder app) { try { _methods.ConfigureDelegate(app); } catch (Exception ex) { if (ex is TargetInvocationException) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } throw; } } public IServiceProvider ConfigureServices(IServiceCollection services) { try { return _methods.ConfigureServicesDelegate(services); } catch (Exception ex) { if (ex is TargetInvocationException) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } throw; } } }logs_code_collapse">View Code
二、框架如何包装我们的StartUp类
从源码看出关键代码是StartupLoader.LoadMethods,我们看看框架源码(省略了部分代码)
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName) { var configureMethod = FindConfigureDelegate(startupType, environmentName); var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName); object instance = null; if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)) { instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType); } Func<IServiceCollection, IServiceProvider> configureServices = services => { return services.BuildServiceProvider(); }; return new StartupMethods(instance, configureMethod.Build(instance), configureServices); }
我们猜测FindConfigureDelegate方法接入了我们的StartUp,源码证实了,框架通过反射拿到了我们的StartUp.Configure方法:原来是通过方法名字符串类匹配的^_^
private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) { var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true); return new ConfigureBuilder(configureMethod); }
三、让我们的StartUp继承自IStartup
从上面分析可以看出,框架可以接入两种StartUp,
既然如此,我们的StartUp可不可以直接继承自IStartup呢?实验证明是可以的,代码如下:
using System; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace MyWeb { class StartUpI : IStartup { public void Configure(IApplicationBuilder app) { app.Run(c => { var req = c.Request.Path.ToString().TrimStart('/'); var res = string.Empty; switch (req) { case "1": res = "one"; break; case "2": res = "two"; break; default: res = "none"; break; } var mtd = string.Empty; switch (c.Request.Method) { case "GET": mtd = "请求方式: get"; break; case "POST": mtd = "请求方式:post"; break; default: mtd = "请求方式:none"; break; } return c.Response.WriteAsync(res); }); } public IServiceProvider ConfigureServices(IServiceCollection services) { return services.BuildServiceProvider(); } } }View Code
我们把这个类传给框架
using Microsoft.AspNetCore.Hosting; namespace MyWeb { class Program { static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() // 指定WebServer为Kestrel .UseStartup<StartUpI>() // 配置WebHost .Build(); host.Run(); // 启动WebHost } } }
然后运行程序,结果正如期待的:OK!
四、框架实现了一个集成自IStartup的基类
通过查找IStartup的引用关系,发现框架实现了一个抽象基类StartupBase:
public abstract class StartupBase : IStartup { public abstract void Configure(IApplicationBuilder app); IServiceProvider IStartup.ConfigureServices(IServiceCollection services) { ConfigureServices(services); return CreateServiceProvider(services); } public virtual void ConfigureServices(IServiceCollection services) { } public virtual IServiceProvider CreateServiceProvider(IServiceCollection services) { return services.BuildServiceProvider(); } }
我们看到,只需要实现Configure这个抽象方法就可以完成StartUp的定制了,减少了我们的开发工作量,我们来实现一个子类:
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; namespace MyWeb { class StartUpB : StartupBase { public override void Configure(IApplicationBuilder app) { app.Run(c => { var req = c.Request.Path.ToString().TrimStart('/'); var res = string.Empty; switch (req) { case "1": res = "one"; break; case "2": res = "two"; break; default: res = "none"; break; } var mtd = string.Empty; switch (c.Request.Method) { case "GET": mtd = "请求方式: get"; break; case "POST": mtd = "请求方式:post"; break; default: mtd = "请求方式:none"; break; } return c.Response.WriteAsync(res); }); } } }View Code
我们把这个类传给框架
using Microsoft.AspNetCore.Hosting; namespace MyWeb { class Program { static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() // 指定WebServer为Kestrel .UseStartup<StartUpB>() // 配置WebHost .Build(); host.Run(); // 启动WebHost } } }
然后运行程序,结果正如期待的:OK!
五、性能分析
至此我们弄清楚了ASP.Net Core2.0如何接入StartUp类,有三种方式:
1、自己定义个类,必须包含Configure方法
2、继承自IStartup,实现所有方法
3、继承自StartupBase抽象类,只需要实现Configure方法
我认为第三种方式相对来讲,效率更高,原因有二:
1、只需要实现一个方法,代码最少
2、不需要反射,效率更高
不知道,微软新建ASP.Net Core2.0 工程,默认使用了第一种方式,是从哪个角度考虑的???