ASP.NET Core 6.0 的新增功能

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

ASP.NET Core MVC 和 Razor 改进

最小 API

构建最小 API,以创建具有最小依赖项的 HTTP API。 它们非常适合于需要在 ASP.NET Core 中仅包括最少文件、功能和依赖项的微服务和应用。 有关详细信息,请参阅:

SignalR

用于 SignalR 连接的长时间运行的活动标记

SignalR 使用新的 Microsoft.AspNetCore.Http.Features.IHttpActivityFeature.Activityhttp.long_running 标记添加到请求活动。 IHttpActivityFeature.ActivityAPM 服务(如 Azure Monitor Application Insights)用来筛选创建长时间运行请求警报的 SignalR 请求。

SignalR 性能改进

  • 每个连接(而不是每个中心方法调用)分配 HubCallerClients 一次。
  • 避免 SignalRDefaultHubDispatcher.Invoke 中的闭包分配。 状态通过参数传递给本地静态函数,以避免闭包分配。 有关详细信息,请参阅此 GitHub 拉取请求
  • 在服务器到客户端流式处理中,每个流分配一个 StreamItemMessage,而不是每个流项分配一个。 有关详细信息,请参阅此 GitHub 拉取请求

Razor 编译器

Razor 编译器已更新为使用源生成器

编译器 Razor 现基于 Razor。 源生成器在编译期间运行,并检查正在编译的内容,以生成与项目的其余部分一起编译的其他文件。 使用源生成器可简化 Razor 编译器并显著加快生成时间。

Razor 编译器不再生成单独的 Views 程序集

Razor 编译器以前利用两步编译过程,该编译过程生成单独的 Views 程序集,其中包含应用中定义的生成视图和页(.cshtml 文件)。 生成的类型是公共的,在 AspNetCore 命名空间下。

更新的 Razor 编译器将视图和页面类型生成到主项目程序集中。 这些类型现在默认生成为 AspNetCoreGeneratedDocument 命名空间中的内部密封类型。 此更改可改进生成性能,启用单文件部署,并使这些类型能够参与热重载

有关此更改的信息,请参阅 GitHub 上的相关公告发布

ASP.NET Core 性能和 API 改进

我们进行了许多更改以减少分配并提高堆栈的性能:

减少了空闲 TLS 连接的内存占用量

对于长时间运行的 TLS 连接(其中数据偶尔会来回发送),我们显著减少了 .NET 6 中 ASP.NET Core 应用的内存占用。 这应有助于提高 WebSocket 服务器等方案的可伸缩性。 由于 System.IO.PipelinesSslStream 和 Kestrel 中的大量改进,这是可以实现的。 以下部分详细介绍了导致内存占用减少的一些改进:

减小 System.IO.Pipelines.Pipe 大小

对于建立的每个连接,在 Kestrel 中分配两个管道:

  • 请求应用的传输层。
  • 响应传输的应用程序层。

System.IO.Pipelines.Pipe 的大小从 368 字节收缩到 264 字节(约减少 28.2%),每个连接可节省 208 个字节(每个管道 104 个字节)。

池 SocketSender

SocketSender 对象(子类 SocketAsyncEventArgs)运行时约为 350 个字节。 可以进行池化,而不是每个连接分配新的 SocketSender 对象。 SocketSender 对象可以池化,因为发送通常非常快。 池化可减少每个连接的开销。 不是为每个连接分配 350 个字节,而是仅为每个 IOQueue 分配 350 个字节。 分配按队列进行,以避免争用。 具有 5000 个空闲连接的 WebSocket 服务器从为 SocketSender 对象分配约 1.75 MB(350 字节 * 5000)到分配约 2.8 kb(350 字节 * 8)。

使用 SslStream 读取零字节

无缓冲区读取是 ASP.NET Core 中采用的一种方法,以避免在套接字上没有可用数据时从内存池租用内存。 在此更改之前,具有 5000 个空闲连接的 WebSocket 服务器需要大约 200 MB(不使用 TLS),而使用 TLS 需要大约 800 MB。 其中一些分配(每个连接 4k)来自 Kestrel,在等待 SslStream 上的读取完成时,必须保留 ArrayPool<T> 缓冲区。 假设这些连接处于空闲状态,那么所有读取操作都没有完成,并将其缓冲区返回到 ArrayPool,这会迫使 ArrayPool 分配更多内存。 剩余的分配本身在 SslStream 中:4k 缓冲区用于 TLS 握手,32k 缓冲区则用于正常读取。 在 .NET 6 中,当用户对 SslStream 执行零字节读取且没有可用数据时,SslStream 会在内部对基础包装流执行零字节读取。 在最佳情况下(空闲连接),这些更改可为每个连接节省 40 Kb,同时仍允许在数据可用时通知使用者 (Kestrel),而无需保留任何未使用的缓冲区。

使用 PipeReader 的零字节读取

