.NET6之后有一种新的默认方式来构建应用使用 WebApplication.CreateBuilder() 方法。在这篇文章中,我将把这种方法与之前的方法进行对比,讨论为什么会进行这样的改变,并分析其影响。
- ASP.NET Core 应用程序的构建历史
在开始之前回顾一下ASP.NET Core Bootstrap过去几年的演变,因为原始的设计到今天发生了很大的变化。
在除了.NET 1.X之外,仍然有三种不同的方式来配置一个 ASP.NET Core 应用程序:
WebHost.CreateDefaultBuilder():这个版本是 ASP.NET Core 2.x 中配置 ASP.NET Core 应用程序的原始方法。
Host.CreateDefaultBuilder():基于通用主机重新构建的 ASP.NET Core 配置方法,支持其他工作负载,例如 后台服务。是 .NET Core 3.x 和 .NET 5 中的默认方法。
WebApplication.CreateBuilder():.NET 6 之后,用于配置 ASP.NET Core 应用程序的最新方式。
为了更好的理解以及比较这三种方式,我们从2.x到今天的6.0之间变化较大,6.0到9.0基本一致。
- ASP.NET Core 2.x: WebHost.CreateDefaultBuilder()
ASP.NET Core 1.x 的第一个版本中,当时并没有默认主机的概念。ASP.NET Core 的一个核心理念是pay for play,只有当你需要某个功能时,它才会被加载、启用或消耗资源。如果你不使用某个功能,它就不会给你的系统带来额外的负担(如性能开销、资源占用或复杂性)
实际上,这意味着入门模板包含了大量的样板代码以及许多 NuGet 包。为了减轻开发者在一开始看到大量代码时的困惑,ASP.NET Core 引入了 WebHost.CreateDefaultBuilder()。它为开发者预设了一整套默认配置,帮助创建一个 IWebHostBuilder 并构建一个 IWebHost,从而大大简化了应用程序的启动流程。
从一开始,ASP.NET Core 就将Host的引导过程与Application层分离开来。传统上,这体现在将启动代码分拆到两个文件中,通常分别命名为 Program.cs 和 Startup.cs。
2.1中Program.cs调用WebHost.CreateDefaultBuilder(),该方法会设置应用程序的配置(例如从 appsettings.json加载),配置日志记录,并设置 Kestrel 和/或 IIS 的集成。
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup()
.Build();
}
默认模板还会引用一个 Startup 类。这个类实没有实现任何接口,IWebHostBuilder查找 ConfigureServices和Configure 方法,分别用于设置依赖注入容器和中间件管道。
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// 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();
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: “default”,
template: “{controller=Home}/{action=Index}/{id?}”);
});
}
Startup 类中,我们将 MVC 服务添加到容器中,配置了异常处理和静态文件中间件,然后添加了 MVC 中间件。最初,MVC 中间件是构建应用程序的唯一实际可行的方法,能够同时满足服务器端渲染视图和 RESTful API 端点的需求。
- ASP.NET Core 3.x/5: 通用HostBuilder
ASP.NET Core 3.x为 ASP.NET Core 的启动代码带来了一些重大变化。ASP.NET Core 主要用于 Web/HTTP 工作负载,但在 .NET Core 3.x 中,转向支持非HTTP工作负载,例如:长时间运行的工作服务(比如用于消费消息队列)、gRPC 服务、Windows 服务等。目标是构建 Web 应用程序开发的基础框架如:配置、日志记录、依赖注入等,并将这些基础框架与其他应用类型共享。
最终的结果是创建了一个通用主机Generic Host与 Web Host 相对,并在其基础上对 ASP.NET Core 栈进行了重新构建。不再使用 IWebHostBuilder,而是引入了 IHostBuilder。
这个改变带来了一些破坏性更改,ASP.NET 团队尽最大努力为使用 IWebHostBuilder 而非 IHostBuilder 编写的所有代码提供了解决方案。其中一个解决方法是默认在Program.cs 模板中使用的 ConfigureWebHostDefaults() 方法:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
};
}
}
尽管发生了这些变化,ASP.NET Core 3.x 中的 Startup 类看起来与 2.x 版本非常相似。下面的示例几乎等同于 2.x 版本(不过这里改用了 Razor Pages 而不是 MVC)。
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
ASP.NET Core 5 对现有应用程序带来的重大变化相对较少,因此从 3.x 升级到5通常只需更改目标框架并更新一些 NuGet 包 对于 .NET 6,如果你是在升级现有应用程序,情况应该仍然如此。但对于新应用程序,默认的引导启动已经发生了完全的变化。
4.ASP.NET Core 6: WebApplicationBuilder
之前所有版本的 ASP.NET Core 都将配置分散在两个文件中。.NET 6 中,由于 C#、BCL 和 ASP.NET Core 的一系列变化,现在可以将所有内容都放在一个文件中。
但是这种风格并不是强制的。我之前展示的 ASP.NET Core 3.x/5 的代码在 .NET 6 中仍然可以正常运行。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.MapGet(“/”, () => “Hello World!”);
app.MapRazorPages();
app.Run();
这里有很多变化,但一些最明显的变化是:
顶层语句(Top-level statements)意味着不再需要 Program.Main() 的模板代码。
隐式 using 指令(Implicit using directives)意味着不需要显式的 using 声明。在之前版本的代码片段中我没有包含这些,但在 .NET 6 中根本不需要!
没有 Startup 类——所有内容都在一个文件中。
代码显然少了很多,但这是必要的吗?这只是为了改变而改变吗?它是如何运作的?
- 代码都去哪儿了?
.NET 6 的一个重要重点是从新手角度出发。对于刚接触 ASP.NET Core 的初学者来说,有许多概念需要快速理解。
.NET 6 的变化主要集中在减少入门时的繁琐流程,并隐藏可能让新手感到困惑的概念。例如:
using 声明在刚开始时是不必要的。虽然工具通常让它们在实践中可以帮助你,但对于初学者来说,这显然是一个多余的概念。
类似地,namespace 对于入门来说也是一个不必要的概念。
Program.Main()……为什么叫这个名字?为什么需要它?答案是:因为你需要它。但现在你不需要了。
配置不再分散在两个文件(Program.cs 和 Startup.cs)中。虽然我喜欢这种关注点分离的设计,但我不会怀念向新手解释为什么要分成两个文件。
说到 Startup,我们不再需要解释那些魔法方法”,它们能被调用,即使它们没有显式实现某个接口。
此外,还引入了新的 WebApplication 和 WebApplicationBuilder 类型。虽然这些类型并非实现上述目标的必需品,但它们确实让配置体验变得更简洁。
- 我们真的需要一个新类型吗?
实际上,并不需要。我们完全可以使用通用主机(generic host)来编写一个与上述示例非常相似的 .NET 6 应用程序:
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddRazorPages();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.Configure((ctx, app) =>
{
if (ctx.HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet(“/”, () => “Hello World!”);
endpoints.MapRazorPages();
});
});
});
hostBuilder.Build().Run();
相比 .NET 6 的 WebApplication,这看起来要复杂得多。我们有一堆嵌套的 lambda 表达式,需要确保使用正确的重载才能访问配置,而且总体来说,这会把一个(大部分是)过程式的引导脚本变得更加复杂。
WebApplicationBuilder 的另一个好处是,启动时的异步代码变得更加简单。你可以随时调用异步方法。
WebApplicationBuilder 和 WebApplication 的巧妙之处在于,它们本质上等同于上述的通用主机设置,但它们通过一个更简单的 API 实现了这些功能。
- WebApplicationBuilder
我们从 WebApplicationBuilder 开始看起:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
WebApplicationBuilder 主要负责以下 4 件事:
使用 builder.Configuration 添加配置。
使用 builder.Services 添加服务。
使用 builder.Logging 配置日志记录。
配置与 IHostBuilder 和 IWebHostBuilder 相关的一般设置。
逐一来看:
WebApplicationBuilder 暴露了 ConfigurationManager 类型,用于添加新的配置源以及访问配置值。
它还直接暴露了一个 IServiceCollection,用于向依赖注入 (DI) 容器添加服务。所以,与通用主机相比,过去你需要这样做:
var hostBuilder = Host.CreateDefaultBuilder(args);
hostBuilder.ConfigureServices(services =>
{
services.AddRazorPages();
services.AddSingleton();
});
而使用 WebApplicationBuilder 时,你可以这样写:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton();
类似地,对于日志记录,以前你需要这样写:
var hostBuilder = Host.CreateDefaultBuilder(args);
hostBuilder.ConfigureLogging(builder =>
{
builder.AddFile();
});
而现在可以这样写:
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddFile();
两者的行为完全相同,唯一的区别就是新的API 更易于使用。WebApplicationBuilder 分别暴露了 Host 和 WebHost 属性来扩展IHostBuilder和IWebHostBuilder,暴露它们。例如:Serilog 的ASP.NET Core 集成会挂钩到IHostBuilder,所以在 ASP.NET Core3.x/5 中,你可以这样添加:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog() // <– 添加这行 .ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
});
在WebApplicationBuilder中,你需要在Host 属性上调用 UseSerilog(),而不是在 builder 本身上:
builder.Host.UseSerilog();
实际上,WebApplicationBuilder 是处理所有配置的地方,除了中间件管道的配置以外。
- WebApplication的解析
在 WebApplicationBuilder 上配置好之后,就可以调用 Build() 来创建 WebApplication 的实例:
var app = builder.Build();
WebApplication 非常有趣,因为它实现了多个不同的接口:
IHost – 用于启动和停止主机。
IApplicationBuilder – 用于构建中间件管道。
IEndpointRouteBuilder – 用于添加端点(Endpoints)。
后面两个点是密切相关的。在ASP.NET Core 3.x 和 5 中,IEndpointRouteBuilder 通过调用 UseEndpoints() 并传递一个 lambda 来添加端点,例如:
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
对于刚接触 ASP.NET Core 的人来说,这种 .NET 3.x/5 的模式有一些复杂性:
中间件管道的构建发生在 Startup 中的 Configure() 函数里。
在调用 app.UseEndpoints() 之前调用 app.UseRouting()(同时还要确保其他中间件放在正确的位置)。
必须使用 lambda 来配置端点(对于熟悉 C# 的用户来说不复杂,但可能会让新手感到困惑)。
WebApplication 显著简化了这一模式:
app.UseStaticFiles();
app.MapRazorPages();
这显然更简单,不过我发现这样处理后中间件和端点之间的区别变得不太清晰,相较于 .NET 5.x 等版本可能会有点令人困惑。这可能只是个人喜好问题,但我认为这模糊了顺序很重要的信息(这个规则适用于中间件,但通常不适用于端点)。
源文地址 https://andrewlock.net/exploring-dotnet-6-part-2-comparing-webapplicationbuilder-to-the-generic-host/
源代码地址 https://github.com/bingbing-gui/AspNetCore-Skill
声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/424739.html