.NET 10 ASP.NET Core 中的新增功能

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

本文将在提供新的预览版时更新。 有关中断性变更,请参阅 .NET 中的中断性变更

Blazor

本部分介绍 Blazor的新功能。

新的和经过更新的Blazor Web App安全示例

我们添加并更新了以下文章中链接的 Blazor Web App 个安全示例:

我们的所有 OIDC 和 Entra 示例解决方案现在都包含单独的 Web API 项目(MinimalApiJwt),演示如何安全地配置和调用外部 Web API。 使用令牌处理程序,并通过 OIDC 身份提供者或 Microsoft Entra ID 的 Microsoft Identity Web 包或 API 所指定的 HTTP 客户端演示调用 Web API。

示例解决方案在 Program 文件中的 C# 代码中配置。 若要从应用设置文件(例如,appsettings.json)配置解决方案,请参阅 OIDC 或 Entra 文章中 新的JSON 配置提供程序(应用设置)配置供应部分。

我们的 Entra 示例还包括有关为 Web 场托管方案使用加密分布式令牌缓存的新指南。

QuickGridRowClass 参数

使用新 RowClass 参数,根据行项将样式表类应用于网格的行。 在以下示例中,对每一行调用 GetRowCssClass 方法,根据行项有条件地应用样式表类:

<QuickGrid ... RowClass="GetRowCssClass">
    ...
</QuickGrid>

@code {
    private string GetRowCssClass(MyGridItem item) =>
        item.IsArchived ? "row-archived" : null;
}

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

Blazor 脚本作为静态 Web 资产

在 .NET 的早期版本中,Blazor 脚本从 ASP.NET Core 共享框架中的嵌入资源提供。 在 .NET 10 或更高版本中,Blazor 脚本用作具有自动压缩和指纹的静态 Web 资产。

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

路由模板要点

[Route] 属性 现在支持路由语法突出显示,以帮助可视化路由模板的结构:

计数器值的路由属性 计数器值的路由属性的路由模板模式显示语法高亮显示

之前,NavigationManager.NavigateTo 会滚动到页面顶部来进行同一页面导航。 .NET 10 中已更改此行为,以便在导航到同一页面时,浏览器不再滚动到页面顶部。 这意味着在更新当前页面的地址(例如更改查询字符串或片段)时不再重置视区。

已将重新连接 UI 组件添加到 Blazor Web App 项目模板中。

Blazor Web App 项目模板现在包含一个 ReconnectModal 组件,包括并置样式表和 JavaScript 文件,用于在客户端失去与服务器的 WebSocket 连接时改进开发人员对重新连接 UI 的控制。 该组件不会以编程方式插入样式,确保符合 style-src 策略更严格的内容安全策略 (CSP) 设置。 在以前的版本中,默认重新连接 UI 由框架创建,从而可能导致 CSP 冲突。 请注意,当应用程序未定义重新连接 UI 时,默认的重新连接 UI 仍会作为备用方案使用,例如通过项目模板的 ReconnectModal 组件或类似的自定义组件。

新的重新连接 UI 功能:

  • 除了通过在重新连接 UI 元素上设置特定的 CSS 类来指示重新连接状态外,还调度新的 components-reconnect-state-changed 事件以更改重新连接状态。
  • 代码可以使用 CSS 类和新事件所指示的新重新连接状态“retrying”更好地区分重新连接过程的阶段。

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

使用 NavLinkMatch.All 时忽略查询字符串和片段

使用 NavLink 参数的 NavLinkMatch.All 值时,Match 组件现在将忽略查询字符串和片段。 这意味着,如果 URL 路径匹配但查询字符串或片段发生更改,则链接将保留 active 类。 若要还原为原始行为,请使用 Microsoft.AspNetCore.Components.Routing.NavLink.EnableMatchAllForQueryStringAndFragmentAppContext 开关 设置为 true

可以在 ShouldMatch 上替代 NavLink 方法,以自定义匹配行为。

public class CustomNavLink : NavLink
{
    protected override bool ShouldMatch(string currentUriAbsolute)
    {
        // Custom matching logic
    }
}

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

关闭 QuickGrid 列选项

现在可以使用新的 QuickGrid 方法关闭 HideColumnOptionsAsync 列选项用户界面。

以下示例展示如何在应用标题筛选器后,立即使用 HideColumnOptionsAsync 方法关闭列选项界面:

<QuickGrid @ref="movieGrid" Items="movies">
    <PropertyColumn Property="@(m => m.Title)" Title="Title">
        <ColumnOptions>
            <input type="search" @bind="titleFilter" placeholder="Filter by title" 
                @bind:after="@(() => movieGrid.HideColumnOptionsAsync())" />
        </ColumnOptions>
    </PropertyColumn>
    <PropertyColumn Property="@(m => m.Genre)" Title="Genre" />
    <PropertyColumn Property="@(m => m.ReleaseYear)" Title="Release Year" />
</QuickGrid>

@code {
    private QuickGrid<Movie>? movieGrid;
    private string titleFilter = string.Empty;
    private IQueryable<Movie> movies = new List<Movie> { ... }.AsQueryable();
    private IQueryable<Movie> filteredMovies => 
        movies.Where(m => m.Title!.Contains(titleFilter));
}

响应流式处理可以选择启用,如何选择停用

在以前的 Blazor 版本中,HttpClient 请求的响应流式处理可以选择启用。 现在,默认启用响应流式处理。

这是一项重大变更,因为调用 HttpContent.ReadAsStreamAsync 对于 HttpResponseMessage.Contentresponse.Content.ReadAsStreamAsync()) 返回的是 BrowserHttpReadStream,而不再返回 MemoryStreamBrowserHttpReadStream 不支持同步作,例如 Stream.Read(Span<Byte>)。 如果代码使用同步操作,可以选择禁用响应流式处理或自行复制StreamMemoryStream

若要选择退出全局响应流式处理,请使用以下任一方法:

  • <WasmEnableStreamingResponse> 属性添加到项目文件,值为 false

    <WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>
    
  • DOTNET_WASM_ENABLE_STREAMING_RESPONSE 环境变量 false 设置为或 0

要选择退出单个请求的响应流式处理,请将 SetBrowserResponseStreamingEnabled 上的 false 设为 HttpRequestMessage(在本例中为 requestMessage):