SslStream 上支持无缓冲区读取时,添加了一个选项来对 StreamPipeReader 执行零字节读取,该内部类型将 Stream 调整为 PipeReader。 在 Kestrel 中,StreamPipeReader 用于将基础 SslStream 调整为 PipeReader。 因此,需要在 PipeReader 上公开这些零字节读取语义。

现在可以创建一个 PipeReader,它支持使用以下 API 对支持零字节读取语义(例如 SslStreamNetworkStream 等)的任何基础 Stream 进行零字节读取:

var reader = PipeReader.Create(stream, new StreamPipeReaderOptions(useZeroByteReads: true));

SlabMemoryPool 中删除碎片

为了减少堆碎片,Kestrel 采用了一种方法,将 128 KB 的内存碎片作为内存池的一部分进行分配。 然后,将碎片进一步划分为 4 KB 块,这些块由 Kestrel 在内部使用。 这些碎片必须大于 85 KB,以强制对大型对象堆进行分配,进而尝试阻止 GC 重新定位此数组。 但是,随着新 GC 代的引入,固定对象堆 (POH),在碎片上分配块不再有意义。 Kestrel 现在直接在 POH 上分配块,从而降低管理内存池所涉及的复杂性。 此更改将使以后的更改更易于执行,例如更轻松地缩小 Kestrel 使用的内存池。

IAsyncDisposable 支持

IAsyncDisposable 现在可用于控制器、Razor Pages 和视图组件。 异步版本已添加到工厂和激活器中的相关接口:

  • 新方法提供一个默认接口实现,该实现委托给同步版本并调用 Dispose
  • 这些实现替代默认实现并处理释放 IAsyncDisposable 实现。
  • 实现在两个接口都实现时倾向于使用 IAsyncDisposable 而不是 IDisposable
  • 扩展程序必须重写包含的新方法以支持 IAsyncDisposable 实例。

IAsyncDisposable 在使用以下项时是有益的:

  • 例如,异步流中的异步枚举器。
  • 具有要释放的资源密集型 I/O 操作的非托管资源。

实现此接口时,请使用 DisposeAsync 方法来释放资源。

请考虑创建和使用 Utf8JsonWriter 的控制器。 Utf8JsonWriter 是一种 IAsyncDisposable 资源:

public class HomeController : Controller, IAsyncDisposable
{
    private Utf8JsonWriter? _jsonWriter;
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
        _jsonWriter = new Utf8JsonWriter(new MemoryStream());
    }

IAsyncDisposable 必须实现 DisposeAsync

public async ValueTask DisposeAsync()
{
    if (_jsonWriter is not null)
    {
        await _jsonWriter.DisposeAsync();
    }

    _jsonWriter = null;
}

SignalR C++ 客户端的 Vcpkg 端口

Vcpkg 是适用于 C 和 C++ 库的跨平台命令行包管理器。 我们最近向 vcpkg 添加了一个端口,以便为 SignalR C++ 客户端添加 CMake 本地支持。 vcpkg 还适用于 MSBuild。

当工具链文件中包含 vcpkg 时,可以使用以下代码片段将 SignalR 客户端添加到 CMake 项目:

find_package(microsoft-signalr CONFIG REQUIRED)
link_libraries(microsoft-signalr::microsoft-signalr)

在上面的代码片段中,SignalR C++ 客户端已准备好使用 #include,无需任何其他配置即可在项目中使用。 有关利用 SignalR C++ 客户端的 C++ 应用程序的完整示例,请参阅 SignalR 存储库。

Blazor

项目模板更改

对 Blazor 应用进行了几处项目模板更改,包括将 Pages/_Layout.cshtml 文件用于早期 Blazor Server 应用的 _Host.cshtml 文件中出现的布局内容。 通过从 6.0 项目模板创建应用或访问项目模板的 ASP.NET Core 参考源来研究更改:

Blazor WebAssembly 本机依赖项支持

Blazor WebAssembly 应用可以使用生成的本机依赖项在 WebAssembly 上运行。 有关详细信息,请参阅 ASP.NET Core Blazor WebAssembly 本机依赖项

WebAssembly 预处理 (AOT) 编译和运行时重新链接

Blazor WebAssembly 支持预先 (AOT) 编译,你可以直接将 .NET 代码编译到 WebAssembly 中。 AOT 编译会提高运行时性能,代价是应用大小增加。 重新链接 .NET WebAssembly 运行时会剪裁未使用的运行时代码,从而提高下载速度。 有关详细信息,请参阅预先 (AOT) 编译运行时重新链接

保留预呈现状态

Blazor 支持在预呈现页中保留状态,这样无需在完全加载应用时重新创建状态。 有关详细信息,请参阅预呈现和集成 ASP.NET Core Razor 组件

错误边界

错误边界为在 UI 级别处理异常提供了一种便捷方法。 有关详细信息,请参阅处理 ASP.NET Core Blazor 应用中的错误

SVG 支持

支持 <foreignObject> 元素 元素在 SVG 中显示任意 HTML。 有关详细信息,请参阅 ASP.NET Core Razor 组件

JS 互操作中对字节数组传输的 Blazor Server 支持

