参考微软文档吧, 很详细: docs.microsoft.com/en-us/aspne…ios
建立完后会有个web项目, 打开后我这面的层级目录以下:git
//为何关注这个类, 由于这里有main函数, 通常来讲main函数都是程序启动的时候的启动类. 看一下这行代码:
public static void Main(string[] args) {
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
复制代码
看到这个 Main 函数时我顿时有种似曾相识的感受, 这个不是 console application 中的 Main 函数么, 两者莫非有关联?github
在个人理解, 其实asp.net core web application其本质应该就是一个 console application,而后经过内置的 web server 去处理 http 请求。这个内置的 web sever 默认是 Kestre, Web server 通常是须要 host在某个宿主上的, 比较 common 的 host 环境有 IIS, windows servie, Nginx, 或者 Docker等等. 这时候你要问我宿主是干什么用的以及为什么要host到宿主上面? 我以为这就稍微超出咱们这篇文章的 scope 了. 简单来讲就是宿主是用来启动咱们的 asp.net core 的程序的. 而且会监听程序的运行状态可以作到一些配置和审核权限等其余的工做, 而且咱们的 web server 能够作成分布式的 server. 它们都分别接收由宿主分发过来的 http 请求, 那么宿主又能够承担做为一个反向代理的角色. 对这些内容若是感兴趣的话能够看看官方的材料, docs.microsoft.com/en-us/aspne…, 这里就很少写了.web
那么回头来看 asp.net core 的 main 函数都作了什么?json
1.建立 WebHostBuilder 对象windows
2.让这个 WebHostBuilder 对象 build一个 webhost 并run起来缓存
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
复制代码
WebHost.CreateDefaultBuilder(args)
打开源码文件路径(..\AspNetCore-2.2.4\src\DefaultBuilder\src)
(由于源码中不少叫WebHost的文件, 容易找不到), 咱们来看一下 CreateDefaultBuilder
这个方法的源码:cookie
/// <summary>
/// Initializes a new instance of the <see cref="WebHostBuilder"/> class with pre-configured defaults.
/// </summary>
/// <remarks>
/// The following defaults are applied to the returned <see cref="WebHostBuilder"/>:
/// use Kestrel as the web server and configure it using the application's configuration providers,
/// set the <see cref="IHostingEnvironment.ContentRootPath"/> to the result of <see cref="Directory.GetCurrentDirectory()"/>,
/// load <see cref="IConfiguration"/> from 'appsettings.json' and 'appsettings.[<see cref="IHostingEnvironment.EnvironmentName"/>].json',
/// load <see cref="IConfiguration"/> from User Secrets when <see cref="IHostingEnvironment.EnvironmentName"/> is 'Development' using the entry assembly,
/// load <see cref="IConfiguration"/> from environment variables,
/// load <see cref="IConfiguration"/> from supplied command line args,
/// configure the <see cref="ILoggerFactory"/> to log to the console and debug output,
/// and enable IIS integration.
/// </remarks>
/// <param name="args">The command line args.</param>
/// <returns>The initialized <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder CreateDefaultBuilder(string[] args) {
var builder = new WebHostBuilder();
if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
{
builder.UseContentRoot(Directory.GetCurrentDirectory());
}
if (args != null)
{
builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
}
builder.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"));
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
})
.ConfigureServices((hostingContext, services) =>
{
// Fallback
services.PostConfigure<HostFilteringOptions>(options =>
{
if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
{
// "AllowedHosts": "localhost;127.0.0.1;[::1]"
var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
// Fall back to "*" to disable.
options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
}
});
// Change notification
services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));
services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
})
.UseIIS()
.UseIISIntegration()
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
});
return builder;
}
复制代码
根据源码,咱们来总结一下 CreateDefaultBuilder
所作的工做:mvc
UseKestrel:使用Kestrel做为Web server。
UseContentRoot:指定Web host使用的content root(内容根目录),好比Views。默认为当前应用程序根目录。
ConfigureAppConfiguration:设置当前应用程序配置。主要是读取 appsettinggs.json 配置文件、开发环境中配置的UserSecrets、添加环境变量和命令行参数 。
ConfigureLogging:读取配置文件中的Logging节点,配置日志系统。
UseIISIntegration:使用IISIntegration 中间件。
UseDefaultServiceProvider:设置默认的依赖注入容器。app
.UseStartup<Startup>();
咱们直接F12
//
// Summary:
// Specify the startup type to be used by the web host.
//
// Parameters:
// hostBuilder:
// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure.
//
// Type parameters:
// TStartup:
// The type containing the startup methods for the application.
//
// Returns:
// The Microsoft.AspNetCore.Hosting.IWebHostBuilder.
public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class;
复制代码
固然这些不够, 咱们的目的是知道底层是用怎么使用的startup
/// <summary>
/// Specify the startup type to be used by the web host.
/// </summary>
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <param name="startupType">The <see cref="Type"/> to be used.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
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));
});
}
});
}
复制代码
能够看到其实UseStartup
方法返回的是IWebHostBuilder
对象, 其中.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
是将 ApplicationKey
和startupAssemblyName
缓存起来. 以下图:
而后.ConfigureServices(Action<IServiceCollection> configureServices)
实际上也是对list集合进行一个缓存操做, 可是注意这个方法的参数是一个委托Action, 实际上调用的时候传递的是一个lambda表达式, 咱们须要看看这个表达式里的神奇
操做:(咱们主要关注 else 里面的部分, if中的没啥可说的.)
services.AddSingleton(typeof(IStartup), sp =>
{
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
});
复制代码
services是咱们 asp.net core DI的核心ServiceCollection
, 而后AddSingleton
方法是它内部的一个静态方法
public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);
复制代码
因此咱们看到的 sp
其实是 IServiceProvider
, 先暂时理解成是用来搞到 service 的. 而后精髓来了,
StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)
复制代码
参数咱们差很少都知道表明什么. 可是这个LoadMethods
是干吗的?? 咱们看一下解释:
很长, 可是主要就是用于经过反射技术, 将咱们自定义的StartUp.cs文件里面的方法都load到低层来. 而后顺便配置一下每次都有的那个service
和建立一个request pipeline of the application
这里我以为有必要将startup.cs文件中的两个function贴到下面方便咱们后续的撸源码:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
复制代码
看完这两个方法, 咱们都能明白一个事儿就是这两个方法都是运行时调用的. 至于为何是运行时调用的, 我们差很少都知道了, 由于底层build的时候反射获取这两个方法, 将两个方法的配置参数什么的配置进底层的service中.
这里有必要解释一下咱们的自定义 startup 文件里面的参数含义, 由于这毕竟是暴露给咱们开发者使用的, 因此应该多了解一下:
IServiceCollection
:当前容器中各服务的配置集合,ASP.NET Core内置的依赖注入容器。
IApplicationBuilder
:用于构建应用程序的请求管道。
IHostingEnvironment
:提供了当前的 EnvironmentName、WebRootPath 以及 ContentRoot等。
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());
}
复制代码
(反射已经用到了出神入化的地步)
想看怎么实现的就本身去看吧. 反正代码贴到这里, 意思明白了就行.
好了, 目前知道了.ConfigureService
的参数是干吗的了... 那看看这个方法的底层要这个参数作啥了吧:
private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates;
/// <summary>
/// Adds a delegate for configuring additional services for the host or web application. This may be called
/// multiple times.
/// </summary>
/// <param name="configureServices">A delegate for configuring the <see cref="IServiceCollection"/>.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices) {
if (configureServices == null)
{
throw new ArgumentNullException(nameof(configureServices));
}
return ConfigureServices((_, services) => configureServices(services));
}
/// <summary>
/// Adds a delegate for configuring additional services for the host or web application. This may be called
/// multiple times.
/// </summary>
/// <param name="configureServices">A delegate for configuring the <see cref="IServiceCollection"/>.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices) {
if (configureServices == null)
{
throw new ArgumentNullException(nameof(configureServices));
}
_configureServicesDelegates.Add(configureServices);
return this;
}
复制代码
卧槽槽, 又是一个缓存....行吧, 底层各类搞缓存, 不就是DI的本质吗..这算基本搞懂了UseStartup
..
那么WebHostBuilder
建立出来后, 里面有两个缓存, 对startup的类型进行缓存以外, 还对startup的services进行缓存. 而后这个builder显然是要进行 build 的, 否则费那么大力气建立干吗. 因此看下一篇吧, 解读后续操做.