requestMessage.SetBrowserResponseStreamingEnabled(false);

有关详细信息,请参阅 HttpClientHttpRequestMessage 以及“提取 API 请求”选项(调用 Web API 文章)

客户端指纹识别

去年,.NET 9 的发布引入了对静态资源进行服务器端指纹识别,并引入了Blazor Web App、MapStaticAssets属性(ImportMap),用于解析指纹化的 JavaScript 模块。 对于 .NET 10,可以选择启用独立 Blazor WebAssembly 应用的 JavaScript 模块的客户端指纹识别功能。

在生成/发布期间的独立 Blazor WebAssembly 应用中,框架使用生成期间计算的值来替代 index.html 中的占位符,以对静态资产进行指纹识别。 指纹会植入到 blazor.webassembly.js 脚本文件名中。

文件中必须存在 wwwwoot/index.html 以下标记才能采用指纹功能:

<head>
    ...
+   <script type="importmap"></script>
</head>

<body>
    ...
-   <script src="_framework/blazor.webassembly.js"></script>
+   <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>

</html>

在项目文件中(.csproj),将 <OverrideHtmlAssetPlaceholders> 属性集添加到 true

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
+   <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
  </PropertyGroup>
</Project>

任何带有指纹标记的脚本 index.html 都会被框架处理以生成指纹。 例如,名为 scripts.js 的脚本文件位于应用的 wwwroot/js 文件夹中,通过在文件扩展名之前添加 #[.{fingerprint}] 进行指纹处理(.js):

<script src="js/scripts#[.{fingerprint}].js"></script>

若要对独立应用中的其他JS模块进行指纹识别,请使用Blazor WebAssembly应用的项目文件 (<StaticWebAssetFingerprintPattern>) 中的.csproj属性。

在以下示例中,将为应用中所有开发人员提供 .mjs 的文件添加指纹:

<StaticWebAssetFingerprintPattern Include="JSModule" Pattern="*.mjs" 
  Expression="#[.{fingerprint}]!" />

文件自动放置在导入映射中:

  • 自动适用于 Blazor Web App CSR。
  • 根据前面的说明在独立的 Blazor WebAssembly 应用中选择启用模块指纹识别时。

在解析 JavaScript 互操作的导入时,浏览器会使用导入映射来解析指纹文件。

在独立 Blazor WebAssembly 应用中设置环境

该文件 Properties/launchSettings.json 不再用于控制独立 Blazor WebAssembly 应用中的环境。

从 .NET 10 开始,在应用的项目文件(<WasmApplicationEnvironmentName>)中使用.csproj属性来设置环境。

以下示例将应用的环境设置为 Staging

<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>

默认环境为:

  • Development(用于生成)。
  • Production(用于发布)。

内联启动配置文件

Blazor的启动配置,在.NET 10发布之前存在于名为blazor.boot.json的文件中,现在已内联到dotnet.js脚本中。 这仅影响直接与blazor.boot.json文件交互的开发人员,例如开发人员:

目前,还没有为上述方法制定明确的替代策略。 如果需要上述任一策略,请使用任一文章底部的 “打开文档问题 ”链接来打开描述方案的新文档问题。

用于保存组件和服务状态的声明性模型

您现在可以通过声明方式指定状态,使其能够通过 [SupplyParameterFromPersistentComponentState] 特性从组件和服务中持久化。 在预呈现期间,具有此属性的属性会通过 PersistentComponentState 服务自动持久化。 当组件以交互方式呈现或实例化服务时,将检索状态。

在以前的 Blazor 版本中,使用 PersistentComponentState 服务预呈现期间保留组件状态涉及大量代码,如以下示例所示:

@page "/movies"
@implements IDisposable
@inject IMovieService MovieService
@inject PersistentComponentState ApplicationState

@if (MoviesList == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <QuickGrid Items="MoviesList.AsQueryable()">
        ...
    </QuickGrid>
}

@code {
    public List<Movie>? MoviesList { get; set; }
    private PersistingComponentStateSubscription? persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        if (!ApplicationState.TryTakeFromJson<List<Movie>>(nameof(MoviesList), 
            out var movies))
        {
            MoviesList = await MovieService.GetMoviesAsync();
        }
        else
        {
            MoviesList = movies;
        }

        persistingSubscription = ApplicationState.RegisterOnPersisting(() =>
        {
            ApplicationState.PersistAsJson(nameof(MoviesList), MoviesList);
            return Task.CompletedTask;
        });
    }

    public void Dispose() => persistingSubscription?.Dispose();
}

现在可以使用新的声明性模型简化此代码:

@page "/movies"
@inject IMovieService MovieService

@if (MoviesList == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <QuickGrid Items="MoviesList.AsQueryable()">
        ...
    </QuickGrid>
}

@code {
    [SupplyParameterFromPersistentComponentState]
    public List<Movie>? MoviesList { get; set; }

    protected override async Task OnInitializedAsync()
    {
        MoviesList ??= await MovieService.GetMoviesAsync();
    }
}

可以为同一类型的多个组件序列化状态,并且可以在服务中建立声明性状态,通过在 RegisterPersistentService 组件生成器(Razor)上以自定义服务类型和渲染模式调用 AddRazorComponents,以便在整个应用中使用。 有关详细信息,请参阅 Prerender ASP.NET Core Razor 组件

新的 JavaScript 互作功能

Blazor 添加了对以下 JS 互操作功能的支持:

以下异步方法在 IJSRuntimeIJSObjectReference 上可用,其作用范围行为与现有 IJSRuntime.InvokeAsync 方法相同:

  • InvokeNewAsync(string identifier, object?[]? args):异步调用指定的 JS 构造函数。 使用 new 运算符调用该函数。 在以下示例中,jsInterop.TestClass 是一个具有构造函数的类,而 classRef 则是一个 IJSObjectReference 的实例。

    var classRef = await JSRuntime.InvokeNewAsync("jsInterop.TestClass", "Blazor!");
    var text = await classRef.GetValueAsync<string>("text");
    var textLength = await classRef.InvokeAsync<int>("getTextLength");
    
  • GetValueAsync<TValue>(string identifier):异步读取指定 JS 属性的值。 该属性不能是 set仅限属性。 如果该属性不存在,则会抛出一个异常JSException。 以下示例从数据属性返回一个值:

    var valueFromDataPropertyAsync = await JSRuntime.GetValueAsync<int>(
      "jsInterop.testObject.num");
    
  • SetValueAsync<TValue>(string identifier, TValue value):异步更新指定 JS 属性的值。 该属性不能是 get仅限属性。 如果未在目标对象上定义该属性,则会创建该属性。 如果属性存在但不可写,或者无法向对象添加新属性,则会引发 A JSException 。 在以下示例中,如果 num 不存在,则在 testObject 上创建并赋值为 30。

    await JSRuntime.SetValueAsync("jsInterop.testObject.num", 30);
    