Blazor 支持优化的字节数组 JS 互操作,这可以避免将字节数组编码和解码为 Base64。 有关详细信息,请参阅以下资源:

查询字符串增强功能

改进了对使用查询字符串的支持。 有关详细信息,请参阅 ASP.NET Core Blazor 路由和导航

绑定以选择多个

绑定支持 <input> 元素的多选项选择。 有关更多信息,请参见以下资源:

头 (<head>) 内容控件

Razor 组件可以修改页面的 HTML <head> 元素内容,包括设置页标题(<title> 元素)和修改元数据(<meta> 元素)。 有关详细信息,请参阅在 ASP.NET Core Blazor 应用中控制 <head> 内容

生成 Angular 和 React 组件

通过 Razor 组件为 Web 框架生成特定于框架的 JavaScript 组件,例如 Angular 或 React。 有关详细信息,请参阅 ASP.NET Core Razor 组件

从 JavaScript 呈现组件

从 JavaScript 动态呈现现有 JavaScript 应用的 Razor 组件。 有关详细信息,请参阅 ASP.NET Core Razor 组件

自定义元素

试验性支持可用于生成使用标准 HTML 接口的自定义元素。 有关详细信息,请参阅 ASP.NET Core Razor 组件

从上级组件推断组件泛型类型

上级组件可以使用新的 [CascadingTypeParameter] 属性将类型参数按名称级联到下级。 有关详细信息,请参阅 ASP.NET Core Razor 组件

动态呈现的组件

使用新的内置 DynamicComponent 组件按类型呈现组件。 有关详细信息,请参阅动态呈现的 ASP.NET Core Razor 组件

改进了 Blazor 辅助功能

在页面之间进行导航后,使用新的 FocusOnNavigate 组件基于 CSS 选择器将 UI 焦点设置到元素。 有关详细信息,请参阅 ASP.NET Core Blazor 路由和导航

自定义事件参数支持

Blazor 支持自定义事件参数。借助这些参数,你可以将任意数据通过自定义事件传递给 .NET 事件处理程序。 有关详细信息,请参阅 ASP.NET Core Blazor 事件处理

必需的参数

应用新的 [EditorRequired] 属性指定所需的组件参数。 有关详细信息,请参阅 ASP.NET Core Razor 组件

包含页面、视图和组件的 JavaScript 文件的并置

为页面、视图和 Razor 组件并置 JavaScript 文件是在应用中组织脚本的一种简便方法。 有关详细信息,请参阅 ASP.NET Core BlazorJavaScript 互操作性(JS 互操作)

JavaScript 初始值设定项

JavaScript 初始值设定项在 Blazor 应用加载之前和之后执行逻辑。 有关详细信息,请参阅 ASP.NET Core BlazorJavaScript 互操作性(JS 互操作)

流式处理 JavaScript 互操作

Blazor 现支持在 .NET 和 JavaScript 之间直接流式传输数据。 有关详细信息,请参阅以下资源:

泛型类型约束

现支持泛型类型参数。 有关详细信息,请参阅 ASP.NET Core Razor 组件

WebAssembly 部署布局

使用部署布局在受限的安全环境中启用 Blazor WebAssembly 应用下载。 有关详细信息,请参阅 ASP.NET Core 托管的 Blazor WebAssembly 应用的部署布局

新 Blazor 文章

除了前面部分所述的 Blazor 功能外,以下主题还提供了新的 Blazor 新文章:

使用 .NET MAUI、WPF 和 Windows 窗体生成 Blazor Hybrid 应用

使用 Blazor Hybrid 将桌面和移动本机客户端框架与 .NET 和 Blazor 结合使用:

  • .NET Multi-platform App UI (.NET MAUI) 用于使用 C# 和 XAML 创建本机移动应用和桌面应用的跨平台框架。
  • Blazor Hybrid 应用可以使用 Windows Presentation Foundation (WPF) 和 Windows 窗体框架构建。

重要

Blazor Hybrid 目前为预览版,在最终发布之前,不应在生产应用中使用。

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

Kestrel

HTTP/3 目前处于草稿状态,因此可能会更改。 ASP.NET Core 中的 HTTP/3 支持未发布,它是 .NET 6 中包含的预览功能。

Kestrel 现支持 HTTP/3。 有关详细信息,请参阅对 ASP.NET Core Kestrel Web 服务器使用 HTTP/3和博客文章 .NET 6 中的 HTTP/3 支持

所选日志记录的新 Kestrel 日志记录类别

