Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo destaca as alterações mais significativas no ASP.NET Core no .NET 10 com links para documentação relevante.
Este artigo será atualizado à medida que novas versões prévias forem disponibilizadas. Para alterações significativas, consulte Alterações significativas no .NET.
Blazor
Esta seção descreve os novos recursos do Blazor.
Exemplos de segurança novos e atualizados Blazor Web App
Adicionamos e atualizamos os Blazor Web App exemplos de segurança vinculados nos seguintes artigos:
- Garantir a segurança de um ASP.NET Core com OpenID Connect (OIDC)Blazor Web App
- Proteger um ASP.NET Core Blazor Web App com o Microsoft Entra ID
- Proteger um ASP.NET Core Blazor Web App com a Autenticação do Windows
Todas as nossas soluções de exemplo OIDC e Entra agora incluem um projeto de API Web separado (MinimalApiJwt
) para demonstrar como configurar e chamar uma API Web externa com segurança. O uso de APIs Web é demonstrado com um manipulador de token e um cliente HTTP nomeado para o provedor de identidade OIDC ou para pacotes/API Web da Microsoft Entra ID.
As soluções de exemplo são configuradas no código C# nos arquivos Program
. Para configurar as soluções a partir de arquivos de configurações de aplicativo (por exemplo, appsettings.json
), consulte a seção nova sobre a configuração de fornecimento utilizando o provedor de configuração JSON (configurações de aplicativo) nos artigos OIDC ou Entra.
Nossos exemplos do Entra também incluem novas diretrizes sobre como usar um cache de token distribuído criptografado para cenários de hospedagem de farm da Web.
parâmetro QuickGridRowClass
Aplique uma classe de estilo a uma linha da grade com base no item da linha usando o novo parâmetro RowClass
. No exemplo a seguir, o método GetRowCssClass
é chamado em cada linha para aplicar condicionalmente uma classe de folha de estilos com base no item de linha:
<QuickGrid ... RowClass="GetRowCssClass">
...
</QuickGrid>
@code {
private string GetRowCssClass(MyGridItem item) =>
item.IsArchived ? "row-archived" : null;
}
Para obter mais informações, consulte o componente `Blazor` do ASP.NET CoreQuickGrid.
Blazor script como recurso web estático
Em versões anteriores do .NET, o script Blazor é fornecido de um recurso inserido na estrutura compartilhada do ASP.NET Core. No .NET 10 ou posterior, o script Blazor é servido como um ativo da Web estático com compactação automática e impressão digital.
Para obter mais informações, consulte os seguintes recursos:
Destaques do esquema de rota
O [Route]
atributo agora dá suporte ao realce de sintaxe de rota para ajudar a visualizar a estrutura do modelo de rota:
NavigateTo
não rola mais para a parte superior para navegação na mesma página
Anteriormente, NavigationManager.NavigateTo rolava até a parte superior da página para navegação na mesma página. Esse comportamento foi alterado no .NET 10 para que o navegador não role mais para a parte superior da página ao navegar para a mesma página. Isso significa que o viewport não é mais redefinido ao fazer atualizações no endereço da página atual, como ao alterar a string de consulta ou o fragmento.
Componente de IU de reconexão adicionado ao modelo de projeto Blazor Web App
O modelo de projeto Blazor Web App agora inclui um componente ReconnectModal
, incluindo arquivos JavaScript e folhas de estilos agrupados, para melhorar o controle do desenvolvedor sobre a IU de reconexão quando o cliente perde a conexão WebSocket com o servidor. O componente não insere estilos programaticamente, garantindo a conformidade com configurações de CSP (Política de Segurança de Conteúdo) mais rigorosas para a política de style-src
. Em versões anteriores, a interface do usuário de reconexão padrão foi criada pela estrutura de uma maneira que poderia causar violações de CSP. Observe que a interface do usuário de reconexão padrão ainda é usada como alternativa quando o aplicativo não define a interface do usuário de reconexão, como ao usar o componente ReconnectModal
do modelo de projeto ou um componente personalizado semelhante.
Novos recursos de IU de reconexão:
- Além de indicar o estado de reconexão definindo uma classe CSS específica no elemento de interface de reconexão, o novo evento
components-reconnect-state-changed
é disparado para alterações de estado de reconexão. - O código pode diferenciar melhor os estágios do processo de reconexão com o novo estado de reconexão "
retrying
", indicado pela classe CSS e pelo novo evento.
Para obter mais informações, consulte as diretrizes do ASP.NET CoreBlazorSignalR.
Ignorar a cadeia de caracteres e o fragmento de consulta ao usar NavLinkMatch.All
O componente NavLink
agora ignora a cadeia de caracteres de consulta e o fragmento ao usar o valor NavLinkMatch.All
para o parâmetro Match
. Isso significa que o link mantém a classe active
se o caminho da URL corresponder, mas a cadeia de caracteres de consulta ou o fragmento mudarem. Para reverter para o comportamento original, use o conjunto de Microsoft.AspNetCore.Components.Routing.NavLink.EnableMatchAllForQueryStringAndFragment
AppContext
opções como true
.
Você também pode substituir o método ShouldMatch
em NavLink
para personalizar o comportamento correspondente:
public class CustomNavLink : NavLink
{
protected override bool ShouldMatch(string currentUriAbsolute)
{
// Custom matching logic
}
}
Para obter mais informações, consulte ASP.NET Core roteamento e navegaçãoBlazor.
Fechar opções de coluna QuickGrid
Agora você pode fechar a UI das opções da coluna QuickGrid
usando o novo método HideColumnOptionsAsync
.
O exemplo a seguir usa o método HideColumnOptionsAsync
para fechar a interface do usuário das opções de coluna assim que o filtro de título é aplicado:
<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));
}
O streaming de resposta é de adesão opcional e como optar por não participar
Em versões anteriores de Blazor, o streaming de resposta para solicitações HttpClient era opcional. Agora, o streaming de resposta está habilitado por padrão.
Essa é uma alteração significativa porque a chamada de HttpContent.ReadAsStreamAsync para um HttpResponseMessage.Content (response.Content.ReadAsStreamAsync()
) retorna um BrowserHttpReadStream
e não mais um MemoryStream.
BrowserHttpReadStream
não dá suporte a operações síncronas, como Stream.Read(Span<Byte>)
. Se o código usar operações síncronas, você poderá optar por não usar o streaming de resposta ou copiar o Stream para um MemoryStream você mesmo.
Para recusar o streaming de resposta globalmente, use uma das seguintes abordagens:
Adicione a
<WasmEnableStreamingResponse>
propriedade ao arquivo de projeto com um valor defalse
:<WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>
Defina a variável de
DOTNET_WASM_ENABLE_STREAMING_RESPONSE
ambiente comofalse
ou0
.
Para desativar o streaming de resposta para uma solicitação individual, defina SetBrowserResponseStreamingEnabled como false
no HttpRequestMessage (requestMessage
no exemplo a seguir).
requestMessage.SetBrowserResponseStreamingEnabled(false);
Para obter mais informações, consulte HttpClient
e HttpRequestMessage
com as opções de solicitação da Fetch API (artigo 'Chamar API Web').
Impressão digital do lado do cliente
No ano passado, o lançamento do .NET 9 introduziu a impressão digital do lado do servidor de ativos estáticos em Blazor Web Apps com a introdução das convenções de ponto de extremidade de roteamento de Ativos Estáticos do Mapa (MapStaticAssets
), o ImportMap
componente e a ComponentBase.Assets propriedade (@Assets["..."]
) para resolver módulos JavaScript com impressão digital. Para o .NET 10, você pode optar por ativar a impressão digital feita no lado do cliente de módulos JavaScript para aplicativos independentes Blazor WebAssembly.
Em aplicativos Blazor WebAssembly autônomos, durante a compilação/publicação, a estrutura substitui espaços reservados em index.html
por valores calculados durante a compilação para ativos estáticos de impressão digital. Uma impressão digital é colocada no nome do arquivo de script blazor.webassembly.js
.
A marcação a seguir deve estar presente no arquivo wwwwoot/index.html
para adotar o recurso de impressão digital.
<head>
...
+ <script type="importmap"></script>
</head>
<body>
...
- <script src="_framework/blazor.webassembly.js"></script>
+ <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>
</html>
No arquivo de projeto (.csproj
), adicione a propriedade <OverrideHtmlAssetPlaceholders>
definida como true
:
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
+ <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
</PropertyGroup>
</Project>
Qualquer script em index.html
com o marcador de impressão digital é identificado pela estrutura. Por exemplo, um arquivo de script nomeado scripts.js
na pasta wwwroot/js
do aplicativo é registrado ao se adicionar #[.{fingerprint}]
antes da extensão do arquivo (.js
).
<script src="js/scripts#[.{fingerprint}].js"></script>
Para identificar módulos adicionais JS em aplicativos independentes Blazor WebAssembly, use a propriedade <StaticWebAssetFingerprintPattern>
no arquivo de projeto do aplicativo (.csproj
).
No exemplo a seguir, uma impressão digital é adicionada para todos os arquivos fornecidos pelo .mjs
desenvolvedor no aplicativo:
<StaticWebAssetFingerprintPattern Include="JSModule" Pattern="*.mjs"
Expression="#[.{fingerprint}]!" />
Os arquivos são colocados automaticamente no mapa de importação:
- Automaticamente para CSR Blazor Web App.
- Ao optar pela impressão digital do módulo em aplicativos autônomos Blazor WebAssembly de acordo com as instruções anteriores.
Ao resolver a importação para a interoperabilidade do JavaScript, o mapa de importação é usado pelo navegador para resolver arquivos com impressão digital.
Definir o ambiente em aplicativos autônomos Blazor WebAssembly
O Properties/launchSettings.json
arquivo não é mais usado para controlar o ambiente em aplicativos autônomos Blazor WebAssembly .
A partir do .NET 10, defina o ambiente com a <WasmApplicationEnvironmentName>
propriedade no arquivo de projeto do aplicativo (.csproj
).
O exemplo a seguir define o ambiente do aplicativo como Staging
:
<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>
Os ambientes padrão são:
-
Development
para compilação. -
Production
para publicação.
Arquivo de configuração de boot integrado
A configuração de inicialização do Blazor, que antes do lançamento do .NET 10 existia em um arquivo chamado blazor.boot.json
, foi embutida no script do dotnet.js
. Isso afeta apenas os desenvolvedores que estão interagindo diretamente com o blazor.boot.json
arquivo, como quando os desenvolvedores estão:
- Verificando a integridade do arquivo para ativos publicados com o script do PowerShell de integridade de solução de problemas de acordo com as diretrizes em ASP.NET cache do pacote .NET principal Blazor WebAssembly e falhas de verificação de integridade.
- Alterando a extensão do nome de arquivo dos arquivos DLL ao não usar o formato de arquivo webcil padrão, conforme a orientação em Hospedar e implantar ASP.NET Core Blazor WebAssembly.
Atualmente, não há nenhuma estratégia de substituição documentada para as abordagens anteriores. Se você precisar de uma das estratégias anteriores, abra um novo problema de documentação descrevendo seu cenário usando o link Abrir um problema de documentação na parte inferior de qualquer artigo.
Modelo declarativo para manter o estado de componentes e serviços
Agora você pode especificar declarativamente o estado para persistir em componentes e serviços usando o atributo [SupplyParameterFromPersistentComponentState]
. As propriedades com esse atributo são mantidas automaticamente usando o PersistentComponentState serviço durante a pré-geração. O estado é recuperado quando o componente é renderizado interativamente ou o serviço é instanciado.
Nas versões anteriores Blazor, manter o estado do componente durante a pré-geração usando o serviço PersistentComponentState envolvia uma quantidade significativa de código, como demonstra o exemplo a seguir:
@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();
}
Esse código agora pode ser simplificado usando o novo modelo declarativo:
@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();
}
}
O estado pode ser serializado para vários componentes do mesmo tipo e você pode estabelecer um estado declarativo em um serviço para uso no aplicativo invocando RegisterPersistentService
no Razor construtor de componentes (AddRazorComponents) com um tipo de serviço personalizado e modo de renderização. Para obter mais informações, consulte Os componentes do Prerender ASP.NET CoreRazor.
Novos recursos de interoperabilidade do JavaScript
Blazor adiciona suporte para os seguintes JS recursos de interoperabilidade:
- Crie uma instância de um JS objeto usando uma função de construtor e obtenha o IJSObjectReference/IJSInProcessObjectReference identificador .NET para referenciar a instância.
- Leia ou modifique o valor de uma propriedade de objeto JS, incluindo propriedades de dados e de acesso.
Os seguintes métodos assíncronos estão disponíveis no IJSRuntime e IJSObjectReference com o mesmo comportamento de escopo que o método existente IJSRuntime.InvokeAsync:
InvokeNewAsync(string identifier, object?[]? args)
: invoca a função de construtor especificada JS de forma assíncrona. A função é invocada com onew
operador. No exemplo a seguir,jsInterop.TestClass
é uma classe com uma função de construtor eclassRef
é um 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)
: lê o valor da propriedade especificada JS de forma assíncrona. A propriedade não pode ser somente umaset
propriedade. Um JSException será gerado se a propriedade não existir. O exemplo a seguir retorna um valor de uma propriedade de dados:var valueFromDataPropertyAsync = await JSRuntime.GetValueAsync<int>( "jsInterop.testObject.num");
SetValueAsync<TValue>(string identifier, TValue value)
: atualiza o valor da propriedade especificada JS de forma assíncrona. A propriedade não pode ser somente umaget
propriedade. Se a propriedade não estiver definida no objeto de destino, a propriedade será criada. Um JSException será gerado se a propriedade existir, mas não for gravável ou quando uma nova propriedade não puder ser adicionada ao objeto. No exemplo a seguir,num
será criado emtestObject
com um valor de 30 se ele não existir.await JSRuntime.SetValueAsync("jsInterop.testObject.num", 30);
Sobrecargas estão disponíveis para cada um dos métodos mencionados, que aceitam um argumento CancellationToken ou um argumento de tempo limite TimeSpan.
Os seguintes métodos síncronos estão disponíveis em IJSInProcessRuntime e IJSInProcessObjectReference com o mesmo comportamento de escopo que o método existente IJSInProcessObjectReference.Invoke:
InvokeNew(string identifier, object?[]? args)
: invoca a função de construtor especificada JS de forma síncrona. A função é invocada com onew
operador. No exemplo a seguir,jsInterop.TestClass
é uma classe com uma função de construtor eclassRef
é um 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)
: lê o valor da propriedade especificada JS de forma síncrona. A propriedade não pode ser somente umaset
propriedade. Um JSException será gerado se a propriedade não existir. O exemplo a seguir retorna um valor de uma propriedade de dados:var inProcRuntime = ((IJSInProcessRuntime)JSRuntime); var valueFromDataProperty = inProcRuntime.GetValue<int>( "jsInterop.testObject.num");
SetValue<TValue>(string identifier, TValue value)
: atualiza o valor da propriedade especificada JS de forma síncrona. A propriedade não pode ser somente umaget
propriedade. Se a propriedade não estiver definida no objeto de destino, a propriedade será criada. Um JSException será gerado se a propriedade existir, mas não for gravável ou quando uma nova propriedade não puder ser adicionada ao objeto. No exemplo a seguir,num
será criado emtestObject
com um valor de 20 se ele não existir.var inProcRuntime = ((IJSInProcessRuntime)JSRuntime); inProcRuntime.SetValue("jsInterop.testObject.num", 20);
Para obter mais informações, consulte as seções a seguir do artigo Chamar funções JavaScript de métodos .NET:
- Criar uma instância de um JS objeto usando uma função de construtor
- Ler ou modificar o valor de uma JS propriedade de objeto
Blazor WebAssembly perfilamento de desempenho e contadores de diagnóstico
Novos contadores de perfil de desempenho e diagnóstico estão disponíveis para Blazor WebAssembly aplicativos. Para obter mais informações, consulte os seguintes artigos:
- diagnóstico das ferramentas de desenvolvedor do navegador ASP.NET Core Blazor WebAssembly
- diagnósticos do Event Pipe do ASP.NET CoreBlazor WebAssembly
Ativos estáticos do framework pré-carregados Blazor
Em Blazor Web Apps, os ativos estáticos da estrutura são automaticamente pré-carregados usando Link
cabeçalhos, o que permite ao navegador pré-carregar recursos antes que a página inicial seja buscada e renderizada. Em aplicativos independentes Blazor WebAssembly, os ativos do framework são agendados para download e cache com alta prioridade no início do processamento de páginas no navegador index.html
.
Saiba mais em Arquivos estáticos de Blazor no ASP.NET Core.
NavigationManager.NavigateTo
não lança mais um NavigationException
Anteriormente, chamar NavigationManager.NavigateTo durante a renderização estática do lado do servidor (SSR) lançaria um erro NavigationException, interrompendo a execução antes de ser convertido em uma resposta de redirecionamento. Isso causou confusão durante a depuração e era inconsistente com a renderização interativa, em que o código após NavigateTo continua a ser executado normalmente.
A chamada NavigationManager.NavigateTo durante o SSR estático não lança mais um NavigationException. Em vez disso, ele se comporta de forma consistente com a renderização interativa ao executar a navegação sem gerar uma exceção.
O código que dependia do lançamento de NavigationException deve ser atualizado. Por exemplo, na interface do usuário padrão BlazorIdentity, o IdentityRedirectManager
anteriormente lançava uma InvalidOperationException após chamar RedirectTo
para garantir que não fosse invocado durante a renderização interativa. Essa exceção e os [DoesNotReturn]
atributos agora devem ser removidos.
Para reverter para o comportamento anterior de gerar um NavigationException, defina a seguinte opção AppContext:
AppContext.SetSwitch(
"Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException",
isEnabled: true);
Respostas de 'Não Encontrado' usando NavigationManager
para SSR estático e renderização interativa global
Agora NavigationManager inclui um NotFound
método para lidar com cenários em que um recurso solicitado não é encontrado durante a renderização estática do lado do servidor (SSR estático) ou a renderização interativa global:
-
Renderização estática do lado do servidor (SSR estático): ao chamar
NotFound
, define o código de status HTTP como 404. - Renderização de streaming: gera uma exceção se a resposta já tiver sido iniciada.
-
Renderização interativa: sinaliza o Blazor roteador (
Router
componente) para renderizar o conteúdo não encontrado.
O suporte à renderização por página/componente está planejado para a Versão Prévia 5 em junho de 2025.
Você pode usar o NavigationManager.OnNotFound
evento para notificações quando NotFound
é invocado.
Para obter mais informações e exemplos, consulte ASP.NET Roteamento e navegação principaisBlazor.
Blazor o roteador tem um NotFoundPage
parâmetro
Blazor agora fornece uma maneira melhorada de exibir uma página "Não Encontrado" quando tenta acessar uma página que não existe. Você pode especificar uma página a ser renderizada ao NavigationManager.NotFound
passar um tipo de página para o Router
componente usando o parâmetro NotFoundPage
. Essa abordagem é recomendada em relação ao fragmento NotFound
anterior, pois oferece suporte ao roteamento e funciona através de middleware de re-execução de código, sendo compatível mesmo com cenários diferentes de Blazor. Se um fragmento NotFound
e NotFoundPage
estiverem definidos, a página especificada por NotFoundPage
tem prioridade.
<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>
O Blazor modelo de projeto agora inclui uma NotFound.razor
página por padrão. Essa página é renderizada automaticamente sempre que NavigationManager.NotFound
é chamada em seu aplicativo, facilitando o processamento de rotas ausentes com uma experiência de usuário consistente.
Para obter mais informações, consulte ASP.NET Core roteamento e navegaçãoBlazor.
Métricas e rastreamento
Esta versão apresenta métricas abrangentes e recursos de rastreamento para Blazor aplicativos, fornecendo observabilidade detalhada do ciclo de vida do componente, navegação, manipulação de eventos e gerenciamento de circuitos.
Para obter mais informações, confira Melhores Práticas do ASP.NET CoreBlazor.
Blazor Hybrid
Esta seção descreve os novos recursos do Blazor Hybrid.
Novo .NET MAUIBlazor Hybrid com um artigo e um exemplo do Blazor Web App e do ASP.NET Core Identity
Um novo artigo e um aplicativo de exemplo foram adicionados para .NET MAUIBlazor Hybrid e Aplicativo Web usando ASP.NET Core Identity.
Para obter mais informações, consulte os seguintes recursos:
- .NET MAUI Blazor Hybrid e Aplicativo Web com o ASP.NET Core Identity
-
MauiBlazorWebIdentity
aplicativo de exemplo (dotnet/blazor-samples
repositório GitHub)
SignalR
Esta seção descreve os novos recursos do SignalR.
APIs mínimas
Esta seção descreve novos recursos para APIs mínimas.
Tratar a cadeia de caracteres vazia em postagens de formulário como nulo para tipos de valor que permitem valor nulo
Ao usar o atributo [FromForm]
com um objeto complexo em APIs mínimas, valores de cadeias de caracteres vazias em uma postagem de formulário agora são convertidos em null
em vez de causar uma falha de análise. Esse comportamento corresponde à lógica de processamento de postagens de formulário não associadas a objetos complexos em APIs mínimas.
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; }
}
Obrigado a @nvmkpk por contribuir com essa mudança!
Suporte à validação em APIs mínimas
O suporte para validação em APIs Mínimas agora está disponível. Esse recurso permite que você solicite a validação dos dados enviados para seus endpoints da API. Habilitar a validação permite que o runtime do ASP.NET Core execute as validações definidas no:
- Consulta
- Cabeçalho
- Corpo da solicitação
As validações são definidas usando atributos no DataAnnotations
namespace. Os desenvolvedores personalizam o comportamento do sistema de validação:
- Criando implementações de atributo personalizado
[Validation]
. - Implementando a
IValidatableObject
interface para lógica de validação complexa.
Se a validação falhar, o runtime retornará uma resposta 400 Solicitação incorreta com detalhes dos erros de validação.
Habilitar o suporte de validação interna para APIs mínimas
Habilite o suporte de validação interna para APIs mínimas chamando o AddValidation
método de extensão para registrar os serviços necessários no contêiner de serviço para seu aplicativo:
builder.Services.AddValidation();
A implementação descobre automaticamente tipos definidos em manipuladores mínimos de API ou como tipos base de tipos definidos em manipuladores mínimos de API. Um filtro de ponto de extremidade executa a validação nesses tipos e é adicionado para cada ponto de extremidade.
A validação pode ser desabilitada para pontos de extremidade específicos usando o DisableValidation
método de extensão, como no exemplo a seguir:
app.MapPost("/products",
([EvenNumber(ErrorMessage = "Product ID must be even")] int productId, [Required] string name)
=> TypedResults.Ok(productId))
.DisableValidation();
Observação
Foram feitas várias pequenas melhorias e correções no gerador de validação de APIs mínimas introduzido no ASP.NET Core para .NET 10. Para dar suporte a aprimoramentos futuros, as APIs de resolvedor de validação subjacente agora são marcadas como experimentais. As APIs de nível AddValidation
superior e o filtro de validação interno permanecem estáveis e não experimentais.
Validação com tipos de registro
APIs mínimas também dão suporte à validação com tipos de registro C#. Os tipos de registro podem ser validados usando atributos do System.ComponentModel.DataAnnotations namespace, semelhantes às classes. Por exemplo:
public record Product(
[Required] string Name,
[Range(1, 1000)] int Quantity);
Ao usar tipos de registro como parâmetros em endpoints da API Minimal, os atributos de validação são aplicados automaticamente da mesma maneira que os tipos de classe:
app.MapPost("/products", (Product product) =>
{
// Endpoint logic here
return TypedResults.Ok(product);
});
Suporte para eventos enviados do servidor (SSE)
ASP.NET Core agora dá suporte ao retorno de um resultado ServerSentEvents usando a API TypedResults.ServerSentEvents . Esse recurso tem suporte em APIs mínimas e aplicativos baseados em controlador.
Server-Sent Eventos é uma tecnologia de push de servidor que permite que um servidor envie um fluxo de mensagens de evento para um cliente por meio de uma única conexão HTTP. No .NET, as mensagens de evento são representadas como SseItem<T>
objetos, que podem conter um tipo de evento, uma ID e um conteúdo de dados do tipo T
.
A classe TypedResults tem um novo método estático chamado ServerSentEvents que pode ser usado para retornar um ServerSentEvents
resultado. O primeiro parâmetro para esse método é um IAsyncEnumerable<SseItem<T>>
que representa o fluxo de mensagens de evento a serem enviadas ao cliente.
O exemplo a seguir ilustra como usar a TypedResults.ServerSentEvents
API para retornar um fluxo de eventos de freqüência cardíaca como objetos JSON para o cliente:
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");
});
Para obter mais informações, consulte:
- Server-Sent eventos no MDN.
-
Aplicativo de exemplo de API mínimo usando a
TypedResults.ServerSentEvents
API para retornar um fluxo de eventos de freqüência cardíaca como cadeia de caracteresServerSentEvents
e objetos JSON para o cliente. -
Aplicativo de exemplo de API do controlador usando a
TypedResults.ServerSentEvents
API para retornar um fluxo de eventos de freqüência cardíaca como cadeia de caracteresServerSentEvents
e objetos JSON para o cliente.
OpenAPI
Esta seção descreve novos recursos para OpenAPI.
Suporte ao OpenAPI 3.1
ASP.NET Core adicionou suporte para gerar documentos OpenAPI versão 3.1 no .NET 10. Apesar do incremento de versão menor, o OpenAPI 3.1 é uma atualização significativa para a especificação OpenAPI, em particular com suporte completo para o rascunho do JSON Schema 2020-12.
Algumas das alterações que você verá no documento OpenAPI gerado incluem:
- Tipos anuláveis não têm mais a propriedade
nullable: true
no esquema. - Em vez de uma propriedade
nullable: true
, eles têm uma palavra-chavetype
cujo valor é uma matriz que incluinull
como um dos tipos. - Propriedades ou parâmetros definidos como um C#
int
oulong
agora aparecem no documento OpenAPI gerado sem otype: integer
campo e têm umpattern
campo que limita o valor a dígitos. Isso acontece quando a propriedade NumberHandling no JsonSerializerOptions é configurada paraAllowReadingFromString
, o padrão para aplicativos Web do ASP.NET Core. Para permitir que C#int
elong
sejam representados no documento OpenAPI comotype: integer
, defina a propriedade NumberHandling comolong
.
Com esse recurso, a versão padrão do OpenAPI para documentos gerados é3.1
. A versão pode ser alterada definindo explicitamente a propriedade OpenApiVersion do OpenApiOptions no configureOptions
parâmetro delegado de AddOpenApi:
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});
Ao gerar o documento OpenAPI no momento da compilação, a versão do OpenAPI pode ser selecionada definindo o item --openapi-version
no OpenApiGenerateDocumentsOptions
do MSBuild.
<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>
O suporte ao OpenAPI 3.1 foi adicionado principalmente na PR a seguir.
Alterações significativas no OpenAPI 3.1
O suporte para OpenAPI 3.1 requer uma atualização para a biblioteca de OpenAPI.NET subjacente para uma nova versão principal, 2.0. Esta nova versão tem algumas alterações significativas da versão anterior. As alterações significativas podem afetar aplicativos se eles tiverem quaisquer transformadores de documento, operação ou esquema. Alterações significativas nesta iteração incluem o seguinte:
- Entidades dentro do documento OpenAPI, como operações e parâmetros, são digitadas como interfaces. Existem implementações concretas para as variantes inseridas e referenciadas de uma entidade. Por exemplo, um
IOpenApiSchema
pode ser umOpenApiSchema
embutido ou umOpenApiSchemaReference
que aponta para um esquema definido em outro lugar no documento. - A propriedade
Nullable
foi removida do tipoOpenApiSchema
. Para determinar se um tipo é anulável, avalie se a propriedadeOpenApiSchema.Type
defineJsonSchemaType.Null
.
Uma das mudanças mais significativas é que a classe OpenApiAny
foi descartada em favor do uso JsonNode
diretamente. Os transformadores que usam OpenApiAny
precisam ser atualizados para usar JsonNode
. A seguinte diferença mostra as alterações no transformador de esquema do .NET 9 para o .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;
});
Observe que essas alterações são necessárias mesmo ao configurar apenas a versão do OpenAPI para 3.0.
OpenAPI em YAML
ASP.NET agora dá suporte ao serviço do documento OpenAPI gerado no formato YAML. YAML pode ser mais conciso do que JSON, eliminando chaves curvas e aspas quando essas podem ser inferidas. O YAML também dá suporte a cadeias de caracteres de várias linhas, que podem ser úteis para descrições longas.
Para configurar um aplicativo para atender ao documento OpenAPI gerado no formato YAML, especifique o ponto de extremidade na chamada MapOpenApi com um sufixo ".yaml" ou ".yml", conforme mostrado no exemplo a seguir:
if (app.Environment.IsDevelopment())
{
app.MapOpenApi("/openapi/{documentName}.yaml");
}
Suporte para:
- Atualmente, o YAML só está disponível para o OpenAPI servido a partir do endpoint OpenAPI.
- A geração de documentos OpenAPI no formato YAML no momento da compilação é adicionada em uma versão prévia futura.
Consulte este PR, que adicionou suporte para disponibilizar o documento OpenAPI gerado em formato YAML.
Descrição da resposta em ProducesResponseType para controladores de API
Os atributos ProducesAttribute, ProducesResponseTypeAttribute e ProducesDefaultResponseType agora aceitam um parâmetro de cadeia de caracteres opcional, Description
que definirá a descrição da resposta. Veja um exemplo:
[HttpGet(Name = "GetWeatherForecast")]
[ProducesResponseType<IEnumerable<WeatherForecast>>(StatusCodes.Status200OK,
Description = "The weather forecast for the next 5 days.")]
public IEnumerable<WeatherForecast> Get()
{
E o OpenAPI gerado:
"responses": {
"200": {
"description": "The weather forecast for the next 5 days.",
"content": {
Atualmente, APIs mínimas não têm suporteProducesResponseType
.
Contribuição da comunidade por Sander ten Brinke🙏
Preencher comentários de documentos XML no documento OpenAPI
A geração de documentos OpenAPI de ASP.NET Core agora incluirá metadados de comentários de documentação XML nas definições de métodos, classes e membros no documento OpenAPI. Você deve habilitar os comentários do documento XML em seu arquivo de projeto para usar esse recurso. Você pode fazer isso adicionando a seguinte propriedade ao arquivo de projeto:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
No tempo de compilação, o pacote OpenAPI utilizará um gerador de código-fonte para descobrir comentários XML no assembly da aplicação atual e quaisquer referências de projeto, emitindo código-fonte para inseri-los no documento por meio de um transformador de documento OpenAPI.
Observe que o processo de build do C# não captura comentários de documento XML colocados em expresões lambda, portanto, para usar comentários de documento XML para adicionar metadados a um ponto de extremidade de API mínimo, você deve definir o manipulador de ponto de extremidade como um método, colocar os comentários do documento XML sobre o método e, em seguida, referenciar esse método do método MapXXX
. Por exemplo, para usar comentários de documento XML para adicionar metadados a um ponto de extremidade de API mínimo originalmente definido como uma expressão lambda:
app.MapGet("/hello", (string name) =>$"Hello, {name}!");
Altere a chamada MapGet
para fazer referência a um método:
app.MapGet("/hello", Hello);
Defina o método Hello
com comentários de documento XML:
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}!";
}
}
No exemplo anterior, o método Hello
é adicionado à classe Program
, mas você pode adicioná-lo a qualquer classe em seu projeto.
O exemplo anterior ilustra os comentários dos documentos XML <summary>
, <remarks>
e <param>
.
Para obter mais informações sobre comentários de documentos XML, incluindo todas as marcas com suporte, consulte a documentação do C#.
Como a funcionalidade principal é fornecida por meio de um gerador de origem, ela pode ser desabilitada adicionando o MSBuild a seguir ao arquivo de projeto.
<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>
O gerador de origem processa os arquivos XML incluídos na propriedade AdditionalFiles
. Para adicionar (ou remover), as fontes modificam a propriedade da seguinte maneira:
<Target Name="AddXmlSources" BeforeTargets="CoreCompile">
<ItemGroup>
<AdditionalFiles Include="$(PkgSome_Package)/lib/net10.0/Some.Package.xml" />
</ItemGroup>
</Target>
Microsoft.AspNetCore.OpenApi adicionado ao modelo de API Web do ASP.NET Core (AOT Nativo)
O modelo de projeto da API Web do ASP.NET Core (AOT Nativo) (nome webapiaot
curto) agora inclui suporte para a geração de documentos OpenAPI usando o Microsoft.AspNetCore.OpenApi
pacote por padrão. Esse suporte é desabilitado usando o --no-openapi
sinalizador ao criar um novo projeto.
Esta foi uma contribuição da comunidade por @sander1095. Obrigado por essa contribuição!
Suporte para IOpenApiDocumentProvider no contêiner de DI.
ASP.NET Core no .NET 10 dá suporte a IOpenApiDocumentProvider no contêiner de DI (injeção de dependência). Os desenvolvedores podem injetar IOpenApiDocumentProvider
em seus aplicativos e usá-lo para acessar o documento OpenAPI. Essa abordagem é útil para acessar documentos OpenAPI fora do contexto de solicitações HTTP, como em serviços em segundo plano ou middleware personalizado.
Anteriormente, a execução da lógica de inicialização do aplicativo sem iniciar um servidor HTTP poderia ser feita usando HostFactoryResolver
com uma implementação no-op IServer
. O novo recurso simplifica esse processo fornecendo uma API simplificada inspirada na Aspire's IDistributedApplicationPublisher, que faz parte da estrutura da Aspire para hospedagem e publicação de aplicativos distribuídos.
Para obter mais informações, consulte dotnet/aspnetcore #61463.
Melhorias no gerador de comentários XML
A geração de comentários XML lida com tipos complexos no .NET 10 melhor do que as versões anteriores do .NET.
- Ele produz comentários XML precisos e completos para uma gama mais ampla de tipos.
- Ele lida com cenários mais complexos.
- Ele contorna de forma elegante o processamento para tipos complexos que causam erros de compilação em versões anteriores.
Essas melhorias alteram o modo de falha para determinados cenários, desde erros de build até metadados ausentes.
Além disso, o processamento de comentários do documento XML agora pode ser configurado para acessar comentários XML em outros assemblies. Isso é útil para gerar documentação para tipos definidos fora do assembly atual, como o ProblemDetails
tipo no Microsoft.AspNetCore.Http
namespace.
Essa configuração é feita com diretivas no arquivo de build do projeto. O exemplo a seguir mostra como configurar o gerador de comentários XML para acessar comentários XML para tipos no assembly Microsoft.AspNetCore.Http
, que inclui a classe 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>
Esperamos incluir comentários XML de um conjunto selecionado de assemblies na estrutura compartilhada em visualizações futuras, para evitar a necessidade dessa configuração na maioria dos casos.
Suporte para gerar OpenApiSchemas em transformadores
Os desenvolvedores agora podem gerar um esquema para um tipo C# usando a mesma lógica que ASP.NET geração de documentos OpenAPI Core e adicioná-lo ao documento OpenAPI. Em seguida, o esquema pode ser referenciado de outro lugar no documento OpenAPI.
O contexto passado para os transformadores de documento, operação e esquema inclui um novo GetOrCreateSchemaAsync
método que pode ser usado para gerar um esquema para um tipo.
Esse método também tem um parâmetro opcional ApiParameterDescription
para especificar metadados adicionais para o esquema gerado.
Para dar suporte à adição do esquema ao documento OpenAPI, uma Document
propriedade foi adicionada aos contextos de transformador de operação e esquema. Isso permite que qualquer transformador adicione um esquema ao documento OpenAPI usando o método do AddComponent
documento.
Exemplo
Para usar esse recurso em um documento, operação ou transformador de esquema, crie o esquema usando o GetOrCreateSchemaAsync
método fornecido no contexto e adicione-o ao documento OpenAPI usando o método AddComponent
do documento.
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 atualizado para Preview.18
A biblioteca de OpenAPI.NET usada na geração de documentos do ASP.NET Core OpenAPI foi atualizada para v2.0.0-preview18. A versão v2.0.0-preview18 melhora a compatibilidade com a versão atualizada da biblioteca.
A versão anterior v2.0.0-preview17 incluiu várias correções de bugs e melhorias e também introduziu algumas alterações que causam incompatibilidades. As mudanças disruptivas devem afetar apenas os usuários que usam transformadores de documento, operação ou esquema. Alterações significativas nesta versão que podem afetar os desenvolvedores incluem o seguinte:
- As propriedades do objeto efêmero agora estão em Metadados
- Usar o objeto do método HTTP em vez de enumerar
Autenticação e autorização
Esta seção descreve novos recursos para autenticação e autorização.
Métricas de autenticação e autorização
As métricas foram adicionadas para determinados eventos de autenticação e autorização no ASP.NET Core. Com essa alteração, agora você pode obter métricas para os seguintes eventos:
- Autenticação:
- Duração da solicitação autenticada
- Contagem de desafios
- Contagem de proibições
- Contagem de entradas
- Contagem de desconexões
- Autorização:
- Contagem de solicitações que exigem autorização
A imagem a seguir mostra um exemplo da métrica de duração da solicitação autenticada no painel do Aspire:
Para obter mais informações, consulte ASP.NET Principais métricas de Autorização e Autenticação.
Variado
Esta seção descreve novos recursos diversos no .NET 10.
Descritores de segurança personalizáveis para HTTP.sys
Agora você pode especificar um descritor de segurança personalizado para filas de solicitação HTTP.sys. A nova propriedade RequestQueueSecurityDescriptor permite HttpSysOptions um controle mais granular sobre os direitos de acesso da fila de solicitação. Esse controle granular permite que você adapte a segurança às necessidades do aplicativo.
O que você pode fazer com a nova propriedade
Uma fila de solicitações no HTTP.sys é uma estrutura no nível do kernel que armazena temporariamente solicitações HTTP de entrada até que seu aplicativo esteja pronto para processá-las. Ao personalizar o descritor de segurança, você pode permitir ou negar acesso de usuários ou grupos específicos à fila de solicitações. Isso é útil em cenários em que você deseja restringir ou delegar o tratamento de solicitações HTTP.sys no nível do sistema operacional.
Como usar a nova propriedade
A RequestQueueSecurityDescriptor
propriedade se aplica somente ao criar uma nova fila de solicitação. A propriedade não afeta as filas de solicitação existentes. Para usar essa propriedade, defina-a como uma GenericSecurityDescriptor instância ao configurar o servidor HTTP.sys.
Por exemplo, o código a seguir permite todos os usuários autenticados, mas nega aos convidados:
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;
});
Para obter mais informações, consulte HTTP.sys implementação do servidor Web no ASP.NET Core.
Melhor suporte para testar aplicativos com instruções de nível superior
O .NET 10 agora tem melhor suporte para testar aplicativos que usam instruções de nível superior. Anteriormente, os desenvolvedores tinham que adicionar manualmente public partial class Program
ao arquivo de Program.cs
para que o projeto de teste pudesse referenciar o Program class
.
public partial class Program
era necessário porque o recurso de instrução de nível superior no C# 9 gerou um Program class
que foi declarado como interno.
No .NET 10, um gerador de origem é usado para gerar a declaração de public partial class Program
se o programador não a declarou explicitamente. Além disso, um analisador foi adicionado para detectar quando public partial class Program
é declarado explicitamente e aconselha o desenvolvedor a removê-lo.
Os seguintes PRs contribuíram para esta funcionalidade:
Nova implementação de Patch JSON com System.Text.Json
- É um formato padrão para descrever as alterações a serem aplicadas a um documento JSON.
- É definido no RFC 6902 e é amplamente usado em APIs RESTful para executar atualizações parciais para recursos JSON.
- Representa uma sequência de operações (por exemplo, Adicionar, Remover, Substituir, Mover, Copiar, Testar) que podem ser aplicadas para modificar um documento JSON.
Em aplicativos Web, o Patch JSON geralmente é usado em uma operação PATCH para executar atualizações parciais de um recurso. Em vez de enviar todo o recurso para uma atualização, os clientes podem enviar um documento de patch JSON contendo apenas as alterações. A aplicação de patch reduz o tamanho da carga e melhora a eficiência.
Esta versão apresenta uma nova implementação de Microsoft.AspNetCore.JsonPatch baseada na serialização System.Text.Json. Esse recurso:
- Alinha-se às práticas modernas do .NET aproveitando a
System.Text.Json
biblioteca, que é otimizada para .NET. - Fornece desempenho aprimorado e consumo de memória reduzido em comparação com a implementação baseada em legado
Newtonsoft.Json
.
Os parâmetros de comparação a seguir comparam o desempenho da nova System.Text.Json
implementação com a implementação herdada Newtonsoft.Json
.
Cenário | Implementação | Média | Memória alocada |
---|---|---|---|
Parâmetros de comparação de aplicativo | Newtonsoft.JsonPatch | 271,924 μs | 25 KB |
System.Text.JsonPatch | 1,584 μs | 3 KB | |
Parâmetros de comparação de desserialização | Newtonsoft.JsonPatch | 19,261 μs | 43 KB |
System.Text.JsonPatch | 7,917 μs | 7 KB |
Esses parâmetros de comparação destacam ganhos significativos de desempenho e redução do uso de memória com a nova implementação.
Observações:
- A nova implementação não é uma substituição direta para a implementação legada. Em particular, a nova implementação não dá suporte a tipos dinâmicos, por exemplo, ExpandoObject.
- O padrão de patch JSON tem riscos de segurança inerentes. Como esses riscos são inerentes ao padrão de Patch JSON, a nova implementação não tenta reduzir os riscos inerentes à segurança. É responsabilidade do desenvolvedor garantir que o documento do Patch JSON seja seguro para aplicar ao objeto de destino. Para obter mais informações, consulte a seção Mitigando riscos de segurança .
Uso
Para habilitar o suporte a JSON Patch com System.Text.Json
, instale o pacote NuGet Microsoft.AspNetCore.JsonPatch.SystemTextJson
.
dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease
Este pacote fornece uma JsonPatchDocument<T>
classe para representar um documento de Patch JSON para objetos de tipo T
e lógica personalizada para serializar e desserializar documentos de Patch JSON usando System.Text.Json
. O método chave da JsonPatchDocument<T>
classe é ApplyTo
, que aplica as operações de patch a um objeto de destino do tipo T
.
Os exemplos a seguir demonstram como usar o ApplyTo
método para aplicar um documento de Patch JSON a um objeto.
Exemplo: aplicação de um JsonPatchDocument
O exemplo a seguir demonstra:
- As operações
add
,replace
eremove
. - Operações em propriedades aninhadas.
- Adicionando um novo item a uma matriz.
- Usando um conversor de enumeração de cadeia de caracteres JSON em um documento de Patch 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"
// }
// ]
// }
O ApplyTo
método geralmente segue as convenções e opções de System.Text.Json
para processamento do JsonPatchDocument
, incluindo o comportamento controlado pelas seguintes opções:
-
NumberHandling
: se as propriedades numéricas são lidas de cadeias de caracteres. -
PropertyNameCaseInsensitive
: se os nomes de propriedade são sensíveis a maiúsculas e minúsculas.
Principais diferenças entre System.Text.Json
e a nova JsonPatchDocument<T>
implementação:
- O tipo de tempo de execução do objeto de destino, não o tipo declarado, determina quais propriedades
ApplyTo
altera. -
System.Text.Json
A desserialização depende do tipo declarado para identificar as propriedades elegíveis.
Exemplo: aplicação de um JsonPatchDocument
com tratamento de erros
Há vários erros que podem ocorrer ao aplicar um documento de patch JSON. Por exemplo, o objeto de destino pode não ter a propriedade especificada ou o valor especificado pode ser incompatível com o tipo de propriedade.
O Patch JSON também dá suporte à operação test
. A test
operação verifica se um valor especificado é igual à propriedade de destino e, se não, retorna um erro.
O exemplo a seguir demonstra como lidar com esses erros normalmente.
Importante
O objeto passado para o ApplyTo
método é modificado no local. É responsabilidade do chamador descartar essas alterações se alguma operação falhar.
// 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": []
// }
Mitigação dos riscos à segurança
Ao usar o Microsoft.AspNetCore.JsonPatch.SystemTextJson
pacote, é essencial entender e atenuar possíveis riscos de segurança. As seções a seguir descrevem os riscos de segurança identificados associados ao Patch JSON e fornecem mitigações recomendadas para garantir o uso seguro do pacote.
Importante
Essa não é uma lista completa de ameaças. Os desenvolvedores de aplicativos devem realizar suas próprias revisões de modelo de ameaças para determinar uma lista abrangente específica do aplicativo e criar mitigações apropriadas, conforme necessário. Por exemplo, os aplicativos que expõem coleções a operações de patch devem considerar o potencial de ataques de complexidade algorítmica se essas operações inserirem ou removerem elementos no início da coleção.
Ao executar modelos de ameaças abrangentes para seus próprios aplicativos e lidar com ameaças identificadas, seguindo as mitigações recomendadas abaixo, os consumidores desses pacotes podem integrar a funcionalidade do Patch JSON em seus aplicativos, minimizando os riscos de segurança.
Os consumidores desses pacotes podem integrar a funcionalidade do Patch JSON em seus aplicativos, minimizando os riscos de segurança, incluindo:
- Execute modelos de ameaças abrangentes para seus próprios aplicativos.
- Abordar ameaças identificadas.
- Siga as mitigações recomendadas nas seções a seguir.
Negação de Serviço (DoS) por meio da amplificação de memória
-
Cenário: um cliente mal-intencionado envia uma
copy
operação que duplica grafos de objetos grandes várias vezes, levando ao consumo excessivo de memória. - Impacto: Possíveis condições de out-Of-Memory (OOM), causando interrupções no serviço.
-
Mitigação:
- Valide os documentos JSON Patch de entrada quanto ao tamanho e à estrutura antes de chamar
ApplyTo
. - A validação deve ser específica do aplicativo, mas uma validação de exemplo pode ser semelhante à seguinte:
- Valide os documentos JSON Patch de entrada quanto ao tamanho e à estrutura antes de chamar
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();
}
}
Subversão lógica de negócios
- Cenário: as operações de patch podem manipular campos com invariáveis implícitas, (por exemplo, sinalizadores internos, IDs ou campos computados), violando restrições comerciais.
- Impacto: problemas de integridade de dados e comportamento de aplicativo não intencional.
-
Mitigação:
- Use objetos POCO com propriedades definidas explicitamente que são seguras de modificar.
- Evite expor propriedades confidenciais ou críticas à segurança no objeto de destino.
- Se nenhum objeto POCO for usado, valide o objeto corrigido após a aplicação de operações para garantir que regras de negócios e invariáveis não sejam violados.
Autenticação e autorização
- Cenário: clientes não autenticados ou não autorizados enviam solicitações de patch JSON mal-intencionadas.
- Impacto: acesso não autorizado para modificar dados confidenciais ou interromper o comportamento do aplicativo.
-
Mitigação:
- Proteja os endpoints que aceitam solicitações JSON Patch com mecanismos de autenticação e autorização adequados.
- Restrinja o acesso a clientes confiáveis ou usuários com permissões apropriadas.
Detectar se a URL é local usando RedirectHttpResult.IsLocalUrl
Use o novo método auxiliar RedirectHttpResult.IsLocalUrl(url)
para detectar se uma URL é local. Uma URL será considerada local se o seguinte for verdadeiro:
- Ele não tem a seção host ou autoridade .
- Tem um caminho absoluto.
URLs que usam caminhos virtuais"~/"
também são locais.
IsLocalUrl
é útil para validar URLs antes de redirecioná-las para evitar ataques de redirecionamento abertos.
if (RedirectHttpResult.IsLocalUrl(url))
{
return Results.LocalRedirect(url);
}
Obrigado @martincostello por esta contribuição!