可用于每个上述方法的重载可以接受CancellationToken参数或TimeSpan超时参数。

以下同步方法可在 IJSInProcessRuntimeIJSInProcessObjectReference 上使用,其范围行为与现有的 IJSInProcessObjectReference.Invoke 方法相同:

  • InvokeNew(string identifier, object?[]? args):同步调用指定的 JS 构造函数。 使用 new 运算符调用该函数。 在以下示例中,jsInterop.TestClass 是一个具有构造函数的类,而 classRef 则是一个 IJSInProcessObjectReference 的实例。

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    var classRef = inProcRuntime.InvokeNew("jsInterop.TestClass", "Blazor!");
    var text = classRef.GetValue<string>("text");
    var textLength = classRef.Invoke<int>("getTextLength");
    
  • GetValue<TValue>(string identifier):同步读取指定 JS 属性的值。 该属性不能是 set仅限属性。 如果该属性不存在,则会抛出一个异常JSException。 以下示例从数据属性返回一个值:

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    var valueFromDataProperty = inProcRuntime.GetValue<int>(
      "jsInterop.testObject.num");
    
  • SetValue<TValue>(string identifier, TValue value):同步更新指定 JS 属性的值。 该属性不能是 get仅限属性。 如果未在目标对象上定义该属性,则会创建该属性。 如果属性存在但不可写,或者无法向对象添加新属性,则会引发 A JSException 。 如果 num 不存在,则在 testObject 创建其值为 20。

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    inProcRuntime.SetValue("jsInterop.testObject.num", 20);
    

有关详细信息,请参阅 .NET 方法文章中调用 JavaScript 函数 的以下部分:

Blazor WebAssembly 性能分析和诊断计数器

新的性能分析和诊断计数器可用于 Blazor WebAssembly 应用。 如需了解更多信息,请参阅以下文章:

预加载 Blazor 的框架静态资产

在 Blazor Web App 中,框架静态资源使用 Link 头信息 自动预加载,这允许浏览器在提取和呈现初始页面之前预加载资源。 在独立 Blazor WebAssembly 应用中,框架资产计划在浏览器 index.html 页面处理早期进行高优先级下载和缓存。

有关详细信息,请参阅 ASP.NET 核心 Blazor 静态文件

以前,在进行静态服务器端渲染(SSR)时,调用 NavigationManager.NavigateTo 会在转换为重定向响应前抛出 NavigationException,从而中断执行。 这在调试期间造成了混淆,并且与交互式呈现不一致,其中代码在 NavigateTo 之后继续正常执行。

在静态 SSR 期间调用 NavigationManager.NavigateTo 不再引发 NavigationException. 相反,它的行为与交互式呈现一致,通过执行导航而不抛出异常。

应更新依赖 NavigationException 抛出的代码。 例如,在默认 BlazorIdentity UI 中,IdentityRedirectManager 过去在调用 InvalidOperationException 之后抛出一个 RedirectTo ,以确保它不会在交互式呈现期间被调用。 现在应删除此异常和 [DoesNotReturn] 属性

要恢复到之前引发 NavigationException 的行为,请设置以下 AppContext 开关:

AppContext.SetSwitch(
    "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException", 
    isEnabled: true);

无法找到使用 NavigationManager 静态 SSR 和全局交互式渲染的响应

NavigationManager现在包含一个NotFound方法,用于处理在静态服务器端渲染(静态 SSR)或全局交互渲染期间找不到请求资源的情况:

  • 静态服务器端呈现(静态 SSR):调用 NotFound 会将 HTTP 状态代码设置为 404。
  • 流呈现:如果响应已启动,则引发异常。
  • 交互式呈现:向路由器(Blazor)发出Router信号以呈现“找不到”内容。

计划于 2025 年 6 月为预览版 5 提供每页/组件呈现支持。

NavigationManager.OnNotFound被调用时,可以使用NotFound事件进行通知。

有关详细信息和示例,请参阅 ASP.NET 核心 Blazor 路由和导航

Blazor路由器具有参数NotFoundPage

Blazor 现在提供了一种改进的方法,用于在导航到不存在的页面时显示“找不到”页面。 通过将页面类型作为参数传递给Router组件,您可以通过NotFoundPage参数在NavigationManager.NotFound时指定要呈现的页面。 建议使用这种方法而不是前面的 NotFound 片段,因为它支持路由,能够在代码重新执行的中间件中正常工作,甚至与非Blazor 方案也兼容。 如果同时定义了NotFound片段和NotFoundPage,则NotFoundPage指定的页面优先。

<Router AppAssembly="@typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>This content is ignored because NotFoundPage is defined.</NotFound>
</Router>

项目 Blazor 模板现在默认包含一个 NotFound.razor 页面。 每当在应用中调用NavigationManager.NotFound时,此页面都会自动呈现,从而更轻松地处理缺失路由,并提供一致的用户体验。

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

指标和跟踪

此版本为 Blazor 应用引入了全面的指标和跟踪功能,提供组件生命周期、导航、事件处理和线路管理的详细可观测性。

有关详细信息,请参阅 ASP.NET Core Blazor 性能最佳做法

Blazor Hybrid

本部分介绍 Blazor Hybrid的新功能。

新 .NET MAUIBlazor Hybrid,其中包含 Blazor Web App 和 ASP.NET Core Identity 文章和示例

已使用 ASP.NET Core .NET MAUI为 Blazor HybridIdentity 和 Web 应用添加了一个新文章和示例应用。

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

SignalR

本部分介绍 SignalR的新功能。

最小 API

本部分介绍最小 API 的新功能。

