• 04、NetCore2.0下Web应用之Startup源码解析


    04、NetCore2.0Web应用之Startup源码解析
     
    通过分析Asp.Net Core 2.0Startup部分源码,来理解插件框架的运行机制,以及掌握Startup注册的最优姿势。
     

    ------------------------------------------------------------------------------------------------------------

     写在前面:这是一个系列的文章,总目录请移步:NetCore2.0技术文章目录

    ------------------------------------------------------------------------------------------------------------

    上一篇中,我们一步步搭建了自己的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 工程,默认使用了第一种方式,是从哪个角度考虑的???

    【敦格原创】欢迎引用,请注明来源:http://www.cnblogs.com/netcore2
  • 相关阅读:
    KISSY 1.3.0 发布,淘宝 Web UI 库
    mongodb的监控与性能优化
    Aspose.Tasks 4.9.0 发布,Project 文件读写
    Fix8 0.6.6 发布,C++ 实现的 FIX 框架
    VIM Pal 1.1.0 发布,VIM 文件树列表
    QT 5.0 正式版发布,支持 C++11
    TWiki 5.1.3 发布,企业 Wiki 系统
    Apache Lucene 3.6.2 发布
    Apache Sqoop 1.99.1 发布
    JAXX 2.5.9 发布,XML用户界面框架
  • 原文地址:https://www.cnblogs.com/netcore2/p/7411841.html
Copyright © 2020-2023  润新知