在此更改之前,为 Kestrel 启用详细日志记录的成本过高,因为所有 Kestrel 共享 Microsoft.AspNetCore.Server.Kestrel 日志记录类别名称。 Microsoft.AspNetCore.Server.Kestrel 仍然可用,但以下新子类别允许对日志记录进行更多控制:

  • Microsoft.AspNetCore.Server.Kestrel(当前类别):ApplicationErrorConnectionHeadResponseBodyWriteApplicationNeverCompletedRequestBodyStartRequestBodyDoneRequestBodyNotEntirelyReadRequestBodyDrainTimedOutResponseMinimumDataRateNotSatisfiedInvalidResponseHeaderRemovedHeartbeatSlow
  • Microsoft.AspNetCore.Server.Kestrel.BadRequestsConnectionBadRequestRequestProcessingErrorRequestBodyMinimumDataRateNotSatisfied
  • Microsoft.AspNetCore.Server.Kestrel.ConnectionsConnectionAcceptedConnectionStartConnectionStopConnectionPauseConnectionResumeConnectionKeepAliveConnectionRejectedConnectionDisconnectNotAllConnectionsClosedGracefullyNotAllConnectionsAbortedApplicationAbortedConnection
  • Microsoft.AspNetCore.Server.Kestrel.Http2Http2ConnectionErrorHttp2ConnectionClosingHttp2ConnectionClosedHttp2StreamErrorHttp2StreamResetAbortHPackDecodingErrorHPackEncodingErrorHttp2FrameReceivedHttp2FrameSendingHttp2MaxConcurrentStreamsReached
  • Microsoft.AspNetCore.Server.Kestrel.Http3Http3ConnectionErrorHttp3ConnectionClosingHttp3ConnectionClosedHttp3StreamAbortHttp3FrameReceivedHttp3FrameSending

现有规则继续工作,但现在可以更有选择性地启用规则。 例如,为错误请求启用 Debug 日志记录的可观测性开销大大减少,并且可以通过以下配置启用:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.Kestrel.BadRequests": "Debug"
    }
  }

日志筛选应用具有最长匹配类别前缀的规则。 有关详细信息,请参阅如何应用筛选规则

通过 EventSource 事件发出 KestrelServerOptions

启用详细 EventLevel.LogAlways 时,KestrelEventSource 发出一个包含 JSON 序列化 KestrelServerOptions 的新事件。 通过此事件,可以在分析收集的跟踪时更轻松地推断服务器行为。 以下 JSON 是事件有效负载的示例:

{
  "AllowSynchronousIO": false,
  "AddServerHeader": true,
  "AllowAlternateSchemes": false,
  "AllowResponseHeaderCompression": true,
  "EnableAltSvc": false,
  "IsDevCertLoaded": true,
  "RequestHeaderEncodingSelector": "default",
  "ResponseHeaderEncodingSelector": "default",
  "Limits": {
    "KeepAliveTimeout": "00:02:10",
    "MaxConcurrentConnections": null,
    "MaxConcurrentUpgradedConnections": null,
    "MaxRequestBodySize": 30000000,
    "MaxRequestBufferSize": 1048576,
    "MaxRequestHeaderCount": 100,
    "MaxRequestHeadersTotalSize": 32768,
    "MaxRequestLineSize": 8192,
    "MaxResponseBufferSize": 65536,
    "MinRequestBodyDataRate": "Bytes per second: 240, Grace Period: 00:00:05",
    "MinResponseDataRate": "Bytes per second: 240, Grace Period: 00:00:05",
    "RequestHeadersTimeout": "00:00:30",
    "Http2": {
      "MaxStreamsPerConnection": 100,
      "HeaderTableSize": 4096,
      "MaxFrameSize": 16384,
      "MaxRequestHeaderFieldSize": 16384,
      "InitialConnectionWindowSize": 131072,
      "InitialStreamWindowSize": 98304,
      "KeepAlivePingDelay": "10675199.02:48:05.4775807",
      "KeepAlivePingTimeout": "00:00:20"
    },
    "Http3": {
      "HeaderTableSize": 0,
      "MaxRequestHeaderFieldSize": 16384
    }
  },
  "ListenOptions": [
    {
      "Address": "https://127.0.0.1:7030",
      "IsTls": true,
      "Protocols": "Http1AndHttp2"
    },
    {
      "Address": "https://[::1]:7030",
      "IsTls": true,
      "Protocols": "Http1AndHttp2"
    },
    {
      "Address": "http://127.0.0.1:5030",
      "IsTls": false,
      "Protocols": "Http1AndHttp2"
    },
    {
      "Address": "http://[::1]:5030",
      "IsTls": false,
      "Protocols": "Http1AndHttp2"
    }
  ]
}

已拒绝 HTTP 请求的新 DiagnosticSource 事件

Kestrel 现针对在服务器层拒绝的 HTTP 请求发出新的 DiagnosticSource 事件。 在此更改之前,无法观察这些被拒绝的请求。 新的 DiagnosticSource 事件 Microsoft.AspNetCore.Server.Kestrel.BadRequest 包含可用于自检拒绝请求的原因的 IBadRequestExceptionFeature

using Microsoft.AspNetCore.Http.Features;
using System.Diagnostics;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var diagnosticSource = app.Services.GetRequiredService<DiagnosticListener>();
using var badRequestListener = new BadRequestEventListener(diagnosticSource,
    (badRequestExceptionFeature) =>
{
    app.Logger.LogError(badRequestExceptionFeature.Error, "Bad request received");
});
app.MapGet("/", () => "Hello world");