针对可为空的值类型将窗体提交中的空字符串视为 null

在最小 API 中将 [FromForm] 属性与复杂对象一起使用时,表单帖子中的空字符串值现在转换为 null,而不是导致分析失败。 这种行为与最小 API 中那些与复杂对象无关的表单提交的处理逻辑相匹配。

using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/todo", ([FromForm] Todo todo) => TypedResults.Ok(todo));

app.Run();

public class Todo
{
  public int Id { get; set; }
  public DateOnly? DueDate { get; set; } // Empty strings map to `null`
  public string Title { get; set; }
  public bool IsCompleted { get; set; }
}

感谢 @nvmkpk 参与此更改!

最小 API 中的验证支持

现已提供对最小 API 中的验证的支持。 此功能允许请求验证发送到 API 终结点的数据。 启用验证允许 ASP.NET Core 运行时对以下项执行定义的任何验证:

  • 查询
  • 标题
  • 请求主体

使用命名空间中的 DataAnnotations 属性定义验证。 开发人员通过以下方式自定义验证系统的行为:

如果验证失败,运行时将返回 400 错误的请求响应,其中包含验证错误的详细信息。

为最小 API 启用内置验证支持

通过调用 AddValidation 扩展方法在应用程序的服务容器中注册所需的服务,为最小 API 启用内置验证支持:

builder.Services.AddValidation();

此实现会自动发现在最小 API 处理程序中定义的类型,或是作为在最小 API 处理程序中所定义类型的基类型。 终结点筛选器对这些类型执行验证,并为每个终结点添加。

可以使用扩展方法为特定终结点 DisableValidation 禁用验证,如以下示例所示:

app.MapPost("/products",
    ([EvenNumber(ErrorMessage = "Product ID must be even")] int productId, [Required] string name)
        => TypedResults.Ok(productId))
    .DisableValidation();

注释

对适用于 .NET 10 的 ASP.NET Core 中引入的最小 API 验证生成器进行了一些小改进和修复。 为了支持将来的增强功能,基础验证解析程序 API 现在标记为实验性。 顶级 AddValidation API 和内置验证筛选器保持稳定和非实验性。

通过记录类型进行验证

最小 API 还支持使用 C# 记录类型进行验证。 可以使用命名空间中的 System.ComponentModel.DataAnnotations 属性(类似于类)验证记录类型。 例如:

public record Product(
    [Required] string Name,
    [Range(1, 1000)] int Quantity);

在最小 API 终结点中将记录类型用作参数时,验证属性将自动应用与类类型相同的方式:

app.MapPost("/products", (Product product) =>
{
    // Endpoint logic here
    return TypedResults.Ok(product);
});

支持服务器端事件 (SSE)

ASP.NET Core 现在支持使用 TypedResults.ServerSentEvents API 返回 ServerSentEvents 结果。 最小 API 和基于控制器的应用都支持此功能。

Server-Sent 事件是一种服务器推送技术,允许服务器通过单个 HTTP 连接将事件消息流发送到客户端。 在 .NET 中,事件消息表示为SseItem<T>对象,这些对象可能包含事件类型、ID 和数据有效负载。T

TypedResults 类具有一个名为 ServerSentEvents 的新静态方法,可用于返回ServerSentEvents结果。 此方法的第一个参数是 IAsyncEnumerable<SseItem<T>>,它表示要发送到客户端的事件消息流。

以下示例演示如何使用 TypedResults.ServerSentEvents API 将心率事件流作为 JSON 对象返回到客户端:

app.MapGet("/json-item", (CancellationToken cancellationToken) =>
{
    async IAsyncEnumerable<HeartRateRecord> GetHeartRate(
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            var heartRate = Random.Shared.Next(60, 100);
            yield return HeartRateRecord.Create(heartRate);
            await Task.Delay(2000, cancellationToken);
        }
    }

    return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken),
                                                  eventType: "heartRate");
});

有关详细信息,请参见:

  • MDN 上的服务器发送的事件
  • 使用 API 将心率事件流作为字符串TypedResults.ServerSentEvents和 JSON 对象返回给客户端ServerSentEvents
  • 控制器 API 示例应用 使用 TypedResults.ServerSentEvents API 将心率事件流作为字符串 ServerSentEvents返回,并将 JSON 对象返回到客户端。

OpenAPI

本部分介绍 OpenAPI 的新功能。

OpenAPI 3.1 支持

ASP.NET Core 添加了对在 .NET 10 中生成 OpenAPI 版本 3.1 文档的支持。 尽管只是次要版本更新,但 OpenAPI 3.1 是 OpenAPI 规范的一个重要更新,特别是它对 JSON 模式草案 2020-12的完全支持。

在生成的 OpenAPI 文档中看到的一些更改包括:

  • 可以为 null 的类型不再具有架构中的 nullable: true 属性。
  • 它们没有 nullable: true 属性,而是具有一个 type 关键字,其值是包含 null 作为类型之一的数组。
  • 现在,定义为 C# intlong 的属性或参数显示在生成的 OpenAPI 文档中,并且没有 type: integer 字段,但包含一个将取值限制为数字的 pattern 字段。 将 NumberHandling 中的 JsonSerializerOptions 属性设为 AllowReadingFromString(即,针对 ASP.NET Core Web 应用的默认值)时,会出现此情况。 若要在 OpenAPI 文档中表示 C# intlongtype: integer,请将 NumberHandling 属性设置为 Strict

使用此功能时,生成的文档的默认 OpenAPI 版本3.1。 通过在 AddOpenApi 的委托参数中显式设置 configureOptionsOpenApiVersion 属性,可以更改版本:

builder.Services.AddOpenApi(options =>
{
    options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});

在构建时生成 OpenAPI 文档时,可以通过在 MSBuild 项中设置--openapi-versionOpenApiGenerateDocumentsOptions来选择 OpenAPI 版本。

<PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
    <!-- Configure build-time OpenAPI generation to produce an OpenAPI 3.1 document. -->
    <OpenApiGenerateDocumentsOptions>--openapi-version OpenApi3_1</OpenApiGenerateDocumentsOptions>
</PropertyGroup>

OpenAPI 3.1 支持主要在以下 PR 中添加。

OpenAPI 3.1 重大更改

