ASP.NET Core 8.0 的新增功能

本文重点介绍 ASP.NET Core 8.0 中最重要的更改,并提供相关文档的链接。

Blazor

全堆栈 Web UI

随着 .NET 8 的发布,Blazor 已成为全堆栈 Web UI 框架,可用于开发在组件或页面级别呈现内容的应用,其中包含:

  • 静态服务器呈现(也称为静态服务器端呈现 [静态 SSR]),用于在服务器上生成静态 HTML。
  • 交互式服务器呈现(也称为交互式服务器端呈现 [交互式 SSR]),用于在服务器上生成具有预呈现的交互式组件。
  • 交互式 WebAssembly 呈现(也称为客户端呈现 [CSR],始终假定为交互式),用于在客户端上生成具有服务器端预呈现的交互式组件。
  • 交互式自动(自动)呈现,最初使用服务器端 ASP.NET Core 运行时进行内容呈现和交互。 下载 Blazor 捆绑包并激活 WebAssembly 运行时后,使用客户端上的 .NET WebAssembly 运行时进行后续呈现和交互。 交互式自动呈现通常提供最快的应用启动体验。

默认情况下,交互式呈现模式还会预呈现内容。

有关详细信息,请参阅以下文章:

Blazor 文档中的示例已更新,可在 Blazor Web 应用中使用。 Blazor Server 示例保留在针对 .NET 7 或更早版本的内容中。

关于使用静态服务器端呈现(静态 SSR)的类库的新文章

我们新增了一篇文章,其中讨论了使用静态服务器端呈现(静态 SSR)的 Razor 类库 (RCL) 中的组件库创作。

有关详细信息,请参阅使用静态服务器端呈现(静态 SSR)的 ASP.NET Core Razor 类库 (RCL)

有关 HTTP 缓存问题的新文章

我们添加了一篇新文章,讨论在跨主要版本升级 Blazor 应用时可能发生的一些常见 HTTP 缓存问题,以及如何解决 HTTP 缓存问题。

有关详细信息,请参阅在升级 ASP.NET Core Blazor 应用时避免 HTTP 缓存问题

新的 Blazor Web 应用模板

我们引入了一个新的 Blazor 项目模板:Blazor Web 应用 模板。 新模板提供了使用 Blazor 组件生成任何样式的 Web UI 的单个起点。 该模板将现有的 Blazor Server 和 Blazor WebAssembly 托管模型的优势与 .NET 8 中添加的新功能 Blazor 相结合:静态服务器端呈现(静态 SSR)、流呈现、增强的导航和表单处理,以及使用 Blazor Server 或 Blazor WebAssembly 按组件添加交互性的功能。

作为将各种 Blazor 托管模型统一到 .NET 8 中单个模型的一部分,我们还合并了 Blazor 项目模板的数量。 我们移除了 Blazor Server 模板,并从 Blazor WebAssembly 模板中移除了 ASP.NET Core 托管选项。 这两种方案都由使用 Blazor Web 应用模板时的选项表示。

注意

.NET 8 仍然支持现有的 Blazor Server 和 Blazor WebAssembly 应用。 可以选择更新这些应用,以使用新的全堆栈 Web UI Blazor 功能。

有关新的 Blazor Web 应用模板的详细信息,请参阅以下文章:

适用于 Blazor Web 应用的新 JS 初始值设定项

对于 Blazor Server、Blazor WebAssembly 和 Blazor Hybrid 应用:

  • beforeStart 用于自定义加载过程、日志记录级别和其他选项等任务。
  • afterStarted 用于注册 Blazor 事件侦听器和自定义事件类型等任务。

在 Blazor Web 应用中,默认情况下不会调用上述旧 JS 初始值设定项。 对于 Blazor Web 应用,将使用一组新的 JS 初始值设定项:beforeWebStartafterWebStartedbeforeServerStartafterServerStartedbeforeWebAssemblyStartafterWebAssemblyStarted

有关详细信息,请参阅 ASP.NET Core Blazor 启动

预呈现和集成指导的拆分

对于 .NET 的早期版本,我们在一篇文章中介绍了预呈现和集成。 为了简化和集中我们的介绍,我们已将主题拆分为以下新文章,这些文章已针对 .NET 8 进行了更新:

在 Blazor Web 应用中保留组件状态

可以使用现有 PersistentComponentState 服务在 Blazor Web 应用中保留和读取组件状态。 这对预呈现期间保留组件状态非常有用。

Blazor Web 应用会自动保留预呈现期间创建的任何已注册应用级状态,从而无需持久化组件状态帮助程序

表单处理和模型绑定

现在,Blazor 组件可以处理提交的表单请求,包括模型绑定和验证请求数据。 组件可以使用标准 HTML <form> 标记或使用现有的 EditForm 组件通过单独的表单处理程序实现窗体。

表单模型绑定 Blazor 遵循数据协定属性(例如 [DataMember][IgnoreDataMember]),用于自定义表单数据绑定到模型的方式。

.NET 8 中包括新的防伪支持。 新的 AntiforgeryToken 组件可将防伪标记呈现为隐藏字段,而新的 [RequireAntiforgeryToken] 属性可启用防伪保护。 如果防伪检查失败,将会返回 400(错误的请求)响应,而无需进行表单处理。 默认情况下,基于 Editform 的表单会启用新的防伪功能,并且可以将其手动应用于标准 HTML 表单。

有关详细信息,请参阅 ASP.NET Core Blazor 表单概述

增强的导航和表单处理

每当用户导航到新页面或提交表单时,静态服务器端呈现(静态 SSR)通常都会执行整页刷新。 在 .NET 8 中,Blazor 可以通过截获请求并改为执行提取请求来增强页面导航和表单处理。 然后,Blazor 会通过将呈现的响应内容修补到浏览器 DOM 中来处理它。 增强的导航和表单处理可避免需要全页刷新并保留更多页面状态,因此页面加载速度更快、更流畅。 默认情况下,当加载 Blazor 脚本(blazor.web.js)时,会启用增强导航。 可以选择为特定表单启用增强的表单处理。

新的增强型导航 API 可以让你通过调用 NavigationManager.Refresh(bool forceLoad = false) 来刷新当前页。

有关详细信息,请参阅 Blazor路由文章的以下部分:

有关静态呈现以及 JS 互操作增强导航的新文章

某些应用依赖于 JS 互操作来执行特定于每个页面的初始化任务。 当将 Blazor 的增强导航功能与执行 JS 互操作初始化任务的静态呈现页面一起使用时,每次发生增强的页面导航时,可能无法按预期再次执行特定于页面的 JS。 新文章介绍如何在 Blazor Web 应用中解决这种情况:

使用静态服务器端呈现(静态 SSR)的 ASP.NET Core Blazor JavaScript

