ASP.NET Core应用本质上就是一个由中间件构成的管道,承载系统将应用承载于一个托管进程中运行起来,其核心任务就是将这个管道构建起来。从设计模式的角度来讲,“管道”是构建者(Builder)模式最典型的应用场景,所以ASP.NET Core先后采用的三种承载方式都是采用这种模式。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)

[S1501]基于IWebHost/IWebHostBuilder的应用承载方式(源代码

[S1502]将初始化设置定义在Startup类型中(源代码

[S1503]基于IHost/IHostBuilder的应用承载方式(源代码

[S1504]将初始化设置定义在Startup类型中(源代码

[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]将初始化设置定义在Startup类型中

“天下大势,分久必合,合久必分”,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类型中,这种编程在Mininal API中不再被支持,所以如下的程序虽然可以成功编译,但是执行的时候会抛出异常。

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseStartup<Startup>();
var app = builder.Build();
app.Run();

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

  1. ASP.NET Core 6框架揭秘实例演示[24]:中间件的多种定义方式

    ASP.NET Core的请求处理管道由一个服务器和一组中间件组成,位于 "龙头" 的服务器负责请求的监听.接收.分发和最终的响应,针对请求的处理由后续的中间件来完成.中间件最终体 ...

  2. ASP.NET Core 6框架揭秘实例演示[21]:如何承载你的后台服务

    借助 .NET提供的服务承载(Hosting)系统,我们可以将一个或者多个长时间运行的后台服务寄宿或者承载我们创建的应用中.任何需要在后台长时间运行的操作都可以定义成标准化的服务并利用该系统来承载,A ...

  3. ASP.NET Core 6框架揭秘实例演示[07]:文件系统

    ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...

  4. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  5. ASP.NET Core 6框架揭秘实例演示[09]:配置绑定

    我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...

  6. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  7. ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式

    在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...

  8. ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法

    一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...

  9. ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]

    <诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...

随机推荐

  1. 推荐的php安全配置选项

    推荐安全配置选项 这里有几个会影响安全功能的 PHP 配置设置.下面是一些显然应该用于生产服务器的: register_globals 设置为 offsafe_mode 设置为 offerror_re ...

  2. 关于Miller-Rabin与Pollard-Rho算法的理解(素性测试与质因数分解)

    前置 费马小定理(即若P为质数,则\(A^P\equiv A \pmod{P}\)). 欧几里得算法(GCD). 快速幂,龟速乘. 素性测试 引入 素性测试是OI中一个十分重要的事,在数学毒瘤题中有着 ...

  3. Docker使用Dockerfile构建新的镜像

    构建镜像步骤; 1.创建Dockerfile文件,该文件是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明. vim Dockerfile //每一个指令都会在镜像上创建一个新 ...

  4. springBoot工程解决跨域问题

    更新:通过一个 @CrossOrigin  注解就可以完美解决跨域问题. 创建一个配置类 package com.miaoshaProject.configuration; import org.sp ...

  5. WebGPU 中消失的 FBO 和 RBO

    目录 1 WebGL 中的 FBO 与 RBO 1.1 帧缓冲对象(FramebufferObject) 1.2 颜色附件与深度模板附件的真正载体 1.3 FBO/RBO/WebGLTexture 相 ...

  6. Java中的多线程你只要看这一篇就够了(引用)

    引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个 ...

  7. 1python基础语法_11模块

    http://www.runoob.com/python3/python3-module.html 模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py.模块可以被别的程序引入,以使用该模块中 ...

  8. Ubuntu下pip3的安装、升级、卸载

    1.安装 sudo apt-get install python3-pip 2.升级 sudo pip3 install --upgrade pip 3.卸载 sudo apt-get remove ...

  9. ConcurrentHashMap怎么保证安全的

    HashMap是一个线程不安全的容器,当容量大于总量*负载因子发生扩容时可能会出现环形链表从而导致死循环 扩容就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下, ...

  10. PHP面试常考之会话控制

    你好,是我琉忆,欢迎您来到PHP面试专栏.本周(2019.2-25至3-1)的一三五更新的文章如下: 周一:PHP面试常考之会话控制周三:PHP面试常考之网络协议周五:PHP面试常考题之会话控制和网络 ...