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
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,然後返回 IServiceProvider 給 Host 容器,找到 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