流式渲染

现在,在将静态服务器端呈现(静态 SSR)与 Blazor 结合使用时,可以在响应流中流式传输内容更新。 流式呈现可以改善执行长期运行异步任务的页面的用户体验,以便在内容可用后立即通过呈现内容来完全呈现。

例如,要呈现页面,可能需要进行长期运行的数据库查询或 API 调用。 通常,作为呈现页面其中一部分来执行的异步任务必须在发送已呈现的响应之前完成,这会导致页面加载延迟。 流式呈现最初会呈现包含占位符内容的整个页面,同时执行异步操作。 异步操作完成后,更新后的内容将在同一响应连接上发送到客户端,并修补到 DOM 中。 此方法的优点是,应用的主要布局将会尽快呈现,并在内容就绪后立即更新页面。

有关详细信息,请参阅 ASP.NET Core Razor 组件呈现

将键控服务注入组件

Blazor 现在支持使用 [Inject] 属性注入键控服务。 密钥允许在使用依赖项注入时界定服务的注册和使用范围。 使用新 InjectAttribute.Key 属性指定服务要注入的密钥:

[Inject(Key = "my-service")]
public IMyService MyService { get; set; }

@injectRazor 指令不支持此版本的键控服务,但 Update @inject 会跟踪工作,以便在未来的 .NET 版本中支持键控服务 (dotnet/razor #9286)

有关详细信息,请参阅 ASP.NET Core Blazor 依赖项注入

以级联参数形式访问 HttpContext

现在可以从静态服务器组件以级联参数形式访问当前 HttpContext

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

从静态服务器组件访问 HttpContext 有助于检查和修改标题或其他属性。

有关将 HttpContext 状态、访问令牌和刷新令牌传递给组件的示例,请参阅服务器端 ASP.NET Core Blazor 其他安全方案

在 ASP.NET Core 之外呈现 Razor 组件

现在,可以在 HTTP 请求的上下文之外呈现 Razor 组件。 可以独立于 ASP.NET Core 托管环境,将 Razor 组件作为 HTML 直接呈现为字符串或数据流。 对于要生成 HTML 片段(例如生成电子邮件或静态网站内容)的方案,这会非常方便。

有关详细信息,请参阅在 ASP.NET Core 之外呈现 Razor 组件

节支持

Blazor 中的新 SectionOutletSectionContent 组件添加了对于为可在稍后填充的内容指定出口的支持。 节通常用于在布局中定义随后由特定页面填充的占位符。 节由唯一名称或使用唯一对象 ID 引用。

有关详细信息,请参阅 ASP.NET Core Blazor 部分

错误页支持

Blazor Web 应用可定义用于 ASP.NET 核心异常处理中间件的自定义错误页。 Blazor Web 应用项目模板包括默认错误页(Components/Pages/Error.razor),其内容与 MVC 和 Razor Pages 应用中使用的内容相似。 在响应异常处理中间件的请求时呈现错误页时,即使启用交互性,错误页也始终呈现为静态服务器组件。

8.0 引用源中的 Error.razor

QuickGrid

Blazor QuickGrid 组件不再处于试验阶段,并且现在已是 .NET 8 中框架 Blazor 的一部分。

QuickGrid 是一个高性能网格组件,可用于以表格形式显示数据。 QuickGrid 旨在提供一种简单便捷的方式来显示数据,同时仍提供强大的功能,例如排序、筛选、分页和虚拟化。

有关详细信息,请参阅 ASP.NET Core Blazor QuickGrid 组件

路由到命名元素

现在,Blazor 支持使用客户端路由,以通过标准 URL 片段导航到页面上的特定 HTML 元素。 如果使用标准 id 属性指定 HTML 元素的标识符,则当 URL 片段与元素标识符匹配时,Blazor 会正确滚动到该元素。

有关详细信息,请参阅 ASP.NET Core Blazor 路由和导航

根级别级联值

可以为整个组件层次结构注册根级别级联值。 支持用于更新通知的命名级联值和订阅。

有关详细信息,请参阅 ASP.NET Core Blazor 级联值和参数

虚拟化空内容

Virtualize 组件已加载,并且 Items 为空或 ItemsProviderResult<T>.TotalItemCount 为零时,使用该组件上的 EmptyContent 参数提供内容。

有关详细信息,请参阅 ASP.NET Core Razor 组件虚拟化

当没有剩余的交互式服务器组件时,关闭线路

交互式服务器组件使用与浏览器的实时连接(称为线路)来处理 Web UI 事件。 呈现根交互式服务器组件时,将设置线路及其关联状态。 当页面上没有剩余的交互式服务器组件时,线路将关闭,以释放服务器资源。

监视 SignalR 线路活动

现在,可以使用 CircuitHandler 上的 CreateInboundActivityHandler 方法监视服务器端应用中的入站线路活动。 入站线路活动是从浏览器发送到服务器的任何活动,例如 UI 事件或 JavaScript 到 .NET 的互操作调用。

有关详细信息,请参阅 ASP.NET Core BlazorSignalR 指南

使用 Jiterpreter 提高运行时性能

Jiterpreter 是 .NET 8 中的一项新运行时功能,可以在 WebAssembly 上运行时启用部分实时 (JIT) 编译支持,以提高运行时性能。

有关详细信息,请参阅托管和部署 ASP.NET Core Blazor WebAssembly

预先 (AOT) SIMD 和异常处理

现在,Blazor WebAssembly 预先 (AOT) 编译默认使用 WebAssembly 固定宽度 SIMDWebAssembly 异常处理来提高运行时性能。

有关详细信息,请参阅以下文章:

Web 友好 Webcil 打包

Webcil 是 .NET 程序集的 Web 友好打包,可移除特定于本机 Windows 执行的内容,以避免部署到阻止下载或使用 .dll 文件的环境时出现问题。 默认情况下,Blazor WebAssembly 应用会启用 Webcil。

有关详细信息,请参阅托管和部署 ASP.NET Core Blazor WebAssembly

注意

在 .NET 8 版本之前,“ASP.NET Core 托管的 Blazor WebAssembly 应用的部署布局”中的指导通过多部分绑定方法解决了阻止客户端下载和执行 DLL 的环境问题。 在 .NET 8 或更高版本中,Blazor 使用 Webcil 文件格式解决此问题。 .NET 8 或更高版本中的 Blazor 应用不支持使用 WebAssembly 部署布局文章中所述的试验 NuGet 包进行多部分捆绑。 有关详细信息,请参阅增强 Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle 包以定义自定义捆绑包格式 (dotnet/aspnetcore #36978)。 如果想要继续在 .NET 8 或更高版本应用中使用多部分捆绑包,则可以按照本文中的指导创建自己的多部分捆绑 NuGet 包,但 Microsoft 不支持该包。

Blazor WebAssembly 调试改进

在 WebAssembly 上调试 .NET 时,调试器现在将从 Visual Studio 首选项中配置的符号位置下载符号数据。 这改进了使用 NuGet 包的应用的调试体验。

现在,可以使用 Firefox 调试 Blazor WebAssembly 应用。 调试 Blazor WebAssembly 应用需要配置浏览器以进行远程调试,然后通过 .NET WebAssembly 调试代理使用浏览器开发人员工具连接到浏览器。 目前不支持从 Visual Studio 调试 Firefox。

有关详细信息,请参阅调试 ASP.NET Core Blazor 应用

内容安全策略 (CSP) 兼容性

zai 指定内容安全策略 (CSP) 时,Blazor WebAssembly 不再需要启用 unsafe-eval 脚本源。

有关详细信息,请参阅为 ASP.NET Core Blazor 强制实施内容安全策略

在 Razor 组件的生命周期外处理捕获的异常

在 Razor 组件中使用 ComponentBase.DispatchExceptionAsync 来处理在组件的生命周期调用堆栈外部引发的异常。 这允许组件的代码将异常视为生命周期方法异常。 此后,Blazor 的错误处理机制(如错误边界)可以处理异常。

有关详细信息,请参阅处理 ASP.NET Core Blazor 应用中的错误

配置 .NET WebAssembly 运行时

现在可以为 .NET WebAssembly 运行时配置 Blazor 启动。

有关详细信息,请参阅 ASP.NET Core Blazor 启动

配置 HubConnectionBuilder 中的连接超时

配置中心连接超时的先前解决方法可以替换为正式的 SignalR 中心连接生成器超时配置。

有关详细信息,请参阅以下部分:

项目模板库 Open Iconic

Blazor 项目模板不再依赖于图标的 Open Iconic

支持对话框取消和关闭事件

Blazor 现在支持 dialog HTML 元素上的 cancelclose 事件。

如下示例中:

  • 使用“关闭”按钮关闭 my-dialog 对话框时,会调用 OnClose
  • 使用 Esc 键取消对话框时会调用 OnCancel。 使用 Esc 键消除 HTML 对话框时,将触发 cancelclose 事件。
<div>
    <p>Output: @message</p>

    <button onclick="document.getElementById('my-dialog').showModal()">
        Show modal dialog
    </button>

    <dialog id="my-dialog" @onclose="OnClose" @oncancel="OnCancel">
        <p>Hi there!</p>

        <form method="dialog">
            <button>Close</button>
        </form>
    </dialog>
</div>

@code {
    private string? message;

    private void OnClose(EventArgs e) => message += "onclose, ";

    private void OnCancel(EventArgs e) => message += "oncancel, ";
}

BlazorIdentity UI

选择个人帐户的身份验证选项时,Blazor 支持生成基于 Blazor 的完整 Identity UI。 可以从 Visual Studio 中在 Blazor Web 应用的“新建项目”对话框中选择“个人帐户”选项,或者在创建新项目时,从命令行将 -au|--auth 选项集传递给 Individual

有关详细信息,请参阅以下资源:

使用 ASP.NET Core Identity 保护 Blazor WebAssembly

Blazor 文档包含一篇新文章和示例应用,介绍如何使用 ASP.NET Core Identity 保护独立 Blazor WebAssembly 应用。

有关更多信息,请参见以下资源:

使用 Yarp 路由的 Blazor Server

包含 Yarp 的 Blazor Server 的路由和深层链接在 .NET 8 中正常工作。

有关详细信息,请参阅从 ASP.NET Core 7.0 迁移到 8.0

Blazor Hybrid

以下文章记录了 .NET 8 中 Blazor Hybrid 的更改:

从查询字符串提供时不再需要 [Parameter] 属性

从查询字符串提供参数时,不再需要 [Parameter] 属性:

- [Parameter]
  [SupplyParameterFromQuery]

SignalR

设置服务器超时和保持活动间隔的新方法

ServerTimeout(默认值:30 秒)和 KeepAliveInterval(默认值:15 秒)可以直接在 HubConnectionBuilder 上设置。

适用于 JavaScript 客户端的先前方法

下面的示例演示了 ASP.NET Core 7.0 或更早版本中默认值的两倍值的分配:

var connection = new signalR.HubConnectionBuilder()
  .withUrl("/chatHub")
  .build();

connection.serverTimeoutInMilliseconds = 60000;
connection.keepAliveIntervalInMilliseconds = 30000;

适用于 JavaScript 客户端的新方法

下面的示例演示了分配 ASP.NET Core 8.0 或更高版本中默认值的两倍值的新方法

var connection = new signalR.HubConnectionBuilder()
  .withUrl("/chatHub")
  .withServerTimeoutInMilliseconds(60000)
  .withKeepAliveIntervalInMilliseconds(30000)
  .build();

Blazor Server 应用的 JavaScript 客户端的先前方法

下面的示例演示了 ASP.NET Core 7.0 或更早版本中默认值的两倍值的分配:

Blazor.start({
  configureSignalR: function (builder) {
    let c = builder.build();
    c.serverTimeoutInMilliseconds = 60000;
    c.keepAliveIntervalInMilliseconds = 30000;
    builder.build = () => {
      return c;
    };
  }
});

服务器端 Blazor 应用的 JavaScript 客户端的新方法

以下示例显示了为 Blazor Web 应用和 Blazor Server 分配两倍于 ASP.NET Core 8.0 或更高版本中默认值的值的新方法

Blazor Web 应用:

Blazor.start({
  circuit: {
    configureSignalR: function (builder) {
      builder.withServerTimeout(60000).withKeepAliveInterval(30000);
    }
  }
});

Blazor Server:

Blazor.start({
  configureSignalR: function (builder) {
    builder.withServerTimeout(60000).withKeepAliveInterval(30000);
  }
});

.NET 客户端的先前方法

下面的示例演示了 ASP.NET Core 7.0 或更早版本中默认值的两倍值的分配:

var builder = new HubConnectionBuilder()
    .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
    .Build();

builder.ServerTimeout = TimeSpan.FromSeconds(60);
builder.KeepAliveInterval = TimeSpan.FromSeconds(30);

builder.On<string, string>("ReceiveMessage", (user, message) => ...

await builder.StartAsync();

.NET 客户端的新方法

下面的示例演示了分配 ASP.NET Core 8.0 或更高版本中默认值的两倍值的新方法

var builder = new HubConnectionBuilder()
    .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
    .WithServerTimeout(TimeSpan.FromSeconds(60))
    .WithKeepAliveInterval(TimeSpan.FromSeconds(30))
    .Build();

builder.On<string, string>("ReceiveMessage", (user, message) => ...

await builder.StartAsync();

SignalR 有状态重新连接

SignalR 有状态重新连接可减少客户端遇到暂时断开网络连接(例如,切换网络连接时或短暂的暂时失去访问权限时)情况时感知到的停机时间。

有状态重新连接可通过以下方式实现此目的:

  • 暂时缓冲服务器和客户端上的数据。
  • 确认服务器和客户端(确认)收到的消息。
  • 识别连接何时返回并重播在连接关闭时可能已发送的消息。

ASP.NET Core 8.0 及更高版本支持监控状态的重新连接。

在服务器中心终结点和客户端处选择加入监控状态的重新连接:

  • 更新服务器中心终结点配置以启用 AllowStatefulReconnects 选项:

    app.MapHub<MyHub>("/hubName", options =>
    {
        options.AllowStatefulReconnects = true;
    });
    

    (可选)可以全局设置服务器允许的最大缓冲区大小(以字节为单位),也可以使用 StatefulReconnectBufferSize 选项针对特定的中心进行设置:

    全局设置的 StatefulReconnectBufferSize 选项:

    builder.AddSignalR(o => o.StatefulReconnectBufferSize = 1000);
    

    针对特定中心进行设置的 StatefulReconnectBufferSize 选项:

    builder.AddSignalR().AddHubOptions<MyHub>(o => o.StatefulReconnectBufferSize = 1000);
    

    StatefulReconnectBufferSize 选项是可选的,默认值为 100,000 字节。

  • 更新 JavaScript 或 TypeScript 客户端代码,以启用 withStatefulReconnect 选项:

    const builder = new signalR.HubConnectionBuilder()
      .withUrl("/hubname")
      .withStatefulReconnect({ bufferSize: 1000 });  // Optional, defaults to 100,000
    const connection = builder.build();
    

    bufferSize 选项是可选的,默认值为 100,000 字节。

  • 更新 .NET 客户端代码以启用 WithStatefulReconnect 选项:

      var builder = new HubConnectionBuilder()
          .WithUrl("<hub url>")
          .WithStatefulReconnect();
      builder.Services.Configure<HubConnectionOptions>(o => o.StatefulReconnectBufferSize = 1000);
      var hubConnection = builder.Build();
    

    StatefulReconnectBufferSize 选项是可选的,默认值为 100,000 字节。

有关详细信息,请参阅配置有状态重新连接

最小 API

本部分介绍最小 API 的新功能。 另请参阅本机 AOT 部分,详细了解最小 API。

用户替代区域性

从 ASP.NET Core 8.0 开始,应用程序可通过 RequestLocalizationOptions.CultureInfoUseUserOverride 属性来确定是否对 CultureInfoDateTimeFormatNumberFormat 属性使用非默认的 Windows 设置。 这对 Linux 没有影响。 这直接对应于 UseUserOverride

    app.UseRequestLocalization(options =>
    {
        options.CultureInfoUseUserOverride = false;
    });

绑定到表单

现在支持使用 [FromForm] 属性显式绑定到表单值。 通过 [FromForm] 绑定到请求的参数包括防伪令牌。 处理请求时会验证防伪令牌。

还支持使用 IFormCollectionIFormFileIFormFileCollection 类型推断绑定到表单。 OpenAPI 元数据针对表单参数进行推断,以支持与 Swagger UI 集成。

有关详细信息,请参阅:

现在针对以下内容支持从表单绑定:

  • 集合,例如列表字典
  • 复杂类型,例如 TodoProject

有关详细信息,请参阅从表单绑定到集合和复杂类型

使用最少 API 进行防伪造

此版本添加了用于验证反伪令牌的中间件,用于缓解跨站点请求伪造攻击。 调用 AddAntiforgery 以在 DI 中注册防伪服务。 在 DI 容器中注册防伪服务时,WebApplicationBuilder 会自动添加防伪中间件。 防伪令牌用于减少跨站点请求伪造攻击

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

app.MapGet("/", () => "Hello World!");

app.Run();

防伪中间件:

仅当出现以下情况时,才会验证防伪令牌:

  • 终结点包含实现 IAntiforgeryMetadata 的元数据,其中 RequiresValidation=true
  • 与终结点关联的 HTTP 方法是一个相关的 HTTP 方法。 除了 TRACE、OPTIONS、HEAD 和 GET 之外,相关方法都是 HTTP 方法
  • 请求与某个有效的终结点关联。

有关详细信息,请参阅用最小 API 实现防伪

ObjectPool 中新建 IResettable 接口

Microsoft.Extensions.ObjectPool 为内存中的池对象实例提供支持。 如果值分配或初始化成本较高,则应用可以使用对象池。

在此版本中,我们通过添加 IResettable 接口,使对象池更易于使用。 可重用类型通常需要在使用之间重置回默认状态。 返回到对象池时,IResettable 类型将自动重置。

有关详细信息,请参阅 ObjectPool 示例

本机 AOT

添加了对 .NET 本机预先 (AOT) 的支持。 使用 AOT 发布的应用的性能要好得多:更小的应用大小、更少的内存使用量和更快的启动时间。 gRPC、最小 API 和工作器服务应用目前支持本机 AOT。 有关详细信息,请参阅ASP.NET Core 对本机 AOT 的支持教程:使用本机 AOT 发布 ASP.NET Core 应用。 如需了解 ASP.NET Core 与本机 AOT 的兼容性的已知问题,请参阅 GitHub 问题dotnet/core #8288

库和本机 AOT

目前,在 ASP.NET Core 项目中使用的许多常用库在针对本机 AOT 的项目中使用时存在一些兼容性问题,例如:

  • 使用反射来检查和发现类型。
  • 在运行时有条件地加载库。
  • 动态生成代码以实现功能。

需要更新使用这些动态功能的库,才能与本机 AOT 配合使用。 可以使用 Roslyn 源生成器等工具更新它们。

建议希望支持本机 AOT 的库作者执行以下操作:

新建项目模板

新的ASP.NET Core Web API(本机 AOT)项目模板(简称为 webapiaot)会创建启用了 AOT 发布的项目。 有关详细信息,请参阅Web API(本机 AOT)模板

新的 CreateSlimBuilder 方法

Web API(原生 AOT)模板中使用的CreateSlimBuilder()方法通过运行应用所需的最少 ASP.NET Core 功能初始化 WebApplicationBuilderCreateSlimBuilder 方法包含高效开发体验通常需要的以下功能:

  • appsettings.jsonappsettings.{EnvironmentName}.json 的 JSON 文件配置。
  • 用户机密配置。
  • 控制台日志记录。
  • 日志记录配置。

有关详细信息,请参阅 CreateSlimBuilder 方法

新的 CreateEmptyBuilder 方法

还有另一种新的 WebApplicationBuilder 工厂方法(用于构建仅包含必要功能的小型应用):WebApplication.CreateEmptyBuilder(WebApplicationOptions options)。 创建此 WebApplicationBuilder 时没有内置行为。 它生成的应用仅包含显式配置的服务和中间件。

下面是一个使用此 API 创建小型 Web 应用程序的示例:

var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions());
builder.WebHost.UseKestrelCore();

var app = builder.Build();

app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Hello, World!");
    await next(context);
});

Console.WriteLine("Running...");
app.Run();

在 linux-x64 计算机上使用 .NET 8 Preview 7 通过原生 AOT 发布此代码会生成约 8.5 MB 的独立、原生可执行文件。

通过可配置的 HTTPS 支持减小了应用大小

我们进一步减少了不需要 HTTPS 或 HTTP/3 支持的应用的本机 AOT 二进制文件大小。 对于在 TLS 终止代理(例如托管在 Azure 上的代理)后面运行的应用,通常不需要使用 HTTPS 或 HTTP/3。 新的 WebApplication.CreateSlimBuilder 方法默认省略了此功能。 可通过调用 builder.WebHost.UseKestrelHttpsConfiguration()(对于 HTTPS)或 builder.WebHost.UseQuic()(对于 HTTP/3)来添加它。 有关详细信息,请参阅 CreateSlimBuilder 方法

编译器生成的 IAsyncEnumerable<T> 类型的 JSON 序列化

System.Text.Json添加了新功能,以更好地支持本机 AOT。 这些新功能为 System.Text.Json 的源生成模式添加了功能,因为 AOT 不支持反射。

其中一项新功能是支持对 C# 编译器实现的 IAsyncEnumerable<T> 实现进行 JSON 序列化。 此支持在配置为发布本机 AOT 的 ASP.NET Core 项目中开放了它们的使用。

在路由处理程序使用 yield return 异步返回枚举的情况下,此 API 非常有用。 例如,具体化数据库查询的行。 有关详细信息,请参阅 .NET 8 预览版 4 公告中的无法形容的类型支持

有关 System.Text.Json 源生成中其他改进的信息,请参阅 .NET 8 中的序列化改进

为剪裁警告批注的顶级 API

现已批注无法可靠地使用本机 AOT 的子系统的主要入口点。 从启用了本机 AOT 的应用程序调用这些方法时,会提供警告。 例如,以下代码在调用AddControllers时生成警告,因为此 API 在剪裁方面不安全,且不受本机 AOT 支持。

Visual Studio 窗口,其中显示了 AddControllers 方法上的 IL2026 警告消息,该信息指示 MVC 当前不支持本机 AOT。

请求委托生成器

为了使最小 API 与本机 AOT 兼容,我们将引入请求委托生成器 (RDG)。 RDG 是一个源生成器,发挥 RequestDelegateFactory (RDF) 的作用。 也就是说,它将各种 MapGet()MapPost() 以及这类调用转换为与指定路由关联的 RequestDelegate 实例。 但 RDG 不会在启动时在应用程序的内存中执行此操作,而是会在编译时执行此操作,并将 C# 代码直接生成到项目中。 RDG:

  • 去除了在运行时生成此代码的操作。
  • 确保 API 中使用的类型可由本机 AOT 工具链静态分析。
  • 确保必要的代码不会被剪裁掉。

我们正在设法确保 RDG 支持尽可能多的最小 API 功能,以便能与本机 AOT 兼容。

当启用了“使用本机 AOT 进行发布”时,会自动在项目中启用 RDG。 即使不使用本机 AOT,也可以通过在项目文件中设置<EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator>来手动启用 RDG。 这在最初评估项目对本机 AOT 的就绪情况或需要缩短应用的启动时间时会很有用。

使用拦截器提高性能

请求委托生成器使用新的 C# 12 拦截器编译器功能,支持在运行时使用静态生成的变体拦截对最小 API Map 方法的调用。 使用拦截器可以提高使用 PublishAot 编译的应用的启动性能。

编译时生成的最小 API 中的日志记录和异常处理

运行时生成的最小 API 支持在参数绑定失败时自动进行记录(或在开发环境中引发异常)。 .NET 8 引入了对通过请求委托生成器 (RDG) 在编译时生成的 API 的相同支持。 有关详细信息,请参阅编译时生成的最小 API 中的日志记录和异常处理

AOT 和 System.Text.Json

最小 API 经过优化,可更好地使用JS接收和返回 System.Text.JsonON 有效负载,因此也适用JSON 和本机 AOT 的兼容性要求。 本机 AOT 兼容性需要使用 System.Text.Json 源生成器。 在最小 API 中作为请求委托的参数或从请求委托返回的参数而被接受的所有类型都必须在通过 ASP.NET Core 的依赖项注入注册的 JsonSerializerContext 上进行配置,例如:

// Register the JSON serializer context with DI
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

...

// Add types used in the minimal API app to source generated JSON serializer content
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}