app.Run();

class BadRequestEventListener : IObserver<KeyValuePair<string, object>>, IDisposable
{
    private readonly IDisposable _subscription;
    private readonly Action<IBadRequestExceptionFeature> _callback;

    public BadRequestEventListener(DiagnosticListener diagnosticListener,
                                   Action<IBadRequestExceptionFeature> callback)
    {
        _subscription = diagnosticListener.Subscribe(this!, IsEnabled);
        _callback = callback;
    }
    private static readonly Predicate<string> IsEnabled = (provider) => provider switch
    {
        "Microsoft.AspNetCore.Server.Kestrel.BadRequest" => true,
        _ => false
    };
    public void OnNext(KeyValuePair<string, object> pair)
    {
        if (pair.Value is IFeatureCollection featureCollection)
        {
            var badRequestFeature = featureCollection.Get<IBadRequestExceptionFeature>();

            if (badRequestFeature is not null)
            {
                _callback(badRequestFeature);
            }
        }
    }
    public void OnError(Exception error) { }
    public void OnCompleted() { }
    public virtual void Dispose() => _subscription.Dispose();
}

有关详细信息,请参阅 Kestrel 中的日志记录和诊断

从接受套接字创建 ConnectionContext

使用新的 SocketConnectionContextFactory,可以基于接受的套接字创建 ConnectionContext。 这样,可生成基于自定义套接字的 IConnectionListenerFactory,而不会丢失 IConnectionListenerFactory 中发生的所有性能工作和池化。

请参阅此自定义 IConnectionListenerFactory 示例,该示例演示如何使用此 SocketConnectionContextFactory

Kestrel 是 Visual Studio 的默认启动配置文件

所有新 dotnet Web 项目的默认启动配置文件为 Kestrel。 启动 Kestrel 速度要快得多,在开发应用时可带来响应速度更快的体验。

IIS Express 仍可以作为 Windows 身份验证或端口共享等场景的启动配置文件使用。

Kestrel 的 Localhost 端口是随机的

有关详细信息,请参阅本文档中的模板生成的 Kestrel 端口

身份验证和授权

身份验证服务器

.NET 3 到 .NET 5 使用 IdentityServer4 作为模板的一部分,以支持为 SPA 和 Blazor 应用程序颁发 JWT 令牌。 这些模板现在使用 Duende Identity Server

如果要扩展标识模型并更新现有项目,则需要将代码中的命名空间从 IdentityServer4.IdentityServer 更新到 Duende.IdentityServer,并按照其IdentityServer4.IdentityServer操作。

Duende Identity Server 的许可证模型已更改为对等许可证,在生产中以商业方式使用时,可能会产生许可证费用。 有关更多详细信息,请参阅 Duende 许可证页

延迟客户端证书协商

开发人员现在可以通过在 HttpsConnectionAdapterOptions 上指定 ClientCertificateMode.DelayCertificate 来选择使用延迟客户端证书协商。 这仅适用于 HTTP/1.1 连接,因为 HTTP/2 禁止延迟证书重新协商。 此 API 的调用方必须在请求客户端证书之前缓冲请求正文:

using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.WebUtilities;

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseKestrel(options =>
{
    options.ConfigureHttpsDefaults(adapterOptions =>
    {
        adapterOptions.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
    });
});

var app = builder.Build();
app.Use(async (context, next) =>
{
    bool desiredState = GetDesiredState();
    // Check if your desired criteria is met
    if (desiredState)
    {
        // Buffer the request body
        context.Request.EnableBuffering();
        var body = context.Request.Body;
        await body.DrainAsync(context.RequestAborted);
        body.Position = 0;

        // Request client certificate
        var cert = await context.Connection.GetClientCertificateAsync();

        //  Disable buffering on future requests if the client doesn't provide a cert
    }
    await next(context);
});


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

现在可以使用新的 OnCheckSlidingExpiration 自定义或禁止 Cookie 身份验证滑动过期。 例如,需要定期 ping 服务器而不影响身份验证会话的单页应用程序可以使用此事件。

杂项

热重载

使用热重载对正在运行的应用进行不丢失应用状态下的快速 UI 和代码更新,并获得更快和更高效的开发人员体验。 有关详细信息,请参阅 ASP.NET Core 的 .NET 热重载支持更新 .NET 热重载进度和 Visual Studio 2022 热点

改进了单页应用 (SPA) 模板

ASP.NET Core 项目模板已针对 Angular 和 React 进行了更新,以对更灵活且更符合新式前端 Web 开发常见模式的单页应用使用改进的模式。

此前,适用于 Angular 和 React 的 ASP.NET Core 模板在开发过程中使用专用中间件来启动前端框架开发服务器,然后将 ASP.NET Core 的请求代理到开发服务器。 启动前端开发服务器的逻辑特定于相应前端框架的命令行接口。 使用此模式支持其他前端框架意味着向 ASP.NET Core 添加额外逻辑。

