aspnetcore源碼學習(一)


---恢復內容開始---

筆者從事netcore相關項目開發已經大半年了,從netcore 1.0到現在3.0大概經過了3年左右的時間,記得netcore剛出來的時候國內相關的學習資料缺乏,限制於外語不大熟練的限制國外的相關書籍看起來相當吃力,於是在當當網上買了一本價值70多的入門書籍,買回來卻發現內容都是掛羊頭賣狗肉,深深地鄙視這些為了賺錢不顧內容的作者。如今網上相關的學習資料也相當多,筆者也趁着現在不忙,再來學習一下aspnetcore的源碼,文章中所用的源碼版本是3.0,如果讀者下的源碼是3.0以下,有些函數會有所區別。

下圖是筆者整理的一個簡單類圖,以助自己理解源碼。

對象介紹

WebHostBuilder:負責初始化環境變量,默認設置,指定startup類,創建servicecollection,讀取configuration,創建基礎服務並注入到DI,加載主機配置(hostingStartup),最主要的創建webhost。

WebHost:站點主機,加載應用服務,加載應用中間件,開始和停止站點

WebHostBuilderContext: 上下文,包含環境變量和默認值

WebHostOptions: 創建webhost的時候使用的參數

ServiceCollection: 所有服務的儲存的集合,添加刪除服務

serviceprovider: 獲得服務的實例

serviceDescriptor: 服務的描述類,所有的服務最后都是轉化成該類后注入DI

 

 

關鍵的對象介紹完了,下面我們來看一下web站點是如何運行起來的。

  •  main函數中創建WebHostBuilder對象
  • 指定startup 

     

 public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
        {
            var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;

            hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);

            // Light up the GenericWebHostBuilder implementation
            if (hostBuilder is ISupportsStartup supportsStartup)
            {
                return supportsStartup.UseStartup(startupType);
            }

            return hostBuilder
                .ConfigureServices(services =>
                {
                    if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
                    {
                        services.AddSingleton(typeof(IStartup), startupType);
                    }
                    else
                    {
                        services.AddSingleton(typeof(IStartup), sp =>
                        {
                            var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();
                            return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
                        });
                    }
                });
        }

  

從代碼可以看到,如果這個startup是繼承自IStartup,直接注入到DI,如果不是,則用ConventtionBaseStartup進行包裝,而該類是繼承自IStartup。微軟默認的startup類不繼承IStartup的,具體的實現細節和原因在后面會具體說明。

  • 加載系統的默認的配置文件,可以通過 ConfigureAppConfiguration擴展方法將自己的配置文件加載到容器里面
 public static IWebHostBuilder ConfigureAppConfiguration(this IWebHostBuilder hostBuilder, Action<IConfigurationBuilder> configureDelegate)
        {
            return hostBuilder.ConfigureAppConfiguration((context, builder) => configureDelegate(builder));
        }

  

  • 創建WebHostBuild上下文
  • 加載web主機配置,尋找程序集中貼有HostingStartupAttribute標簽的類並調用Configure方法,在WebHost實例化之前預留的鈎子,由於筆者也沒有用到過這個功能,就不多贅述了。
 foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase))
                {
                    try
                    {
                        var assembly = Assembly.Load(new AssemblyName(assemblyName));

                        foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
                        {
                            var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
                            hostingStartup.Configure(this);
                        }
                    }
                    catch (Exception ex)
                    {
                       ...
                    }
                }
  • 創建servicecollection 類,此類存在於整個webhost生命周期,所有的應用服務和系統服務都存儲在該類中
  • 添加系統服務,包括環境變量,日志服務,配置服務,監聽服務等等
var services = new ServiceCollection();
            services.AddSingleton(_options);
            services.AddSingleton<IWebHostEnvironment>(_hostingEnvironment);
            services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
#pragma warning disable CS0618 // Type or member is obsolete
            services.AddSingleton<AspNetCore.Hosting.IHostingEnvironment>(_hostingEnvironment);
            services.AddSingleton<Extensions.Hosting.IHostingEnvironment>(_hostingEnvironment);