有关 TypeInfoResolverChain API 的详细信息,请参阅下列资源:

库和本机 AOT

目前许多可用于 ASP.NET Core 项目的通用库如果在面向本机 AOT 的项目中使用,会有一些兼容性问题。 常用库通常依赖于 .NET 反射的动态功能来检查和发现类型,会在运行时有条件地加载库,并动态生成代码以实现其功能。 这些库需要进行更新,以便使用Roslyn 源生成器等工具处理本机 AOT。

对于希望详细了解如何让库为本机 AOT 做准备的库作者,建议首先让库做好剪裁的准备,并详细了解本机 AOT 兼容性要求

Kestrel 和 HTTP.sys 服务器

Kestrel 和 HTTP.sys 有几个新功能。

对 Kestrel 中命名管道的支持

命名管道是一种用于生成 Windows 应用间的进程间通信 (IPC) 的常用技术。 现在可以使用 .NET、Kestrel 和命名管道生成 IPC 服务器。

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ListenNamedPipe("MyPipeName");
});

有关此功能以及如何使用 .NET 和 gRPC 创建 IPC 服务器和客户端的详细信息,请参阅与 gRPC 的进程间通信

命名管道传输的性能改进

我们改进了命名管道的连接性能。 Kestrel 的命名管道传输现在接受并行连接,并会重复使用 NamedPipeServerStream 实例。

