Asp.net Core啟動流程講解(四)

沉迷程式碼的萌新發表於2020-08-24

Asp.net Core內 DI(DependencyInjection)貫穿了專案的始終,要學習Asp.net Core就無法越過DI。
下面講解一下DI在Asp.Net Core內的流程

asp.net core 3.0以下

Asp.Net core 3.0以下有兩種自定義替換DI容器的方式
替換 IServiceProviderFactory 的預設實現,以及IStartup.Configure 函式修改返回值

1、IServiceProviderFactory

檢視 WebHostBuilder.Build

        public IWebHost Build()
        {
            var hostingServices = BuildCommonServices(out var hostingStartupErrors);
            var applicationServices = hostingServices.Clone();
            var hostingServiceProvider = GetProviderFromFactory(hostingServices);

            AddApplicationServices(applicationServices, hostingServiceProvider);

            var host = new WebHost(
                applicationServices,
                hostingServiceProvider,
                _options,
                _config,
                hostingStartupErrors);
            try
            {
                host.Initialize();
                
                //省略不重要的程式碼段

                return host;
            }
            catch
            {
                host.Dispose();
                throw;
            }

            IServiceProvider GetProviderFromFactory(IServiceCollection collection)
            {
                var provider = collection.BuildServiceProvider();
                var factory = provider.GetService<IServiceProviderFactory<IServiceCollection>>();

                if (factory != null && !(factory is DefaultServiceProviderFactory))
                {
                    using (provider)
                    {
                        return factory.CreateServiceProvider(factory.CreateBuilder(collection));
                    }
                }

                return provider;
            }
        }

再跟進 BuildCommonServices 函式

        private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
        {
            var services = new ServiceCollection();

            //省略不重要的程式碼段

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

            _configureServices?.Invoke(_context, services);

            return services;
        }

思路很簡,註冊DI,然後返回 IServiceProviderHost 容器,找到 IServiceProviderFactory ,判斷型別不是
DefaultServiceProviderFactory,則轉換DI,最後返回新的 IServiceProvider

       public interface IServiceProviderFactory<TContainerBuilder>
       {

              TContainerBuilder CreateBuilder(IServiceCollection services);

              IServiceProvider CreateServiceProvider(TContainerBuilder 
containerBuilder);
       }

WebHostBuilder.ConfigureServices 內DI註冊 預設了實現的IServiceProviderFactory
IServiceProviderFactory 的流程是 IServiceProviderFactory.CreateBuilder->IServiceProviderFactory.CreateServiceProvider
先把DI容器 IServiceCollection 轉換成自定義容器,再通過自定義容器轉換成 IServiceProvider,最後依賴於根節點的IServiceProvider 即可

註冊 IServiceProviderFactory 還有一處邏輯
WebHostBuilder.UseDefaultServiceProvider 轉到原始碼

        public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ServiceProviderOptions> configure)
        {
            return hostBuilder.ConfigureServices((context, services) =>
            {
                var options = new ServiceProviderOptions();
                configure(context, options);
                //替換預設的IServiceProviderFactory<IServiceCollection>
                services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IServiceCollection>>(new DefaultServiceProviderFactory(options)));
            });
        }

異曲同工之妙,不再贅述。

2、IStartup.Configure

還是開啟熟悉的 WebHostBuilder.BuildCommonServices

        private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
        {
            var services = new ServiceCollection();

            //忽略無關程式碼

            if (!string.IsNullOrEmpty(_options.StartupAssembly))
            {
                try
                {
                    var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);

                    if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
                    {
                        services.AddSingleton(typeof(IStartup), startupType);
                    }
                    else
                    {
                        services.AddSingleton(typeof(IStartup), sp =>
                        {
                            var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();
                            var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
                            return new ConventionBasedStartup(methods);
                        });
                    }
                }
                catch (Exception ex)
                {
                    var capture = ExceptionDispatchInfo.Capture(ex);
                    services.AddSingleton<IStartup>(_ =>
                    {
                        capture.Throw();
                        return null;
                    });
                }
            }

            _configureServices?.Invoke(_context, services);

            return services;
        }