对 OpenAPI 3.1 的支持需要更新到新的主版本 2.0 的基础 OpenAPI.NET 库。 这个新版本与之前的版本相比有一些重大更改。 如果应用程序具有任何文档、操作或架构转换器,重大变更可能会影响该应用程序。 此迭代中的重大变更包括:

  • OpenAPI 文档中的实体(如操作和参数)类型化为接口。 实体的内联和引用变体存在具体的实现。 例如,IOpenApiSchema 可以是内联的 OpenApiSchema,也可以是指向文档中其他地方定义的架构的 OpenApiSchemaReference
  • Nullable 属性已从 OpenApiSchema 类型中删除。 若要确定类型是否为可空,请评估 OpenApiSchema.Type 属性是否设置 JsonSchemaType.Null

最重要的变化之一是,OpenApiAny 类已被删除,以支持直接使用 JsonNode。 使用 OpenApiAny 的转换器需要更新为使用 JsonNode。 以下差异显示架构转换器从 .NET 9 版本升级到 .NET 10 版本过程中的变化:

options.AddSchemaTransformer((schema, context, cancellationToken) =>
{
    if (context.JsonTypeInfo.Type == typeof(WeatherForecast))
    {
-       schema.Example = new OpenApiObject
+       schema.Example = new JsonObject
        {
-           ["date"] = new OpenApiString(DateTime.Now.AddDays(1).ToString("yyyy-MM-dd")),
+           ["date"] = DateTime.Now.AddDays(1).ToString("yyyy-MM-dd"),
-           ["temperatureC"] = new OpenApiInteger(0),
+           ["temperatureC"] = 0,
-           ["temperatureF"] = new OpenApiInteger(32),
+           ["temperatureF"] = 32,
-           ["summary"] = new OpenApiString("Bracing"),
+           ["summary"] = "Bracing",
        };
    }
    return Task.CompletedTask;
});

请注意,即使仅将 OpenAPI 版本配置为 3.0,这些更改也是必需的。

YAML 中的 OpenAPI

ASP.NET 现在支持以 YAML 格式提供生成的 OpenAPI 文档。 YAML 比 JSON 更简洁,当可以推断出大括号和引号时,它消除了这些。 YAML 还支持多行字符串,这对于长说明很有用。

若要将应用配置为以 YAML 格式提供生成的 OpenAPI 文档,请使用“.yaml”或“.yml”后缀在 MapOpenApi 调用中指定终结点,如以下示例所示:

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi("/openapi/{documentName}.yaml");
}

支持:

  • YAML 目前仅适用于由 OpenAPI 终结点提供的 OpenAPI。
  • 在未来的预览版中将增加在构建时以 YAML 格式生成 OpenAPI 文档的功能。

请参阅此 PR,它添加了对以 YAML 格式提供生成的 OpenAPI 文档的支持。

描述 API 控制器中 ProducesResponseType 的响应情况

ProducesAttributeProducesResponseTypeAttributeProducesDefaultResponseType 属性现在接受可选字符串参数 Description,该参数将设置响应的说明。 下面是一个示例:

[HttpGet(Name = "GetWeatherForecast")]
[ProducesResponseType<IEnumerable<WeatherForecast>>(StatusCodes.Status200OK,
                   Description = "The weather forecast for the next 5 days.")]