创建 100,000 个连接的时间:

  • 之前:5.916 秒
  • 之后:2.374 秒

Kestrel 中 macOS 上的 HTTP/2 over TLS (HTTPS) 支持

.NET 8 向 macOS 添加了对应用程序层协议协商 (ALPN) 的支持。 ALPN 是一项 TLS 功能,用于协商连接将使用的 HTTP 协议。 例如,ALPN 允许浏览器和其他 HTTP 客户端请求 HTTP/2 连接。 此功能对于需要 HTTP/2 的 gRPC 应用特别有用。 有关详细信息,请参阅对 ASP.NET Core Kestrel Web 服务器使用 HTTP/2

在 Kestrel 中进行证书文件监视

现在,将 reloadOnChange 传递到 KestrelServerOptions.Configure() 时,会监视按路径配置的 TLS 证书是否发生更改。 对证书文件更改的处理方式与对配置路径更改的处理方式相同(即重新加载终结点)。

请注意,文件删除不会被特别跟踪,因为它们是暂时发生的;如果不是暂时发生的,则会导致服务器崩溃。

不使用指定的 HTTP 协议时发出警告

如果 TLS 已被禁用而 HTTP/1.x 可用,则 HTTP/2 和 HTTP/3 都将被禁用,即使已指定这二者。 这可能会导致发生一些不好的意外情况,因此我们添加了警告输出,以便在发生这种情况时通知你。