註冊 IStartup 到 DI

再檢視 WebHostBuilder.Build

        public IWebHost Build()
        {
            var hostingServices = BuildCommonServices(out var hostingStartupErrors);
            var applicationServices = hostingServices.Clone();
            var hostingServiceProvider = GetProviderFromFactory(hostingServices);

            var host = new WebHost(
                applicationServices,
                hostingServiceProvider,
                _options,
                _config,
                hostingStartupErrors);
            try
            {
                host.Initialize();  //關建程式碼行
                
                //省略不重要的程式碼段

                return host;
            }
            catch
            {
                host.Dispose();
                throw;
            }
        }

檢視 WebHost.Initialize 程式碼

        public void Initialize()
        {
            try
            {
                EnsureApplicationServices();
            }
            catch (Exception ex)
            {
                // EnsureApplicationServices may have failed due to a missing or throwing Startup class.
                if (_applicationServices == null)
                {
                    _applicationServices = _applicationServiceCollection.BuildServiceProvider();
                }

                if (!_options.CaptureStartupErrors)
                {
                    throw;
                }

                _applicationServicesException = ExceptionDispatchInfo.Capture(ex);
            }
        }

檢視 WebHost.EnsureApplicationServices 程式碼

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

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

            _startup = _hostingServiceProvider.GetService<IStartup>();

            if (_startup == null)
            {
                throw new InvalidOperationException($"No application configured. Please specify startup via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, injecting {nameof(IStartup)} or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration.");
            }
        }

獲取 IStartup 介面的例項,然後呼叫 IStartup.ConfigureServices 返回 IServiceProvider 例項

Asp.Net Core 3.0以上

Asp.Net Core 3.0以下的設計程式碼過於混亂,在Asp.Net Core 3.0開始,替換DI的流程變得簡潔了

預設程式入口

    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

檢視 Host.CreateDefaultBuilder

        public static IHostBuilder CreateDefaultBuilder(string[] args)
        {
            var builder = new HostBuilder();

            //忽略無關程式碼

            return builder;
        }

轉向 HostBuilder.Build

        public IHost Build()
        {
            //忽略無關程式碼
            CreateServiceProvider();

            return _appServices.GetRequiredService<IHost>();
        }

檢視 HostBuilder.CreateServiceProvider

        private void CreateServiceProvider()
        {
            var services = new ServiceCollection();
            //忽略無關程式碼

            var containerBuilder = _serviceProviderFactory.CreateBuilder(services);

            foreach (var containerAction in _configureContainerActions)
            {
                containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
            }

            _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);

            if (_appServices == null)
            {
                throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider.");
            }

            // resolve configuration explicitly once to mark it as resolved within the
            // service provider, ensuring it will be properly disposed with the provider
            _ = _appServices.GetService<IConfiguration>();
        }

這裡有個 _serviceProviderFactory,轉到定義

    public class HostBuilder : IHostBuilder
    {
        //忽略無關程式碼
        private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
    }

對應傳參,有兩個函式不同過載的UseServiceProviderFactory

        public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
        {
            _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
            return this;
        }
        
        public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
        {
            _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(() => _hostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory)));
            return this;
        }        

後記

檸檬大佬說Asp.Net Core至少有三處可以替換DI的地方~ 還有一處
看了一下原始碼,大概是 IApplicationBuilderFactory

    public interface IApplicationBuilderFactory
    {
        IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures);
    }

這裡IApplicationBuilderFactory.CreateBuilder返回 IApplicationBuilder
IApplicationBuilder裡的Ioc物件可以在這裡設定

如果對於內容有交流和學習的,可以加 .Net應用程式框架交流群,群號386092459

相關文章