先初始化一个.net core 5的webapi项目。
项目初始时候文件结构如下图:
1. 首先
首先,从Program.cs
文件中的Main函数开始,这是最没有疑义的了。
如有,原因就很简单:Just it is
2. 开始分析Main函数
main函数内容如图:
public class Program
{
//注释1
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
//注释2
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.Inject()
.UseStartup<Startup>();
});
}
显然,main里面直接调用了注释2
的这个方法。
那么注释1可以怎么理解
如果把注释1的代码换一种写法,只学过一天代码的小白也会迅速理解的。
必要的知识储备:建造者模式
其中,CreateHostBuilder(args).Build()
就是建造者模式的常见写法而已,用于最终创建一个对象实例。
public static void Main(string[] args)
{
IHostBuilder hostBuilder = CreateHostBuilder(args);
IHost host = hostBuilder.Build();
host.Run();
}
那么注释2是个什么?
是个匿名函数。
换一个写法:
//是注释2的简单易懂版。
public static IHostBuilder CreateHostBuilder(string[] args)
{
//创建默认的IHostBuilder
IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args);
//显然此处是关键部分,注释3
hostBuilder.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.Inject()
.UseStartup<Startup>();
});
return hostBuilder;
}
由此可知,在main里得到一个 IHostBuildr
实例,然后 .Build()
[xxxBuilder.Build() 是 建造者模式
的特有写法,就是创建实例。],然后是.Run()
,如名称所示,含义很清楚的了。
而且, ConfigureWebHostDefaults() 这个方法的意思就是: 接收上文产生的一个 IHostBuilder 实例,并且继续给它调用 Action<IWebHostBuilder>
参数。
注释3处相关的参考信息:
参考地址: ConfigureWebHostDefaults 方法的分析
上面文档中的关键之处:
关键处:
builder
IHostBuilder
The IHostBuilder instance to configure.
configure
Action<IWebHostBuilder>
The configure callback
然后看看源码(注意找到源码不是一件容易的事):
源码出处:https://github.com/dotnet/aspnetcore.git
[上面一图的代码出自上面的链接]
文件地址:aspnetcore\src\DefaultBuilder\src\GenericHostBuilderExtensions.cs L32
源码出处2:https://github.com/dotnet/runtime.git
源码出处3:https://github.com/aspnet/Hosting.git
以上三个项目,是我找到的比较有用的M$官方源码,GitHub。
由上图的绿框,蓝框可知,是先创建了一个Default的WebHostBuilder,再 把这个WebHostBuilder交给参数。
也就是这个WebHostBuilder
又执行了UseStartup<Startup>()
方法。
3 再分析分析UseStartup()
webBuilder.UseStartup<Startup>();
中的 UserStartUp 和 怎么解读?
3.1 先上UseStartup源码
源码出处:
在刚才提到的源码出处1
的项目中,路径如下:
aspnetcore\src\Hosting\Hosting\src\WebHostBuilderExtensions.cs
3.2 再向深处跟踪
继续向里跳转(L92行的方法往里找):
aspnetcore\src\Hosting\Hosting\src\GenericHost\GenericWebHostBuilder.cs L243
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2006:UnrecognizedReflectionPattern", Justification = "We need to call a generic method on IHostBuilder.")]
private void UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, HostBuilderContext context, IServiceCollection services, object? instance = null)
{
var webHostBuilderContext = GetWebHostBuilderContext(context);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
ExceptionDispatchInfo? startupError = null;
ConfigureBuilder? configureBuilder = null;
try
{
// We cannot support methods that return IServiceProvider as that is terminal and we need ConfigureServices to compose
if (typeof(IStartup).IsAssignableFrom(startupType))
{
throw new NotSupportedException($"{typeof(IStartup)} isn't supported");
}
if (StartupLoader.HasConfigureServicesIServiceProviderDelegate(startupType, context.HostingEnvironment.EnvironmentName))
{
throw new NotSupportedException($"ConfigureServices returning an {typeof(IServiceProvider)} isn't supported.");
}
instance ??= ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType);
context.Properties[_startupKey] = instance;
// Startup.ConfigureServices
var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName);
var configureServices = configureServicesBuilder.Build(instance);
configureServices(services);
// REVIEW: We're doing this in the callback so that we have access to the hosting environment
// Startup.ConfigureContainer
var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName);
if (configureContainerBuilder.MethodInfo != null)
{
var containerType = configureContainerBuilder.GetContainerType();
// Store the builder in the property bag
_builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder;
var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType);
// Get the private ConfigureContainer method on this type then close over the container type
var configureCallback = typeof(GenericWebHostBuilder).GetMethod(nameof(ConfigureContainerImpl), BindingFlags.NonPublic | BindingFlags.Instance)!
.MakeGenericMethod(containerType)
.CreateDelegate(actionType, this);
// _builder.ConfigureContainer<T>(ConfigureContainer);
typeof(IHostBuilder).GetMethod(nameof(IHostBuilder.ConfigureContainer))!
.MakeGenericMethod(containerType)
.InvokeWithoutWrappingExceptions(_builder, new object[] { configureCallback });
}
// Resolve Configure after calling ConfigureServices and ConfigureContainer
configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName);
}
catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
{
startupError = ExceptionDispatchInfo.Capture(ex);
}
// Startup.Configure
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.ConfigureApplication = app =>
{
// Throw if there was any errors initializing startup
startupError?.Throw();
// Execute Startup.Configure
if (instance != null && configureBuilder != null)
{
configureBuilder.Build(instance)(app);
}
};
});
}
看,上图。
获取到Startup的具体class,然后还是对这个原来一路走来的 IWebHostBuilder,先 ConfigServices()
,再 Config()
。
至此就全部通了。