HTTP_PORTSHTTPS_PORTS 配置密钥

应用程序和容器通常只能侦听一个端口(比如端口 80),且没有其他限制(比如主机或路径)。 HTTP_PORTSHTTPS_PORTS 是允许为 Kestrel 和 HTTP.sys 服务器指定侦听端口的新配置密钥。 可以使用 DOTNET_ASPNETCORE_ 环境变量前缀定义这些密钥,也可以直接通过任何其他配置输入(例如 appsettings.json)指定这些密钥。 每个密钥都是一个以分号分隔的端口值列表。 例如:

ASPNETCORE_HTTP_PORTS=80;8080
ASPNETCORE_HTTPS_PORTS=443;8081

这是以下项的简写,它指定了方案(HTTP 或 HTTPS)和任何主机或 IP:

ASPNETCORE_URLS=http://*:80/;http://*:8080/;https://*:443/;https://*:8081/

有关详细信息,请参阅为 ASP.NET Core Kestrel Web 服务器配置终结点在 ASP.NET Core 中的 HTTP.sys Web 服务器实现

ITlsHandshakeFeature 中的 SNI 主机名

现在,服务器名称指示器 (SNI) 主机名已在 ITlsHandshakeFeature 接口的HostName 属性中公开。

SNI 是 TLS 握手过程的一部分。 它允许客户端在服务器托管多个虚拟主机或域时指定尝试连接的主机名。 要在握手过程中提供正确的安全证书,服务器需要知道为每个请求选择的主机名。

