04、NetCore2.0网页应用程序之Startup源码解析

来源:互联网 发布:灯杆数据基础调研 编辑:程序博客网 时间:2024/05/22 20:44
04、NetCore2.0网页应用程序之Startup源码解析
 
通过分析Asp.Net Core 2.0Startup部分源码,来理解插件框架的运行机制,以及掌握Startup注册的最优姿势。
 
上一篇中,我们一步步搭建了自己的Web应用程序,其中新建了一个StartUp类,只有一个Configure方法,并没有继承自任何接口,也就是说Asp.Net Core 2.0框架并没有使用接口来和开发者约定如何定制StartUp类,那么这个类是如何被框架使用的呢?
先下载Asp.Net Core 2.0的开源代码
 
一、重新看一下框架接入StartUp类的代码
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;            }        }    }
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,

  • 一种是继承自IStartup的类
  • 另外一种是包含Configure方法的类

既然如此,我们的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 工程,默认使用了第一种方式,是从哪个角度考虑的???

原创粉丝点击