.NET 6 中适用于 Angular 和 React 的已更新 ASP.NET Core 模板可翻转这一安排,并充分利用大多数新式前端框架的开发服务器中的内置代理支持。 启动 ASP.NET Core 应用时,前端开发服务器将像以前那样启动,但开发服务器配置为将请求代理到后端 ASP.NET Core 进程。 用于设置代理的所有前端特定配置都是应用的一部分,而不是 ASP.NET Core 的一部分。 现在将 ASP.NET Core 项目设置为与其他前端框架一起工作很简单:使用 Angular 和 React 模板中建立的模式,为所选框架设置前端开发服务器,以代理到 ASP.NET Core 后端。

ASP.NET Core 应用的启动代码不再需要任何单页应用特定的逻辑。 开发期间启动前端开发服务器的逻辑是由新的 Microsoft.AspNetCore.SpaProxy 包在运行时注入到应用中的。 回退路由使用终结点路由而不是特定于 SPA 的中间件进行处理。

遵循此模式的模板仍可作为单个项目在 Visual Studio 中运行,或者从命令行使用 dotnet run 运行。 发布应用后,ClientApp 文件夹中的前端代码将像以前那样生成并收集到主机 ASP.NET Core 应用的 Web 根目录,并作为静态文件提供。 模板中包含的脚本使用 ASP.NET Core 证书,将前端开发服务器配置为使用 HTTPS。

.NET 6 中的草稿 HTTP/3 支持

HTTP/3 目前处于草稿状态,因此可能会更改。 ASP.NET Core 中的 HTTP/3 支持未发布,它是 .NET 6 中包含的预览功能。

请参阅博客文章 .NET 6 中的 HTTP/3 支持

可以为 Null 的引用类型注释

ASP.NET Core 6.0 源代码部分已应用了为 Null 性注释

通过利用 C# 8 中的可以为 Null 的功能,ASP.NET Core 在处理引用类型时提供额外的编译时安全性。 例如,防止 null 引用异常。 已选择使用可以为 Null 的注释的项目可能会看到来自 ASP.NET Core API 的新生成时警告。

若要启用可以为 Null 的引用类型,请向项目文件添加以下属性:

<PropertyGroup>
    <Nullable>enable</Nullable>
</PropertyGroup>

有关详细信息,请参阅可为空引用类型

源代码分析

添加了多个 .NET 编译器平台分析器,用于检查应用程序代码是否存在中间件配置或顺序错误、路由冲突等问题。有关详细信息,请参阅 ASP.NET Core 应用中的代码分析

Web 应用模板改进

Web 应用模板:

  • 使用新的最小托管模型
  • 显著减少创建应用所需的文件和代码行数。 例如,ASP.NET Core 空 Web 应用创建一个包含四行代码的 C# 文件,它是一个完整的应用。
  • Startup.csProgram.cs 统一到单个 Program.cs 文件中。
  • 使用顶级语句最大程度减少应用所需的代码。
  • 使用全局 using 指令消除或最大程度减少所需的 using 语句行数。

模板生成的 Kestrel 端口

随机端口在项目创建期间分配,供 Kestrel Web 服务器使用。 在同一计算机上运行多个项目时,随机端口有助于最大程度地减少端口冲突。

创建项目时,将在生成的 Properties/launchSettings.json 文件中指定介于 5000-5300 之间的随机 HTTP 端口和介于 7000-7300 之间的随机 HTTPS 端口。 可以在 Properties/launchSettings.json 文件中更改这些端口。 如果未指定端口,则 Kestrel 默认为 HTTP 5000 和 HTTPS 5001 端口。 有关详细信息,请参阅为 ASP.NET Core Kestrel Web 服务器配置终结点

新日志记录默认值

appsettings.jsonappsettings.Development.json 中进行了以下更改:

- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Microsoft.AspNetCore": "Warning"

"Microsoft": "Warning" 更改为 "Microsoft.AspNetCore": "Warning" 会导致记录 Microsoft 命名空间中除 "Microsoft": "Warning"Microsoft.AspNetCore 以外的所有信息性消息。 例如,Microsoft.EntityFrameworkCore 现记录在信息级别。

自动添加开发人员异常页中间件

开发环境中,默认添加 DeveloperExceptionPageMiddleware。 不再需要将以下代码添加到 Web UI 应用:

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

支持 HttpSysServer 中的 Latin1 编码请求标头

HttpSysServer 现支持通过将 HttpSysOptions 上的 UseLatin1RequestHeaders 属性设置为 trueLatin1 编码的请求头进行解码:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseHttpSys(o => o.UseLatin1RequestHeaders = true);

var app = builder.Build();

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

app.Run();

ASP.NET Core 模块日志包括时间戳和 PID

用于 IIS 的 ASP.NET Core 模块 (ANCM) 增强的诊断日志包括发出日志的进程的时间戳和 PID。 记录时间戳和 PID 可以更轻松地诊断多个 IIS 工作进程运行时 IIS 中重叠进程重启的问题。

生成的日志现在类似于下面显示的示例输出:

[2021-07-28T19:23:44.076Z, PID: 11020] [aspnetcorev2.dll] Initializing logs for 'C:\<path>\aspnetcorev2.dll'. Process Id: 11020. File Version: 16.0.21209.0. Description: IIS ASP.NET Core Module V2. Commit: 96475a2acdf50d7599ba8e96583fa73efbe27912.
[2021-07-28T19:23:44.079Z, PID: 11020] [aspnetcorev2.dll] Resolving hostfxr parameters for application: '.\InProcessWebSite.exe' arguments: '' path: 'C:\Temp\e86ac4e9ced24bb6bacf1a9415e70753\'
[2021-07-28T19:23:44.080Z, PID: 11020] [aspnetcorev2.dll] Known dotnet.exe location: ''

IIS 的可配置未用传入缓冲区大小

IIS 服务器以前仅缓冲 64 KiB 的未用请求正文。 64 KiB 缓冲导致读取被限制到该最大大小,这会影响大型传入正文(如上传)的性能。 在 .NET 6 中,默认缓冲区大小从 64 KiB 更改到 1 MiB,这可以提高大型上传的吞吐量。 在我们的测试中,过去需要 9 秒的 700 MiB 上传,现在只需要 2.5 秒。

大型缓冲区大小的缺点是应用无法从请求正文快速读取时,每个请求内存消耗增加。 因此,除了更改默认缓冲区大小外,缓冲区大小可配置,允许应用根据工作负荷配置缓冲区大小。

查看组件标记帮助程序

请考虑具有可选参数的视图组件,如以下代码所示:

class MyViewComponent
{
    IViewComponentResult Invoke(bool showSomething = false) { ... }
}

使用 ASP.NET Core 6 时,无需为 showSomething 参数指定值即可调用标记帮助程序:

<vc:my />

Angular 模板已更新为 Angular 12

适用于 Angular 的 ASP.NET Core 6.0 模板现在使用 Angular 12

React 模板已更新至 React 17

在以 Json.NET 输出格式化程序写入磁盘之前可配置的缓冲区阈值

注意:建议使用 输出格式化程序,除非出于兼容性原因需要 Newtonsoft.Json 序列化程序。 System.Text.Json 序列化程序完全 async,适用于大型有效负载。

默认情况下,Newtonsoft.Json 输出格式化程序在缓冲到磁盘之前在内存中最多缓冲 32 KiB 的响应。 这是为了避免执行同步 IO,这可能会导致其他副作用,例如线程不足和应用程序死锁。 但是,如果响应大于 32 KiB,则会发生大量磁盘 I/O。 现在可以通过 MvcNewtonsoftJsonOptions.OutputFormatterMemoryBufferThreshold 属性配置内存阈值,然后再缓冲到磁盘:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages()
            .AddNewtonsoftJson(options =>
            { 
                options.OutputFormatterMemoryBufferThreshold = 48 * 1024;
            });

var app = builder.Build();

有关详细信息,请参阅此 GitHub 拉取请求NewtonsoftJsonOutputFormatterTest.cs 文件。

更快地获取和设置 HTTP 头

添加了新的 API,将 Microsoft.Net.Http.Headers.HeaderNames 上可用的所有通用标头公开为 IHeaderDictionary 上的属性,从而更轻松地使用 API。 例如,以下代码中的内联中间件使用新 API 获取和设置请求头和响应头:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Use(async (context, next) =>
{
    var hostHeader = context.Request.Headers.Host;
    app.Logger.LogInformation("Host header: {host}", hostHeader);
    context.Response.Headers.XPoweredBy = "ASP.NET Core 6.0";
    await next.Invoke(context);
    var dateHeader = context.Response.Headers.Date;
    app.Logger.LogInformation("Response date: {date}", dateHeader);
});

app.Run();

对于实现的标头,通过直接进入字段并绕过查找实现 get 和 set 访问器。 对于未实现的标头,访问器可以绕过针对已实现标头的初始查找,直接执行 Dictionary<string, StringValues> 查找。 避免查找可加快对这两种方案的访问。

异步流式处理

ASP.NET Core 现支持来自控制器操作的异步流式处理和来自 JSON 格式化程序的响应。 从操作返回 IAsyncEnumerable 时,在发送响应内容之前,不再在内存中缓冲响应内容。 返回可异步枚举的大型数据集时,不缓冲有助于减少内存使用量。

请注意,Entity Framework Core 提供 IAsyncEnumerable 实现来查询数据库。 改进了对 .NET 6 中 ASP.NET Core 的 IAsyncEnumerable 的支持,可以更有效地将 EF Core 与 ASP.NET Core 配合使用。 例如,以下代码在发送响应之前不再将产品数据缓冲到内存中:

public IActionResult GetMovies()
{
    return Ok(_context.Movie);
}

但是,在 EF Core 中使用延迟加载时,此新行为可能会导致在枚举数据时由于执行并发查询而出错。 应用可以通过缓冲数据还原到以前的行为:

public async Task<IActionResult> GetMovies2()
{
    return Ok(await _context.Movie.ToListAsync());
}