通常,主机名仅在 TLS 堆栈中处理,并用于选择匹配的证书。 但通过公开它,应用中的其他组件可以将该信息用于诊断、速率限制、路由和计费等目的。

公开主机名对于管理数千个 SNI 绑定的大规模服务很有用。 此功能可以在客户升级期间显著提高调试效率。 通过提高透明度,可以更快地解决问题和提高服务可靠性。

有关详细信息,请参阅 ITlsHandshakeFeature.HostName

IHttpSysRequestTimingFeature

在使用 HTTP.sys 服务器IIS 进程内托管时,IHttpSysRequestTimingFeature 用于为请求提供详细的时间信息:

IHttpSysRequestTimingFeature.TryGetTimestamp 可检索提供的时间类型的时间戳:

using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.HttpSys;
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseHttpSys();

var app = builder.Build();

app.Use((context, next) =>
{
    var feature = context.Features.GetRequiredFeature<IHttpSysRequestTimingFeature>();

    var loggerFactory = context.RequestServices.GetRequiredService<ILoggerFactory>();
    var logger = loggerFactory.CreateLogger("Sample");

    var timingType = HttpSysRequestTimingType.RequestRoutingEnd;

    if (feature.TryGetTimestamp(timingType, out var timestamp))
    {
        logger.LogInformation("Timestamp {timingType}: {timestamp}",
                                          timingType, timestamp);
    }
    else
    {
        logger.LogInformation("Timestamp {timingType}: not available for the "
                                           + "current request",    timingType);
    }

    return next(context);
});

app.MapGet("/", () => Results.Ok());

app.Run();

有关详细信息,请参阅使用 IHttpSysRequestTimingFeature 获取详细的时间信息以及 IIS 进程内托管

HTTP.sys:选择加入对内核模式响应缓冲的支持

在某些场景下,高延迟的大量小型写入可能会对 HTTP.sys 造成重大的性能影响。 这种影响是由于 HTTP.sys 实现中缺少 Pipe 缓冲区导致的。 为了在这些方案中提高性能,已向 HTTP.sys 添加对响应缓冲的支持。 通过将 HttpSysOptions.EnableKernelResponseBuffering 设置为 true 来启用缓冲。

响应缓冲应由执行同步 I/O 或异步 I/O 且一次不超过一个未完成写入的应用来启用。 在这些方案中,响应缓冲可以在高延迟连接上显著提高吞吐量。

使用异步 I/O 且一次可能有多个未完成的写入的应用不应使用此标志。 通过 HTTP.Sys 启用此标志可能会导致 CPU 和内存使用率升高。

身份验证和授权

ASP.NET Core 8 为身份验证和授权添加了新功能。

Identity API 终结点

MapIdentityApi<TUser> 是一种新的扩展方法,用于添加两个 API 终结点(/register/login)。 MapIdentityApi 的主要目的是为了让开发人员能够轻松使用 ASP.NET Core Identity 在基于 JavaScript 的单页应用 (SPA) 或 Blazor 应用中进行身份验证。 MapIdentityAPI 没有使用 ASP.NET Core Identity 提供的基于 Razor Pages 的默认 UI,而是添加了更适合 SPA 应用和非浏览器应用的 JSON API 终结点。 有关详细信息,请参阅Identity API 终结点

IAuthorizationRequirementData

在 ASP.NET Core 8 之前的版本中,将参数化授权策略添加到终结点需要:

  • 为每个策略实现 AuthorizeAttribute
  • 实现 AuthorizationPolicyProvider 以处理基于字符串的协定中的自定义策略。
  • 为策略实现 AuthorizationRequirement
  • 为每个要求实现 AuthorizationHandler

例如,来看看为 ASP.NET Core 7.0 编写的以下示例:

using AuthRequirementsData.Authorization;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
builder.Services.AddControllers();
builder.Services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>();

var app = builder.Build();

app.MapControllers();

app.Run();
using Microsoft.AspNetCore.Mvc;

namespace AuthRequirementsData.Controllers;

[ApiController]
[Route("api/[controller]")]
public class GreetingsController : Controller
{
    [MinimumAgeAuthorize(16)]
    [HttpGet("hello")]
    public string Hello() => $"Hello {(HttpContext.User.Identity?.Name ?? "world")}!";
}
using Microsoft.AspNetCore.Authorization;
using System.Globalization;
using System.Security.Claims;

namespace AuthRequirementsData.Authorization;

class MinimumAgeAuthorizationHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    private readonly ILogger<MinimumAgeAuthorizationHandler> _logger;

    public MinimumAgeAuthorizationHandler(ILogger<MinimumAgeAuthorizationHandler> logger)
    {
        _logger = logger;
    }

    // Check whether a given MinimumAgeRequirement is satisfied or not for a particular
    // context.
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                               MinimumAgeRequirement requirement)
    {
        // Log as a warning so that it's very clear in sample output which authorization
        // policies(and requirements/handlers) are in use.
        _logger.LogWarning("Evaluating authorization requirement for age >= {age}",
                                                                    requirement.Age);

        // Check the user's age
        var dateOfBirthClaim = context.User.FindFirst(c => c.Type ==
                                                                 ClaimTypes.DateOfBirth);
        if (dateOfBirthClaim != null)
        {
            // If the user has a date of birth claim, check their age
            var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value, CultureInfo.InvariantCulture);
            var age = DateTime.Now.Year - dateOfBirth.Year;
            if (dateOfBirth > DateTime.Now.AddYears(-age))
            {
                // Adjust age if the user hasn't had a birthday yet this year.
                age--;
            }

            // If the user meets the age criterion, mark the authorization requirement
            // succeeded.
            if (age >= requirement.Age)
            {
                _logger.LogInformation("Minimum age authorization requirement {age} satisfied",
                                         requirement.Age);
                context.Succeed(requirement);
            }
            else
            {
                _logger.LogInformation("Current user's DateOfBirth claim ({dateOfBirth})" +
                    " does not satisfy the minimum age authorization requirement {age}",
                    dateOfBirthClaim.Value,
                    requirement.Age);
            }
        }
        else
        {
            _logger.LogInformation("No DateOfBirth claim present");
        }

        return Task.CompletedTask;
    }
}

完整示例位于 AspNetCore.Docs.Samples 存储库中的此处

ASP.NET Core 8 引入了 IAuthorizationRequirementData 接口。 IAuthorizationRequirementData 接口允许属性定义指定与授权策略关联的要求。 使用 IAuthorizationRequirementData,可以使用更少的代码行编写前面的自定义授权策略代码。 已更新的 Program.cs 文件:

  using AuthRequirementsData.Authorization;
  using Microsoft.AspNetCore.Authorization;
  
  var builder = WebApplication.CreateBuilder();
  
  builder.Services.AddAuthentication().AddJwtBearer();
  builder.Services.AddAuthorization();
  builder.Services.AddControllers();
- builder.Services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
  builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>();
  
  var app = builder.Build();
  
  app.MapControllers();
  
  app.Run();

已更新的 MinimumAgeAuthorizationHandler

using Microsoft.AspNetCore.Authorization;
using System.Globalization;
using System.Security.Claims;

namespace AuthRequirementsData.Authorization;

- class MinimumAgeAuthorizationHandler : AuthorizationHandler<MinimumAgeRequirement>
+ class MinimumAgeAuthorizationHandler : AuthorizationHandler<MinimumAgeAuthorizeAttribute>
{
    private readonly ILogger<MinimumAgeAuthorizationHandler> _logger;

    public MinimumAgeAuthorizationHandler(ILogger<MinimumAgeAuthorizationHandler> logger)
    {
        _logger = logger;
    }

    // Check whether a given MinimumAgeRequirement is satisfied or not for a particular
    // context
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
-                                              MinimumAgeRequirement requirement)
+                                              MinimumAgeAuthorizeAttribute requirement)
    {
        // Remaining code omitted for brevity.

可以在此处找到完整的更新示例。

有关新示例的详细说明,请参阅使用 IAuthorizationRequirementData 自定义授权策略

保护 Swagger UI 终结点

现在,可以通过调用 MapSwagger().RequireAuthorization 在生产环境中保护 Swagger UI 终结点。 有关详细信息,请参阅保护 Swagger UI 终结点

其他

以下各节介绍 ASP.NET Core 8 中的其他新功能。

依赖项注入中的键控服务支持

密钥服务是指使用密钥注册和检索依赖项注入 (DI) 服务的机制。 通过调用 AddKeyedSingleton (或 AddKeyedScopedAddKeyedTransient)来注册服务,与密钥相关联。 使用 [FromKeyedServices] 属性指定密钥来访问已注册的服务。 以下代码演示如何使用键化服务:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
                                                               smallCache.Get("date"));

app.MapControllers();

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
    [HttpGet("big-cache")]
    public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
    {
        return cache.Get("data-mvc");
    }
}

public class MyHub : Hub
{
    public void Method([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

适用于具有 ASP.NET 核心后端的 SPA 应用的 Visual Studio 项目模板

Visual Studio 项目模板现在是推荐用于创建具有 ASP.NET 核心后端的单页应用 (SPA) 的方法。 提供了基于 JavaScript 框架 AngularReactVue 创建应用的模板。 这些模板:

  • 创建一个包含前端项目和后端项目的 Visual Studio 解决方案。
  • 将 Visual Studio 项目类型用于 JavaScript,将 TypeScript (.esproj) 用于前端。
  • 将 ASP.NET Core 项目用于后端。

有关 Visual Studio 模板以及如何访问旧模板的详细信息,请参阅 ASP.NET Core 中的单页应用 (SPA) 概述

对泛型属性的支持

以前需要 Type 参数的属性现在可采用更简洁的泛型变体。 这可以通过支持 C# 11 中的泛型属性来实现。 例如,可以按以下所示修改用于注释操作响应类型的语法:

[ApiController]
[Route("api/[controller]")]
public class TodosController : Controller
{
  [HttpGet("/")]
- [ProducesResponseType(typeof(Todo), StatusCodes.Status200OK)]
+ [ProducesResponseType<Todo>(StatusCodes.Status200OK)]
  public Todo Get() => new Todo(1, "Write a sample", DateTime.Now, false);
}

支持以下属性的泛型变体:

  • [ProducesResponseType<T>]
  • [Produces<T>]
  • [MiddlewareFilter<T>]
  • [ModelBinder<T>]
  • [ModelMetadataType<T>]
  • [ServiceFilter<T>]
  • [TypeFilter<T>]

ASP.NET Core 应用中的代码分析

下表中显示的新分析器在 ASP.NET Core 8.0 中可用。

诊断 ID 中断性或非中断性 说明
ASP0016 非中断性 不从 RequestDelegate 返回值
ASP0019 非中断性 建议使用 IHeaderDictionary.Append 或索引器
ASP0020 非中断性 路由参数引用的复杂类型必须是可分析的
ASP0021 非中断性 BindAsync 方法的返回类型必须为 ValueTask<T>
ASP0022 非中断性 检测到路由处理程序之间的路由冲突
ASP0023 非中断性 MVC:检测到路由处理程序之间的路由冲突
ASP0024 非中断性 路由处理程序具有包含 [FromBody] 属性的多个参数
ASP0025 非中断性 使用 AddAuthorizationBuilder

路由工具

ASP.NET Core 的构建基于路由。 最小 API、Web API、Razor Pages 和 Blazor 都使用路由来自定义 HTTP 请求映射到代码的方式。

在 .NET 8 中,我们引入了一套新功能,可使路由更易于被了解和使用。 这些新功能包括:

有关详细信息,请参阅 .NET 8 中的路由工具

ASP.NET Core 指标

指标是指在一段时间内报告的度量值,通常用于监视应用的运行状况以及生成警报。 例如,报告 HTTP 请求失败的计数器可以显示在仪表板中,或者在失败超过阈值时生成警报。

此预览版使用 System.Diagnostics.Metrics 在整个 ASP.NET Core 中添加了新指标。 Metrics 是用于报告和收集有关应用信息的新式 API。

与现有事件计数器相比,指标提供了许多改进:

  • 包含计数器、仪表和直方图的新度量类型。
  • 包含多维值的功能强大的报告。
  • 通过与 OpenTelemetry 标准保持一致,可集成到更广泛的云原生生态系统中。

ASP.NET Core 托管 Kestrel 和 SignalR 中已添加指标。 有关详细信息,请参阅 System.Diagnostics.Metrics

IExceptionHandler

IExceptionHandler 是一个新接口,可为开发人员提供用于在中心位置处理已知异常的回叫。

IExceptionHandler 实现是通过调用 IServiceCollection.AddExceptionHandler<T> 来注册的。 可以添加多个实现,并按注册顺序调用它们。 如果异常处理程序处理请求,它可以返回 true 来停止处理。 如果异常不是由任何异常处理程序处理,则控件将回退到中间件的默认行为和选项。

有关详细信息,请参阅 IExceptionHandler

改善了调试体验

调试自定义属性已添加到 HttpContextHttpRequestHttpResponseClaimsPrincipalWebApplication 等类型。 针对这些类型的增强型调试程序显示使你可以更轻松地在 IDE 调试程序中查找重要信息。 以下屏幕截图显示了这些属性给调试程序显示的 HttpContext 带来的差异。

.NET 7:

.NET 7 中 HttpContext 类型的无用调试器显示。

.NET 8:

.NET 8 中 HttpContext 类型的有用调试器显示。

WebApplication 的调试器显示突出显示了重要信息,例如配置的终结点、中间件和 IConfiguration 值。

.NET 7:

.NET 7 中 WebApplication 类型的无用调试器显示。

.NET 8:

.NET 8 中 WebApplication 类型的有用调试器显示。

有关 .NET 8 中调试改进的详细信息,请参阅:

IPNetwork.ParseTryParse

IPNetwork 上新的 ParseTryParse 方法增加了对使用 CIDR 表示法或“斜杠表示法”中的输入字符串创建 IPNetwork 的支持。

下面是 IPv4 示例:

// Using Parse
var network = IPNetwork.Parse("192.168.0.1/32");
// Using TryParse
bool success = IPNetwork.TryParse("192.168.0.1/32", out var network);
// Constructor equivalent
var network = new IPNetwork(IPAddress.Parse("192.168.0.1"), 32);

下面是 IPv6 的示例:

// Using Parse
var network = IPNetwork.Parse("2001:db8:3c4d::1/128");
// Using TryParse
bool success = IPNetwork.TryParse("2001:db8:3c4d::1/128", out var network);
// Constructor equivalent
var network = new IPNetwork(IPAddress.Parse("2001:db8:3c4d::1"), 128);

基于 Redis 的输出缓存

ASP.NET Core 8 增加了对将 Redis 用作输出缓存的分布式缓存的支持。 输出缓存是一项功能,使应用能够缓存最小 API 终结点、控制器操作或 Razor Page 的输出。 有关详细信息,请参阅输出缓存

路由后对中间件进行短路

当路由与终结点匹配时,它通常允许中间件管道的其余部分在调用终结点逻辑之前运行。 服务可以通过在管道中提前筛选出已知请求来减少资源使用量。 使用 ShortCircuit 扩展方法使路由立即调用终结点逻辑,然后结束请求。 例如,给定路由可能不需要通过身份验证或 CORS 中间件。 以下示例对与 /short-circuit 路由匹配的请求进行短路:

app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();

使用 MapShortCircuit 方法一次为多个路由设置短路,方法是向其传递 URL 前缀的参数数组。 例如,浏览器和机器人通常会探测服务器中的已知路径,例如 robots.txtfavicon.ico。 如果应用没有这些文件,则一行代码可以配置这两个路由:

app.MapShortCircuit(404, "robots.txt", "favicon.ico");

有关详细信息,请参阅路由后对中间件进行短路

HTTP 日志记录中间件扩展性

HTTP 日志记录中间件具有多项新功能:

  • HttpLoggingFields.Duration:启用后,中间件会在请求和响应结束时发出一个新日志,以计算处理所需的总时间。 此新字段已添加到 HttpLoggingFields.All 集。
  • HttpLoggingOptions.CombineLogs:中间件会将请求和响应的所有已启用日志合并到最后一个日志中。 单个日志消息包括请求、请求正文、响应、响应正文和持续时间。
  • IHttpLoggingInterceptor:可使用 AddHttpLoggingInterceptor 实现和注册的服务的新接口,用于接收每个请求和每个响应回调,以自定义记录的详细信息。 首先应用任何特定于终结点的日志设置,然后可在这些回调中替代它们。 实现可以:
    • 检查请求和响应。
    • 启用或禁用任何 HttpLoggingFields
    • 调整记录的请求或响应正文量。
    • 将自定义字段添加到日志。

有关详细信息,请参阅 .NET Core 和 ASP.NET Core 中的 HTTP 日志记录

ProblemDetails 中的新 API 支持更具弹性的集成

在 .NET 7 中引入了 ProblemDetails 服务,用于改善生成符合 ProblemDetails 规范的错误响应的体验。 在 .NET 8 中增加了一个新的 API,以便更轻松地实现回退行为(如果 IProblemDetailsService 无法生成 ProblemDetails)。 下面的示例阐释了新 TryWriteAsync API 的用法:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp =>
{
    exceptionHandlerApp.Run(async httpContext =>
    {
        var pds = httpContext.RequestServices.GetService<IProblemDetailsService>();
        if (pds == null
            || !await pds.TryWriteAsync(new() { HttpContext = httpContext }))
        {
            // Fallback behavior
            await httpContext.Response.WriteAsync("Fallback: An error occurred.");
        }
    });
});

app.MapGet("/exception", () =>
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

有关详细信息,请参阅 IProblemDetailsService 回退

其他资源