public IEnumerable<WeatherForecast> Get()
{

生成的 OpenAPI:

        "responses": {
          "200": {
            "description": "The weather forecast for the next 5 days.",
            "content": {

目前不支持ProducesResponseType

社区贡献Sander ten Brinke

将 XML 文档注释填充到 OpenAPI 文档中

ASP.NET Core OpenAPI 文档生成现在将包含来自 XML 文档注释的元数据,这些元数据涉及 OpenAPI 文档中的方法、类和成员定义。 必须在项目文件中启用 XML 文档注释才能使用此功能。 可以通过将以下属性添加到项目文件来执行此作:

  <PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

在生成时,OpenAPI 包将利用源生成器来发现当前应用程序程序集中的 XML 注释以及任何项目引用,并发出源代码,以通过 OpenAPI 文档转换器将其插入文档中。

请注意,C# 生成过程不会捕获在 lambda expresions 上放置的 XML 文档注释,因此若要使用 XML 文档注释将元数据添加到最小的 API 终结点,必须将终结点处理程序定义为方法,将 XML 文档注释放在方法上,然后从 MapXXX 方法引用该方法。 例如,若要使用 XML 文档注释将元数据添加到最初定义为 lambda 表达式的最小 API 终结点:

app.MapGet("/hello", (string name) =>$"Hello, {name}!");

更改 MapGet 调用以引用方法:

app.MapGet("/hello", Hello);

使用 XML 文档注释定义 Hello 方法:

static partial class Program
{
    /// <summary>
    /// Sends a greeting.
    /// </summary>
    /// <remarks>
    /// Greeting a person by their name.
    /// </remarks>
    /// <param name="name">The name of the person to greet.</param>
    /// <returns>A greeting.</returns>
    public static string Hello(string name)
    {
        return $"Hello, {name}!";
    }
}

在前面的示例中,Hello 方法将添加到 Program 类,但你可以将其添加到项目中的任何类。

上一个示例演示 <summary><remarks><param> XML 文档注释。 有关 XML 文档注释(包括所有受支持的标记)的详细信息,请参阅 C# 文档

由于核心功能是通过源生成器提供的,因此可以通过将以下 MSBuild 添加到项目文件来禁用该功能。

<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-preview.2.*" GeneratePathProperty="true" />
</ItemGroup>

<Target Name="DisableCompileTimeOpenApiXmlGenerator" BeforeTargets="CoreCompile">
  <ItemGroup>
    <Analyzer Remove="$(PkgMicrosoft_AspNetCore_OpenApi)/analyzers/dotnet/cs/Microsoft.AspNetCore.OpenApi.SourceGenerators.dll" />
  </ItemGroup>
</Target>

源生成器处理 AdditionalFiles 属性中包含的 XML 文件。 若要添加或删除,源将修改属性,如下所示:

<Target Name="AddXmlSources" BeforeTargets="CoreCompile">
  <ItemGroup>
    <AdditionalFiles Include="$(PkgSome_Package)/lib/net10.0/Some.Package.xml" />
  </ItemGroup>
</Target>

Microsoft.AspNetCore.OpenApi 添加到 ASP.NET Core Web API(本机 AOT)模板

ASP.NET 核心 Web API(本机 AOT)项目模板(短名称webapiaot)现在默认支持通过Microsoft.AspNetCore.OpenApi包生成 OpenAPI 文档。 创建新项目时,使用 --no-openapi 标志禁用此支持。

这是由@sander1095社区贡献的。 感谢这一贡献!

在DI容器中支持IOpenApiDocumentProvider。

ASP.NET Core 在 .NET 10 中支持依赖注入(DI)容器中的 IOpenApiDocumentProvider。 开发人员可以将 IOpenApiDocumentProvider 注入其应用中,并使用该组件来访问 OpenAPI 文档。 此方法可用于在 HTTP 请求上下文之外访问 OpenAPI 文档,例如在后台服务或自定义中间件中。

以前,可以通过 HostFactoryResolver 和 no-op IServer 实现来运行应用程序启动逻辑,而不启动 HTTP 服务器。 新功能通过提供受 Aspire 启发的 IDistributedApplicationPublisher简化 API 来简化此过程,该 API 是 Aspire 的分布式应用程序托管和发布框架的一部分。

有关详细信息,请参阅 dotnet/aspnetcore #61463

对 XML 注释生成器的改进

XML 注释生成比早期版本的 .NET 更好地处理 .NET 10 中的复杂类型。

  • 它为更广泛的类型生成准确的完整 XML 注释。
  • 它处理更复杂的方案。
  • 它能够优雅地跳过复杂类型的处理,这些复杂类型在早期版本中会导致构建错误。

这些改进将某些方案的失败模式从生成错误更改为缺少元数据。

此外,现在可以将 XML 文档注释处理配置为访问其他程序集中的 XML 注释。 这对于为当前程序集外部定义的类型(如 ProblemDetails 命名空间中的 Microsoft.AspNetCore.Http 类型)生成文档非常有用。

此配置是使用项目生成文件中的指令完成的。 以下示例演示如何配置 XML 注释生成器以访问程序集中 Microsoft.AspNetCore.Http 类型的 XML 注释,其中包括该 ProblemDetails 类。

<Target Name="AddOpenApiDependencies" AfterTargets="ResolveReferences">
  <ItemGroup>
  <!-- Include XML documentation from Microsoft.AspNetCore.Http.Abstractions
    to get metadata for ProblemDetails -->
    <AdditionalFiles
          Include="@(ReferencePath->'
            %(RootDir)%(Directory)%(Filename).xml')"
          Condition="'%(ReferencePath.Filename)' ==
           'Microsoft.AspNetCore.Http.Abstractions'"
          KeepMetadata="Identity;HintPath" />
  </ItemGroup>
</Target>

我们期望在未来的预览版中在共享框架中包含来自一组选定程序集的 XML 注释,以避免在大多数情况下需要进行此配置。

支持在转换器中生成 OpenApiSchemas

开发人员现在可以使用与 ASP.NET Core OpenAPI 文档生成相同的逻辑为 C# 类型生成架构,并将其添加到 OpenAPI 文档。 然后,可以从 OpenAPI 文档中的其他地方引用架构。

传递给文档、操作和架构转换器的上下文中包括一个可以用于为类型生成架构的新 GetOrCreateSchemaAsync 方法。 此方法还有一个可选 ApiParameterDescription 参数,用于为生成的架构指定其他元数据。

为了支持将架构添加到 OpenAPI 文档,已在操作和架构转换器上下文中增加了一个 Document 属性。 这允许任何转换器使用文档 AddComponent 的方法将架构添加到 OpenAPI 文档。

示例:

若要在文档、作或架构转换器中使用此功能,请使用 GetOrCreateSchemaAsync 上下文中提供的方法创建架构,并使用文档 AddComponent 的方法将其添加到 OpenAPI 文档中。

builder.Services.AddOpenApi(options =>
{
    options.AddOperationTransformer(async (operation, context, cancellationToken) =>
    {
        // Generate schema for error responses
        var errorSchema = await context.GetOrCreateSchemaAsync(typeof(ProblemDetails), null, cancellationToken);
        context.Document?.AddComponent("Error", errorSchema);

        operation.Responses ??= new OpenApiResponses();
        // Add a "4XX" response to the operation with the newly created schema
        operation.Responses["4XX"] = new OpenApiResponse
        {
            Description = "Bad Request",
            Content = new Dictionary<string, OpenApiMediaType>
            {
                ["application/problem+json"] = new OpenApiMediaType
                {
                    Schema = new OpenApiSchemaReference("Error", context.Document)
                }
            }
        };
    });
});

OpenAPI.NET 已更新为 Preview.18

ASP.NET Core OpenAPI 文档生成中使用的 OpenAPI.NET 库已升级到 v2.0.0-preview18。 v2.0.0-preview18 版本可提高与更新库版本的兼容性。

以前的 v2.0.0-preview17 版本包括许多错误修复和改进,还引入了一些重大变更。 中断性变更应仅影响使用文档、操作或架构转换器的用户。 此版本中可能影响开发人员的重大更改包括:

身份验证和授权

本部分介绍身份验证和授权的新功能。

身份验证和授权指标

为 ASP.NET Core 中的某些身份验证和授权事件添加了指标。 通过此更改,现在可以获取以下事件的指标:

  • 认证:
    • 经过身份验证的请求持续时间
    • 质询计数
    • 禁止计数
    • 登录次数
    • 注销计数
  • 授权:
    • 需要授权的请求计数

下图显示了 Aspire 仪表板中经过身份验证的请求持续时间指标的示例:

Aspire 仪表板中经过身份验证的请求持续时间

有关详细信息,请参阅 ASP.NET 核心授权和身份验证指标

杂项

本部分介绍 .NET 10 中的其他新功能。

HTTP.sys 的可自定义安全描述符

现在可以为 HTTP.sys 请求队列指定自定义安全描述符。 新的 RequestQueueSecurityDescriptor 属性支持 HttpSysOptions 对请求队列的访问权限进行更精细的控制。 这种精细控制使你可以根据应用程序的需求定制安全性。

使用新属性可以执行哪些操作

HTTP.sys 中的 请求队列 是一种内核级结构,可以临时存储传入的 HTTP 请求,直到应用程序准备好处理这些请求。 通过自定义安全描述符,可以允许或拒绝对请求队列的特定用户或组的访问权限。 在希望在操作系统级别限制或委托 HTTP.sys 请求处理的情况下,这非常有用。

如何使用新属性

仅当创建新请求队列时,该 RequestQueueSecurityDescriptor 属性才适用。 该属性不会影响现有请求队列。 若要使用此属性,请在配置 HTTP.sys 服务器时将其设置为 GenericSecurityDescriptor 实例。

例如,以下代码允许所有经过身份验证的用户,但拒绝来宾:

using System.Security.AccessControl;
using System.Security.Principal;
using Microsoft.AspNetCore.Server.HttpSys;

// Create a new security descriptor
var securityDescriptor = new CommonSecurityDescriptor(isContainer: false, isDS: false, sddlForm: string.Empty);

// Create a discretionary access control list (DACL)
var dacl = new DiscretionaryAcl(isContainer: false, isDS: false, capacity: 2);
dacl.AddAccess(
    AccessControlType.Allow,
    new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
    -1,
    InheritanceFlags.None,
    PropagationFlags.None
);
dacl.AddAccess(
    AccessControlType.Deny,
    new SecurityIdentifier(WellKnownSidType.BuiltinGuestsSid, null),
    -1,
    InheritanceFlags.None,
    PropagationFlags.None
);

// Assign the DACL to the security descriptor
securityDescriptor.DiscretionaryAcl = dacl;

// Configure HTTP.sys options
var builder = WebApplication.CreateBuilder();
builder.WebHost.UseHttpSys(options =>
{
    options.RequestQueueSecurityDescriptor = securityDescriptor;
});

有关详细信息,请参阅 ASP.NET Core 中的HTTP.sys Web 服务器实现

提供对使用顶层语句测试应用的更好支持

.NET 10 现在更好地支持使用 顶级语句的测试应用。 以前,开发人员必须手动将 public partial class Program 添加到 Program.cs 文件,以便测试项目可以引用 Program classpublic partial class Program 是必需的,因为 C# 9 中的顶级语句特性功能了一个 Program class,它被声明为内部

在 .NET 10 中,源生成器 用于生成 public partial class Program 声明(如果程序员未显式声明)。 此外,还添加了一个分析器来检测何时显式声明 public partial class Program,并建议开发人员将其删除。

图像

以下 PR 为此功能做出了贡献:

使用 System.Text.Json 实现新的 JSON 补丁

JSON 补丁

  • 描述要应用于 JSON 文档的更改的标准格式。
  • 在 RFC 6902 中定义,在 RESTful API 中广泛使用,以对 JSON 资源执行部分更新。
  • 表示可用于修改 JSON 文档的作序列(例如,添加、删除、替换、移动、复制、测试)。

在web应用中,JSON Patch通常用于PATCH操作以执行资源的部分更新。 与其发送整个资源进行更新,客户端可以发送仅包含更改的 JSON Patch 文档。 修补可减少有效负载大小并提高效率。

此版本引入了新的Microsoft.AspNetCore.JsonPatch实现,基于System.Text.Json序列化。 此功能:

  • 利用 System.Text.Json 这个针对 .NET 进行优化的库,符合现代 .NET 的实践。
  • 与基于旧 Newtonsoft.Json版的实现相比,提供改进的性能和减少的内存使用量。

以下基准将新 System.Text.Json 实现的性能与旧 Newtonsoft.Json 实现进行比较。

情景 执行 平均值 分配的内存
应用程序基准 Newtonsoft.JsonPatch 271.924μs 25 KB
System.Text.JsonPatch 1.584μs 3 KB
反序列化基准 Newtonsoft.JsonPatch 19.261μs 43 KB
System.Text.JsonPatch 7.917μs 7 KB

这些基准测试突出显示了显著的性能提升,并减少了新实现的内存使用量。

注释:

  • 新实现不是旧实现的直接替换。 具体而言,新实现不支持动态类型,例如 ExpandoObject
  • JSON 修补程序标准具有 固有的安全风险。 由于这些风险固有于 JSON 修补程序标准,因此新实现 不会尝试降低固有的安全风险。 开发人员有责任确保 JSON 修补程序文档可以安全地应用于目标对象。 有关详细信息,请参阅“ 缓解安全风险 ”部分。

用法

要启用 JSON Patch 支持,请安装 System.Text.JsonMicrosoft.AspNetCore.JsonPatch.SystemTextJson NuGet 包。

dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease

此包提供一个 JsonPatchDocument<T> 类,用于表示类型 T 对象的 JSON Patch 文档,并使用 System.Text.Json 提供用于序列化和反序列化 JSON Patch 文档的自定义逻辑。 类JsonPatchDocument<T>的关键方法是ApplyTo,它将修补操作应用于类型T的目标对象。

以下示例演示如何使用 ApplyTo 该方法将 JSON 修补程序文档应用于对象。

示例:应用 JsonPatchDocument

下面的示例展示了如何:

  1. addreplaceremove操作。
  2. 对嵌套属性的操作。
  3. 向数组添加新项。
  4. 在 JSON 修补程序文档中使用 JSON 字符串枚举转换器。
// Original object
var person = new Person {
  FirstName = "John",
  LastName = "Doe",
  Email = "johndoe@gmail.com",
  PhoneNumbers = [new() {Number = "123-456-7890", Type = PhoneNumberType.Mobile}],
  Address = new Address
  {
    Street = "123 Main St",
    City = "Anytown",
    State = "TX"
  }
};

// Raw JSON Patch document
var jsonPatch = """
[
  { "op": "replace", "path": "/FirstName", "value": "Jane" },
  { "op": "remove", "path": "/Email"},
  { "op": "add", "path": "/Address/ZipCode", "value": "90210" },
  {
    "op": "add",
    "path": "/PhoneNumbers/-",
    "value": { "Number": "987-654-3210", "Type": "Work" }
  }
]
""";

// Deserialize the JSON Patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);

// Apply the JSON Patch document
patchDoc!.ApplyTo(person);

// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));

// Output:
// {
//   "firstName": "Jane",
//   "lastName": "Doe",
//   "address": {
//     "street": "123 Main St",
//     "city": "Anytown",
//     "state": "TX",
//     "zipCode": "90210"
//   },
//   "phoneNumbers": [
//     {
//       "number": "123-456-7890",
//       "type": "Mobile"
//     },
//     {
//       "number": "987-654-3210",
//       "type": "Work"
//     }
//   ]
// }

该方法通常遵循ApplyTo的约定和选项来处理System.Text.Json,包括以下选项控制的行为:

  • NumberHandling:是否从字符串中读取数值属性。
  • PropertyNameCaseInsensitive:属性名称是否区分大小写。

System.Text.Json和新JsonPatchDocument<T>实现之间的主要区别:

  • 目标对象的运行时类型,而不是声明的类型,决定了哪些属性被修改 ApplyTo
  • System.Text.Json 反序列化依赖于声明的类型来标识符合条件的属性。

示例:应用带有错误处理的 JsonPatchDocument

应用 JSON 修补程序文档时可能会出现各种错误。 例如,目标对象可能没有指定的属性,或者指定的值可能与属性类型不兼容。

JSON 补丁还支持该 test 操作。 该 test 作检查指定的值是否等于目标属性,如果不是,则返回错误。

以下示例演示如何正常处理这些错误。

重要

传递给 ApplyTo 方法的对象被就地修改。 如果任何操作失败,调用方有责任放弃这些更改。

// Original object
var person = new Person {
  FirstName = "John",
  LastName = "Doe",
  Email = "johndoe@gmail.com"
};

// Raw JSON Patch document
var jsonPatch = """
[
  { "op": "replace", "path": "/Email", "value": "janedoe@gmail.com"},
  { "op": "test", "path": "/FirstName", "value": "Jane" },
  { "op": "replace", "path": "/LastName", "value": "Smith" }
]
""";

// Deserialize the JSON Patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);

// Apply the JSON Patch document, catching any errors
Dictionary<string, string[]>? errors = null;
patchDoc!.ApplyTo(person, jsonPatchError =>
    {
        errors ??= new ();
        var key = jsonPatchError.AffectedObject.GetType().Name;
        if (!errors.ContainsKey(key))
        {
            errors.Add(key, new string[] { });
        }
        errors[key] = errors[key].Append(jsonPatchError.ErrorMessage).ToArray();
    });
if (errors != null)
{
    // Print the errors
    foreach (var error in errors)
    {
        Console.WriteLine($"Error in {error.Key}: {string.Join(", ", error.Value)}");
    }
}

// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));

// Output:
// Error in Person: The current value 'John' at path 'FirstName' is not equal 
// to the test value 'Jane'.
// {
//   "firstName": "John",
//   "lastName": "Smith",              <<< Modified!
//   "email": "janedoe@gmail.com",     <<< Modified!
//   "phoneNumbers": []
// }

减轻安全风险

使用 Microsoft.AspNetCore.JsonPatch.SystemTextJson 包时,了解和缓解潜在的安全风险至关重要。 以下部分概述了与 JSON 修补程序关联的已识别的安全风险,并提供建议的缓解措施,以确保包的安全使用。

重要

这不是威胁的详尽列表。 应用开发人员必须进行自己的威胁模型评审,以确定特定于应用的综合列表,并根据需要提出适当的缓解措施。 例如,向修补操作公开集合的应用程序,应考虑到如果这些操作在集合开头插入或删除元素,可能会引发算法复杂性攻击。

通过运行自己的应用的综合威胁模型并解决已识别的威胁,同时遵循以下建议的缓解措施,这些包的使用者可以将 JSON 修补程序功能集成到其应用中,同时最大程度地降低安全风险。

这些包的使用者可以将 JSON 修补程序功能集成到其应用中,同时最大程度地降低安全风险,包括:

  • 为自己的应用运行全面的威胁模型。
  • 解决已识别的威胁。
  • 请遵循以下部分中的建议缓解措施进行操作。
通过内存放大拒绝服务 (DoS)
  • 场景:恶意客户端提交了一个 copy 操作,该操作多次重复复制大型对象图,导致内存过度消耗。
  • 影响:潜在的Of-Memory(内存不足条件),导致服务中断。
  • 缓解措施
    • 在调用 ApplyTo之前验证传入的 JSON 补丁文档的大小和结构。
    • 验证必须特定于应用,但示例验证可能类似于以下内容:
public void Validate(JsonPatchDocument<T> patch)
{
    // This is just an example. It's up to the developer to make sure that
    // this case is handled properly, based on the app's requirements.
    if (patch.Operations.Where(op=>op.OperationType == OperationType.Copy).Count()
        > MaxCopyOperationsCount)
    {
        throw new InvalidOperationException();
    }
}
业务逻辑颠覆
  • 场景:补丁操作可以操纵具有隐式不变量的字段(例如,内部标志、ID 或计算字段),从而违反业务约束。
  • 影响:数据完整性问题和意外的应用行为。
  • 缓解措施
    • 将 POCO 对象与显式定义的属性一起使用,这些属性可以安全地进行修改。
    • 避免在目标对象中公开敏感或安全关键属性。
    • 如果未使用 POCO 对象,请在应用操作后验证已修补对象,以确保不违反业务规则和不可变条件。
身份验证和授权
  • 场景:未经身份验证或未经授权的客户端发送恶意 JSON 修补请求。
  • 影响:未经授权的访问以修改敏感数据或中断应用行为。
  • 缓解措施
    • 使用适当的身份验证和授权机制保护接受 JSON 修补请求的终结点。
    • 限制对具有适当权限的受信任客户端或用户的访问权限。

使用 RedirectHttpResult.IsLocalUrl 检测 URL 是否为本地

使用新的 RedirectHttpResult.IsLocalUrl(url) 辅助方法检测 URL 是否为本地。 如果符合以下条件,则该 URL 被视为本地 URL:

使用 虚拟路径"~/" 的 URL 也是本地的。

IsLocalUrl 用于在重定向到 URL 之前验证 URL,以防止打开重定向攻击

if (RedirectHttpResult.IsLocalUrl(url))
{
    return Results.LocalRedirect(url);
}

感谢 @martincostello 的贡献!

ASP.NET Core 中的HTTP.sys Web 服务器实现