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 工程,默認使用了第一種方式,是從哪個角度考慮的???


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM