ASP.NET Core 6框架揭秘实例演示[23]:ASP.NET Core应用承载方式的变迁

2022-05-09 12:47:20 浏览数 (1)

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扩展方法将它启动起来。此时同构注册的服务器和中间件组成的管道被构建起来,服务器开始监听、接收请求,在将请求交付给后续的中间件进行处理后,它会将响应回复给客户端。

代码语言:javascript复制
new WebHostBuilder()
    .UseKestrel()
    .Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!")))
    .Build()
.Run();

按照“面向接口编程”的原则,其实我们不应该调用构造函数去创建一个“空”的WebHostBuilder对象并自行完成针对它的所有设置,而是选择按照如下的方式调用定义在静态类型WebHost中的工厂方法CreateDefaultBuilder去创建一个具有默认设置的IWebHostBuilder对象。由于Kestrel服务器的配置就属于“默认设置”的一部分,针对UseKestrel扩展方法的调用也不再需要。

代码语言:javascript复制
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对象上就可以了。

代码语言:javascript复制
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)。

代码语言:javascript复制
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)。

代码语言:javascript复制
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对象暴露出来,所以任何的服务注册都可以利用这个属性来完成。

代码语言:javascript复制
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对象。

代码语言:javascript复制
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对象创建出来。

代码语言:javascript复制
var app = WebApplication.Create(args);
app.Run(context => context.Response.WriteAsync("Hello World!"));
app.Run();

值得一提的是,之前的两种承载方式都倾向于将初始化操作定义在注册的Startup类型中,这种编程在Minimal API中不再被支持,所以如下的程序虽然可以成功编译,但是执行的时候会抛出异常。

代码语言:javascript复制
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseStartup<Startup>();
var app = builder.Build();
app.Run();

0 人点赞