#pragma warning restore CS0618 // Type or member is obsolete
            services.AddSingleton(_context);

            var builder = new ConfigurationBuilder()
                .SetBasePath(_hostingEnvironment.ContentRootPath)
                .AddConfiguration(_config);

            _configureAppConfigurationBuilder?.Invoke(_context, builder);

            var configuration = builder.Build();
            services.AddSingleton<IConfiguration>(configuration);
            _context.Configuration = configuration;

            var listener = new DiagnosticListener("Microsoft.AspNetCore");
            services.AddSingleton<DiagnosticListener>(listener);
            services.AddSingleton<DiagnosticSource>(listener);

            services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
            services.AddTransient<IHttpContextFactory, DefaultHttpContextFactory>();
            services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
            services.AddOptions();
            services.AddLogging();

            services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();

  

  • 創建 ServiceProvider 類,該類依賴於ServiceCollection,並為其中的服務提供實例
  • 創建使用 ServiceProvider和 ServiceCollection類創建 webhost實例並初始化實例
    var host = new WebHost(
                applicationServices,
                hostingServiceProvider,
                _options,
                _config,
                hostingStartupErrors);
            try
            {
                host.Initialize();

              ...

                return host;
            }

  

初始化實例的時候會調用EnsureApplicationServices的方法。看到_startup.ConfigureServices了沒有,對這個就是我們在startup中寫的方法。

 private void EnsureApplicationServices()
        {
            if (_applicationServices == null)
            {
                EnsureStartup();
                _applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
            }
        } 

 

可是他是如何找到這個方法的呢,再看一下EnsureStartup方法,原來是實例化了我們在存在servicecollection中的startup類,前面我們說過,這個類ConventionBasedStartup封裝了,所以這里獲取的也ConventionBasedStartup類的實例。

  private void EnsureStartup()
        {
            if (_startup != null)
            {
                return;
            }

            _startup = _hostingServiceProvider.GetService<IStartup>();
            ...
        }

  

再讓我們看下ConventionBasedStartup的結構,實際上就是將該類作為一個代理來調用我們startup類中的方法

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;
            }
        }
    }
  •   主機實例已創建並且相關服務已初始化完成,這時候就可以start()了。可能你會想可是我的中間件還沒加載,是的 ,start的時候就是加載中間件並創建監聽,讓我們來看一下代碼
public virtual async Task StartAsync(CancellationToken cancellationToken = default)
        {
         ...

            var application = BuildApplication();
        ...
  


        }

  

  private RequestDelegate BuildApplication()
        {
            try
            {
                _applicationServicesException?.Throw();
                EnsureServer();

                var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
                var builder = builderFactory.CreateBuilder(Server.Features);
                builder.ApplicationServices = _applicationServices;
Action<IApplicationBuilder> configure = _startup.Configure; ... configure(builder); return builder.Build(); } }

  發現代碼中的_startup.Configure了嗎!這個就是調用ConventionBasedStartup這個代理類的c方法,也就是我們startup中的Configure方法。這時候筆者當時也產生了一個疑問,這里的委托只有一個參數,可是在實際應用的時候都會加入很多參數,想這樣public void Configure(IApplicationBuilder app, IHostingEnvironment env,IXxxx xxxx)。猜想是不是初始化ConventionBasedStartup類的時候做了什么封裝,重新回到UseStartup方法

return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));

  

 public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
        {
            var configureMethod = FindConfigureDelegate(startupType, environmentName);

            var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName);
            var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName);

            object instance = null;
            if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic))
            {
                instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
            }

            // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it's not
            // going to be used for anything.
            var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object);

            var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance(
                typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type),
                hostingServiceProvider,
                servicesMethod,
                configureContainerMethod,
                instance);

            return new StartupMethods(instance, configureMethod.Build(instance), builder.Build());
        }

  哈哈果然 LoadMethods方法將我們寫在StartUp中的方法進行了封裝並創建了一個新的委托,到此我們也就明白為什么官方推薦的startup類不是繼承了Istartup接口的,繼承了接口的類在調用時沒法將DI中的類注入到方法參數中去,需要自己去獲取DI中的實例,筆者設想control類中的注入也是同樣的思想實現的。現在我們的所有中間件和服務已經全部加載進入WebHost中了。

 


免責聲明!

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



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