ASP.NET Core 6框架揭秘实例演示[23]:ASP.NET Core应用承载方式的变迁
ASP.NET Core应用本质上就是一个由中间件构成的管道,承载系统将应用承载于一个托管进程中运行起来,其核心任务就是将这个管道构建起来。从设计模式的角度来讲,“管道”是构建者(Builder)模式最典型的应用场景,所以ASP.NET Core先后采用的三种承载方式都是采用这种模式。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)
[S1501]基于IWebHost/IWebHostBuilder的应用承载方式(源代码)
[S1502]将初始化设置定义在Startup类型中(源代码)
[S1503]基于IHost/IHostBuilder的应用承载方式(源代码)
[S1504]Minimal API(源代码)
[S1501]基于IWebHost/IWebHostBuilder的应用承载方式
ASP.NET Core Core 1.X/2.X采用的承载模型以IWebHostBuilder和IWebHost为核心。IWebHost对象代表承载Web应用的宿主(Host),管道随着IWebHost对象的启动被构建出来。IWebHostBuilder对象作为宿主对象的构建者,我们针对管道构建的设置都应用在它上面。
这种“原始”的应用承载方式依然被保留了下来,如下这个Hello World应用就是采用的这种承载方式。如代码片段所示,我们先创建一个实现了IWebHostBuilder接口的WebHostBuilder对象,并调用其UseKestrel扩展方法注册了一个Kestrel服务器。我们接下来调用它的Configure方法利用提供的Action<IApplicationBuilder>委托注册了一个中间件,该中间件将指定的“Hello World”文本作为响应内容。我们调用IWebHostBuilder对象的Build方法将作为宿主的IWebHost对象构建出来后,我们调用其Run扩展方法将它启动起来。此时同构注册的服务器和中间件组成的管道被构建起来,服务器开始监听、接收请求,在将请求交付给后续的中间件进行处理后,它会将响应回复给客户端。
new WebHostBuilder() .UseKestrel() .Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!"))) .Build() .Run();
按照“面向接口编程”的原则,其实我们不应该调用构造函数去创建一个“空”的WebHostBuilder对象并自行完成针对它的所有设置,而是选择按照如下的方式调用定义在静态类型WebHost中的工厂方法CreateDefaultBuilder去创建一个具有默认设置的IWebHostBuilder对象。由于Kestrel服务器的配置就属于“默认设置”的一部分,针对UseKestrel扩展方法的调用也不再需要。
using Microsoft.AspNetCore; WebHost.CreateDefaultBuilder() .Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!"))) .Build() .Run();
[S1502]将初始化设置定义在Startup类型中
如果管道涉及过多的 中间件需要注册,我们还可以将“中间件注册”这部分工作实现在一个按照约定定义的Startup类型中。由于ASP.NET Core建立在依赖注入框架之上,所以应用往往需要涉及到很多服务注册,我们一般也会将“服务注册”的工作也放在这个Startup类型中。我们最终只需要按照如下的方式将这个Startup注册到创建的IWebHostBuilder对象上就可以了。
using Microsoft.AspNetCore; WebHost.CreateDefaultBuilder() .UseStartup<Startup>() .Build() .Run(); public class Startup { public void ConfigureServices(IServiceCollection services)=> services.AddSingleton<IGreeter, Greeter>(); public void Configure(IApplicationBuilder app, IGreeter greeter)=> app.Run(context => context.Response.WriteAsync(greeter.Greet())); } public interface IGreeter { string Greet(); } public class Greeter : IGreeter { public string Greet() => "Hello World!"; }
[S1503]基于IHost/IHostBuilder的应用承载方式
除了承载Web应用,我们还有很多针对后台服务(比如很多批处理任务)的承载需求,为此微软推出了以IHostBuilder/IHost为核心的服务承载系统。Web应用本身实际上就是一个长时间运行的后台服务,我们完全可以将应用定义成一个IHostedService服务(GenericWebHostService)。如果将上面介绍的称为第一代应用承载模式的话,这就是第二代承载模式。IHostBuilder接口定义的很多方法(其中很多是扩展方法)旨在完成两个方面的设置:第一,为创建的IHost对象及承载的IHostedService服务注册依赖服务;第二,为服务承载和应用提供相应的配置。如果采用基于IWebHostBuilder/IWebHost的承载方式,上述这两方面的设置由IWebHostBuilder对象来完成,后者在此基础上还提供了针对中间件的注册。
虽然IWebHostBuilder接口提供的除中间件注册的其他设置基本可以调用IHostBuilder接口相应的方法来完成,但是由于IWebHostBuilder承载的很多配置都是以扩展方法的形式提供的,所以有必要提供针对IWebHostBuilder接口的兼容。基于IHostBuilder/IHost的承载系统中提供对IWebHostBuilder接口的兼容是通过如下所示的ConfigureWebHost扩展方法达成的,GenericWebHostService承载服务也是在这个方法中被注册的。ConfigureWebHostDefaults扩展方法则会在此基础上做一些默认设置(如KestrelServer)。
public static class GenericHostWebHostBuilderExtensions { public static IHostBuilder ConfigureWebHost(this IHostBuilder builder,Action<IWebHostBuilder> configure); public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder); public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure) }
如果采用基于IHostBuilder/IHost的承载方式,上面演示的“Hello World”应用可以改写成如下的形式。如代码片段所示,在调用Host的静态工厂方法CreateDefaultBuilder创建出具有默认设置的IHostBuilder对象之后,我们调用它的ConfigureWebHostDefaults扩展方法针对承载ASP.NET Core应用的GenericWebHostService做进一步设置。该方法提供的Action<IApplicationBuilder>委托完成了针对Startup类型的注册(S1503)。
Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.UseStartup<Startup>()) .Build() .Run(); public class Startup { public void ConfigureServices(IServiceCollection services) => services.AddSingleton<IGreeter, Greeter>(); public void Configure(IApplicationBuilder app, IGreeter greeter)=> app.Run(context => context.Response.WriteAsync(greeter.Greet())); } public interface IGreeter { string Greet(); } public class Greeter : IGreeter { public string Greet() => "Hello World!"; }
[S1504]Minimal API
“天下大势,分久必合,合久必分”,ASP.NET Core应用通过GenericWebHostService这个承载服务被整合到基于IHostBuilder/IHost的服务承载系统中之后,也许微软还是意识到Web应用和后台服务的承载方式还是应该加以区分,而且它们采用的SDK都不一样(ASP.NET Core应用采用的SDK为“Microsoft.NET.Sdk.Web”,后台服务采用的SDK一般为“Microsoft.NET.Sdk.Worker”),于是推出了基于WebApplicationBuilder/WebApplication的承载方式。但这一次并非又回到了起点,因为底层的承载方式其实没有改变,它只是在上面再封装了一层而已。
新的应用承载方式依然采用“构建者(Builder)”模式,核心的两个对象分别为WebApplication和WebApplicationBuilder,代表承载应用的WebApplication对象由WebApplicationBuilder对象进行构建。我们可以将其称为第三代承载模式,它有一个官方的名称叫做“Minimal API”。第二代承载模式需要提供针对IWebHostBuilder接口的兼容,作为第三代承载模式的Minimal API则需要同时提供针对IWebHostBuilder和IHostBuilder接口的兼容,此兼容性是通过这两个接口的实现类型ConfigureWebHostBuilder和ConfigureHostBuilder达成的。
WebApplicationBuilder类型的WebHost和Host属性返回了这两个对象,之前定义在IWebHostBuilder和IHostBuilder接口上的绝大部分API(并非所有API)借助它们得以复用。也正是有了这段历史,我们会发现相同的功能具有两到三种不同的编程方式。比如IWebHostBuilder和IHostBuilder接口上都提供了注册服务的方法,而WebApplicationBuilder类型利用Services属性直接将存放服务注册的IServiceCollection对象暴露出来,所以任何的服务注册都可以利用这个属性来完成。
public sealed class WebApplicationBuilder { public ConfigureWebHostBuilder WebHost { get; } public ConfigureHostBuilder Host { get; } public IServiceCollection Services { get; } public ConfigurationManager Configuration { get; } public ILoggingBuilder Logging { get; } public IWebHostEnvironment Environment { get; } public WebApplication Build(); } public sealed class ConfigureWebHostBuilder : IWebHostBuilder, ISupportsStartup public sealed class ConfigureHostBuilder : IHostBuilder, ISupportsConfigureWebHost
IWebHostBuilder和IHostBuilder接口都提供了设置配置和日志的方法,这两方面的设置都可以利用WebApplicationBuilder利用Configuration和Logging暴露出来的ConfigurationManager和ILoggingBuilder对象来实现。既然我们采用了Minimal API,那么我们就应该尽可能得使用WebApplicationBuilder类型提供得API。如果采用这种全新的承载方式,我们演示的Hello World程序可以改写成如下的形式。如代码片段所示,我们调用定义在WebApplication类型中的静态工厂方法CreateBuilder根据指定的命令行参数(args)创建一个WebApplicationBuilder对象,并调用其Build方法构建出对代表承载Web应用的WebApplication对象。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run(context => context.Response.WriteAsync("Hello World!"));
app.Run();
接下来我们调用了它的两个Run方法,调用得第一个Run方法是IApplicationBuilder接口(WebApplication类型实现了该接口)的扩展方法是为了注册中间件,调用第二个Run方法才是启动WebApplication对象代表的应用。由于我们并没有在WebApplicationBuilder对象上作任何设置,所以我们可以按照如下的方式调用WebApplication的静态Create方法将WebApplication对象创建出来。
var app = WebApplication.Create(args);
app.Run(context => context.Response.WriteAsync("Hello World!"));
app.Run();
值得一提的是,之前的两种承载方式都倾向于将初始化操作定义在注册的Startup类型中,这种编程在Minimal API中不再被支持,所以如下的程序虽然可以成功编译,但是执行的时候会抛出异常。
var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseStartup<Startup>(); var app = builder.Build(); app.Run();