有关此行为更改的其他详细信息,请参阅相关公告

HTTP 日志记录中间件

HTTP 日志记录是一个新的内置中间件,它记录有关 HTTP 请求和 HTTP 响应的信息(包括标头和整个正文):

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();
app.UseHttpLogging();

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

app.Run();

使用上述代码日志信息导航到 /,输出信息如下所示:

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
      Request:
      Protocol: HTTP/2
      Method: GET
      Scheme: https
      PathBase: 
      Path: /
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
      Accept-Encoding: gzip, deflate, br
      Accept-Language: en-US,en;q=0.9
      Cache-Control: max-age=0
      Connection: close
      Cookie: [Redacted]
      Host: localhost:44372
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30
      sec-ch-ua: [Redacted]
      sec-ch-ua-mobile: [Redacted]
      sec-ch-ua-platform: [Redacted]
      upgrade-insecure-requests: [Redacted]
      sec-fetch-site: [Redacted]
      sec-fetch-mode: [Redacted]
      sec-fetch-user: [Redacted]
      sec-fetch-dest: [Redacted]
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      StatusCode: 200
      Content-Type: text/plain; charset=utf-8

以下 appsettings.Development.json 文件启用了上述输出:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
    }
  }
}

HTTP 日志记录可以记录:

  • HTTP 请求信息
  • 公共属性
  • 标头
  • 正文
  • HTTP 响应信息

若要配置 HTTP 日志记录中间件,请指定 HttpLoggingOptions

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
    // Customize HTTP logging.
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("My-Request-Header");
    logging.ResponseHeaders.Add("My-Response-Header");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
});

var app = builder.Build();
app.UseHttpLogging();

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

app.Run();

IConnectionSocketFeature

IConnectionSocketFeature 请求功能提供对与当前请求关联的基础接受套接字的访问。 可以通过 HttpContext 上的 FeatureCollection 进行访问。

例如,以下应用在接受的套接字上设置 LingerState 属性:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ConfigureEndpointDefaults(listenOptions => listenOptions.Use((connection, next) =>
    {
        var socketFeature = connection.Features.Get<IConnectionSocketFeature>();
        socketFeature.Socket.LingerState = new LingerOption(true, seconds: 10);
        return next();
    }));
});
var app = builder.Build();
app.MapGet("/", (Func<string>)(() => "Hello world"));
await app.RunAsync();

Razor 中的泛型类型约束

使用 @typeparam 指令在 Razor 中定义泛型类型参数时,现在可以使用标准 C# 语法指定泛型类型约束:

较小的 SignalR、Blazor Server 和 MessagePack 脚本

SignalR、MessagePack 和 Blazor Server 脚本现在明显较小,可实现较小的下载量、更少的 JavaScript 分析并由浏览器编译,以及更快的启动速度。 大小缩减:

  • signalr.js:70%
  • blazor.server.js:45%

这些较小的脚本是 Ben Adams 在社区贡献的结果。 有关大小缩减的详细信息,请参阅 Ben 的 GitHub 拉取请求

启用 Redis 分析会话

Gabriel Lucaci 在社区贡献的内容可通过 Microsoft.Extensions.Caching.StackExchangeRedis 启用 Redis 分析会话:

using StackExchange.Redis.Profiling;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.ProfilingSession = () => new ProfilingSession();
});

有关详细信息,请参阅 StackExchange.Redis 分析

IIS 中的卷影复制

试验性功能已添加到用于 IIS 的 ASP.NET Core 模块 (ANCM),以添加对卷影复制应用程序程序集的支持。 目前,.NET 在 Windows 上运行时会锁定应用程序二进制文件,因此在应用运行时无法替换二进制文件。 虽然仍建议使用应用脱机文件,但我们发现在某些情况下(例如 FTP 部署)无法这样做。

在这种情况下,通过自定义 ASP.NET Core 模块处理程序设置来启用卷影复制。 在大多数情况下,ASP.NET Core 没有签入可修改的源代码管理的 web.config。 在 ASP.NET Core中,web.config 通常由 SDK 生成。 以下示例 web.config 可帮助你入门:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <!-- To customize the asp.net core module uncomment and edit the following section. 
  For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->

  <system.webServer>
    <handlers>
      <remove name="aspNetCore"/>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout">
      <handlerSettings>
        <handlerSetting name="experimentalEnableShadowCopy" value="true" />
        <handlerSetting name="shadowCopyDirectory" value="../ShadowCopyDirectory/" />
        <!-- Only enable handler logging if you encounter issues-->
        <!--<handlerSetting name="debugFile" value=".\logs\aspnetcore-debug.log" />-->
        <!--<handlerSetting name="debugLevel" value="FILE,TRACE" />-->
      </handlerSettings>
    </aspNetCore>
  </system.webServer>
</configuration>

IIS 中的卷影复制是一项试验性功能,不一定属于 ASP.NET Core。 请在此 GitHub 问题中留下有关 IIS 卷影复制的反馈。

其他资源