Exploring Program.cs, Startup.cs and CreateDefaultBuilder in ASP.NET Core 2 preview 1
ASP.NET Core 2.0 的目标之一是已经被简洁化的基础模板。简化了其基本使用,并且让开始一个新项目变得更加简单。
明显从表面上来看,新的 Program 和 Startup 类型相比于 ASP.NET Core 1.0 更加简单。现在,我将从新的 WebHost.CreateDefaultBuilder() 方法出发,看看它是如何引导你的应用程序的。
Program和Startup在ASP.NET Core 1.X中的职责
在ASP.NET Core 1.X中, Program类用来建立IWebHost。一个基础的web app模板就像这样:
public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build(); host.Run(); } }
这个相对紧凑的文件做了下面这些事情:
- 配置一个Web服务器(Kestrel)
- 设置内容目录(这个目录包括appsettings.json文件等)
- 建立IIS集
- 定义Startup类
- Build()和Run IWebHost
Startup的实现可能存在很大的差别,这取决于你建立的应用程序。 MVC模板展现的是一个典型的启动模板
public class Startup { public Startup(IHostingEnviroment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsetting.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build(); } public IConfigurationRoot Configuration { get; } public void ConfigureServices(IServiceCollection services) { // add framework services services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); if(env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{Controller=Home}/{action=Index}/{id?}" ); }); } }
这个文件主要有以下四个职责:
- 在Startup构造函数中建立配置
- 在ConfigureService中建立依赖注入关系
- 在Configure中启动Logging
- 在Configure中建立中间件管道
这些都运行的不错,但是ASP.NET团队认为还是有很多地方考虑的不够全面。
首先,设置配置比较冗长,但也很标准,总的来说不太需要过多的调整。
其次,Logging是在Startup的Configure中配置的,它建立在Configuration和DI配置完成之后。这里有两个缺点。一方面,这使Logging看上去像个二等公民-Configure通常被用来配置中间件管道,这样使得Logging配置在这里意义不大。同样也意味着你无法记录应用程序本身引导过程的日志。有很多方法能解决这个问题,但都不是特别有效。
在ASP.NET Core 2.0 preview 1中,这两点已经通过修改IWebHost角色,通过创建一个辅助方法配置应用程序。
Program和Startup在ASP.NET Core 2.0 preview 1中的责任
在ASP.NET Core 2.0 previe 1中,IWebHost的职责发生了一些改变。在之前拥有的职责上,增加了额外两点:
- Setup Configuration
- Setup Logging
此外,ASP.NET Core 2.0引入了一个辅助方法CreateDefaultBuilder。它封装了Program.cs中大部分常见的代码,以及configuration和logging!
public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .Build(); }
正如你所看到的,这里没有提及Kestrel,IIS,configuration等-这些全部放在CreateDefaultBuilder方法中处理。
将configuration和logging代码转移到这个方法中处理简化了Startup文件:
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if(env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}" ); }); } }
这个类除了logging和许多configuration代码被移除外,和1.0的非常相似。注意IConfiguration对象注入到类型的一个属性,而不是创建配置构造函数本身。
这是ASP.NET 2.0新引入的-IConfiguration对象默认通过DI注册。在1.X中,如果你需要从DI中获取IConfiguration对象,你必须自己注册。
最初,我认为CreateDefaultBuilder只是进行了模糊的设置,感觉好像有点退步,但是回想起来,这里更多的只是一个“谁动了我的奶酪”的感觉。CreateDefaultBuilder没有什么奇特的地方,他只是隐藏了一些通常保持不变的标准代码。
WebHost.CreateDefaultBuilder辅助方法
为了能确切地弄懂静态辅助方法CreateDefaultBuilder方法,我决定看一看GitHub上的源代码! 你会惊喜地发现,如果你使用过ASP.NET Core 1.X, 这些看起来都很相似。
public static IWebHostBuilder CreateDefaultBulder(string[] args) { var builder = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((hostingContext, config) => {/* setup config */}) .ConfigureLogging((hostingContext, config) => {/* setup logging */}) .UseIISIntegration() .UseDefaultServiceProvider((context, options) => {/* setup the DI container to use */}) .ConfigureServices(services => { services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>(); }); return builder; }
这里一些新的方法被我省略了,我将在以后的帖子对这些方法进行解析。你能看出来这些方法和ASP.NET Core 1.0中很大程度上做着相同的工作-建立Kestrel服务器,定义ContentRoot,建立IIS集。另外,它也做了一些其他的事情
- ConfigureAppConfiguration-这里包含过去编写在Startup的配置代码
- ConfigureLogging-建立过去在Startup.Configure中的logging配置
- UseDefaultServiceProvider-后面我会就此深入讨论, 但是这里设置内置的DI容器,并且允许你定制自定义行为
- ConfigureServices-向IWebHost中添加额外所需的组件服务。特别地,可以配置Kestrel服务的选项,允许你可以轻松定义你的Web主机就像普通配置的一部分一样。
这篇帖子中,我将仔细看看configuration和logging,其他方法的深入将在以后的帖子中展现。
在ConfigureAppConfiguration中设置app configuration
ConfigureAppConfiguration方法中是一个包含两个参数的lambda表达式-WebHostBuilderContext对象hostingContext,和IConfigurationBuilder实例config:
ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsetting.json, optional:true, reloadOnChange: true") .AddJsonFile($"appsetting.{env.Environment}.json", optional: true, reloadOnChange: true); if(env.IsDevelopment()) { var appAssembly = Assembly.Load(new Assembly(env.ApplicationName)); if(appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } config.AddEnvironmentVariables(); if(args != null) { config.AddCommandLine(args); } });
可以看到,参数hostingContext暴露了IHostingEnvironment(不管是运行在开发环境还是生产环境)作为一个属性,HostingEnvironment。如果你使用过ASP.NET Core 2.0, 除了形式上意外,大部分代码都非常相似。
一个特例是在建立User Secret时候,做法有一点点不同。它通过一个程序集引用加载User Secret,但你仍然可以使用通用config.AddUserSecrets<T>进行配置。
在ASP.NET Core 2.0中, UserSecretsId被存储在一个Assembly Attribute中,因此需要先引入Assembly中。你仍然可以使用csproj文件定义id-它将会在编译时被嵌入到assembly level attribute中。
这些都是标准的东西。它通过下面的提供者,通过下面的顺序加载配置:
- appsettings.json(optional)
- appsettings.{env.EnvironmentName}.json(optional)
- User Secrets
- Environment Variables
- Command line arguments
这个方法与ASP.NET Core 1.X的主要不同在于所处的位置-config现在作为WebHost的一部分,而不是通过构造函数植入。同时,最初的建立和最终的调用通过Web Host本身的IConfigurationBuilder,而不是你处理。
在ConfigureLogging中配置logging
ConfigureLogging方法同样是两个参数的lambda表达式-一个WebHostBuilderContext对象hostingContext,一个和configuration方法一样的LoggerFactory实例logging:
ConfigureLogging((hostingContext, logging) => { logging.UseConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); });
logging基础设施在ASP.NET Core 2.0中有一些改变。但是一般来说,这些代码与你在ASP.NET Core1.0应用程序的Configure方法中发现的建立Console和Debug日志方式遥相呼应。你可以用UseConfiguration方法设置日志级别,通过访问已定义好的暴露在hostingContext.Configuration的IConfiguration接口。
自定义WebHostBuilder
我希望通过对WebHost.CreateDefaultBuilder的深入探讨能够表达ASP.NET团队之所以使用它的原因。有许多方法可以建立并运行一个app,但是这种方式使得它更加简单。
但是,假如这不是你想要的启动方式?你可以不适用它!没有什么特殊的辅助,你可以复制粘贴实现代码到你自己的app中,定制它,同样也可以很好的工作。
这不是很确切-KestrelServerOptionsSetup类在ConfigureServices内部引用,因此你将它删除。我将在后面的帖子中进行深入分析。
总结
这篇帖子看起来像是介绍从ASP.NET Core 1.X向ASP.NET Core 2.0 preview 1转移中Program.cs和Startup.cs的不同。特别地,我稍微深入了解了一下WebHost.CreateDefaultBuilder方法,它的作用实际上只是使建立app更加简单。如果你不喜欢他给你做的选择,或者需要自己定制。你仍然可以做到这点,就像以前一样。选择在于你自己!