Partilhar via


O que há de novo no ASP.NET Core no .NET 10

Este artigo destaca as alterações mais significativas no ASP.NET Core no .NET 10 com links para documentação relevante.

Para alterações de quebra, consulte Alterações de quebra no .NET.

Blazor

Esta seção descreve as novas funcionalidades para 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:

Todas as nossas soluções de exemplo OIDC e Entra agora incluem um projeto de API da Web separado (MinimalApiJwt) para demonstrar como configurar e chamar uma API da Web externa com segurança. As chamadas às APIs da Web são demonstradas com um handler de token e um cliente HTTP nomeado para pacotes da Web/API da Microsoft ou um provedor de identidade OIDC Identity para Microsoft Entra ID.

As soluções de exemplo são configuradas em código C# em seus Program arquivos. Para configurar as soluções a partir de arquivos de configurações do aplicativo (por exemplo, appsettings.json) consulte a nova seção Configuração de fornecimento com o provedor de configuração JSON (configurações do aplicativo) dos artigos OIDC ou Entra.

Nosso artigo e aplicativos de exemplo do Entra também incluem novas orientações sobre as seguintes abordagens:

QuickGrid RowClass parâmetro

Aplique uma classe de estilo a uma linha da grade com base no elemento 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 estilo 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 ASP.NET componente Core Blazor 'QuickGrid'.

Blazor script como ativo web estático

Em versões anteriores do .NET, o script Blazor é servido a partir de um recurso incorporado 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.

O Blazor script (blazor.web.js ou blazor.server.js) é incluído pelo framework se o projeto contiver pelo menos um Razor ficheiro componente (.razor). Se a sua aplicação exigir o Blazor script mas não contiver pelo menos um componente, adicione a seguinte propriedade MSBuild ao ficheiro de projeto da aplicação para forçar a inclusão incondicional do script:

<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>

Para obter mais informações, consulte os seguintes recursos:

Destaques do modelo de rota

O [Route] atributo agora oferece suporte ao realce de sintaxe de rota para ajudar a visualizar a estrutura do modelo de rota:

O padrão de modelo de rota de um atributo de rota para o valor do contador mostra o realce de sintaxe

Anteriormente, NavigationManager.NavigateTo rolava até ao topo da página para navegações dentro da mesma página. Esse comportamento foi alterado no .NET 10 para que o navegador não rola mais para a parte superior da página ao navegar para a mesma página. Isso significa que a janela de visualização não é mais redefinida ao fazer atualizações no endereço da página atual, como alterar o query ou o fragmento.

Componente da interface do usuário de reconexão adicionado ao modelo de projeto Blazor Web App

O modelo de projeto Blazor Web App agora inclui um componente ReconnectModal, incluindo folhas de estilo e arquivos JavaScript localizados, para proporcionar um maior controlo ao desenvolvedor sobre a interface do usuário 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 mais rígidas da Política de Segurança de Conteúdo (CSP) para a política style-src. Em versões anteriores, a interface do usuário de reconexão padrão foi criada pela estrutura de uma forma que poderia causar violações de CSP. Note que a interface do utilizador de reconexão padrão ainda é utilizada como alternativa quando a aplicação não define a interface do utilizador de reconexão, como ao utilizar o componente ReconnectModal do modelo de projeto ou um componente personalizado semelhante.

Novos recursos da interface de reconexão

  • Além de indicar o estado de reconexão ao definir uma classe CSS específica no elemento da interface do usuário de reconexão, o novo evento components-reconnect-state-changed é disparado para alterações no 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 ASP.NET Diretrizes principaisBlazorSignalR.

Ignore a cadeia de caracteres de consulta e o fragmento 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 retém a classe active se o caminho da URL corresponder, mas a cadeia de caracteres de consulta ou o fragmento forem alterados. Para reverter para o comportamento original, use o Microsoft.AspNetCore.Components.Routing.NavLink.EnableMatchAllForQueryStringAndFragmentAppContext interruptor definido para true.

Você também pode substituir o método ShouldMatch no NavLink para personalizar o comportamento correspondente:

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

Para mais informações, consulte ASP.NET Core Blazor navegação.

Fechar opções de coluna QuickGrid

Pode agora fechar as 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 de 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));
}

Transmissão de resposta do HttpClient ativada por defeito

Em versões anteriores Blazor, o streaming de respostas para HttpClient pedidos tinha de ser ativado. Agora, o streaming de resposta está habilitado por padrão.

Esta é uma mudança de quebra porque chamar HttpContent.ReadAsStreamAsync para um HttpResponseMessage.Content (response.Content.ReadAsStreamAsync()) retorna um BrowserHttpReadStream e não mais um MemoryStream. BrowserHttpReadStream não oferece suporte a operações síncronas, como Stream.Read(Span<Byte>). Se o seu código usa operações síncronas, você pode desativar o streaming de respostas ou copiar o Stream para um MemoryStream você mesmo.

Para desativar o streaming de respostas globalmente, use uma das seguintes abordagens:

  • Adicione a <WasmEnableStreamingResponse> propriedade ao arquivo de projeto com um valor de false:

    <WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>
    
  • Defina a DOTNET_WASM_ENABLE_STREAMING_RESPONSE variável de ambiente como false ou 0.

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 mais informações, veja HttpClient e HttpRequestMessage com as opções de pedido do Fetch API (artigo Chamar API da Web).

Impressão digital do lado do cliente

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 pontos de extremidade de roteamento de ativos estáticos do Mapa (MapStaticAssets), o componente e a propriedade ImportMap () para resolver módulos JavaScript com impressão digital. Para o .NET 10, pode optar por incorporar a impressão digital do lado do cliente de módulos JavaScript em aplicações autónomas Blazor WebAssembly.

Em aplicativos autónomos Blazor WebAssembly, durante a compilação/publicação, o framework substitui espaços reservados em index.html por valores calculados durante a construção para marcar digitalmente os ativos estáticos. Uma impressão digital é colocada no nome do ficheiro de script blazor.webassembly.js.

A seguinte marcação deve estar presente no wwwroot/index.html arquivo 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 <OverrideHtmlAssetPlaceholders> propriedade 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 é assinalado pela estrutura. Por exemplo, um ficheiro de script nomeado scripts.js na pasta wwwroot/js da aplicação é identificado adicionando #[.{fingerprint}] antes da extensão do ficheiro (.js):

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

Para gerar a impressão digital de módulos adicionais JS em aplicações 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 automaticamente colocados no mapa de importação:

  • Automático para Blazor Web App CSR.
  • Ao optar pela impressão digital do módulo em aplicações autónomas Blazor WebAssembly de acordo com as instruções anteriores.

Ao resolver a importação para interoperabilidade JavaScript, o mapa de importação é usado pelo navegador resolver arquivos com impressão digital.

Definir o ambiente em aplicações independentes 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 construção.
  • Production para publicação.

Para mais informações, consulte ASP.NET Ambientes centraisBlazor.

Arquivo de configuração de inicialização integrado

A configuração de inicialização de Blazor, que antes do lançamento do .NET 10 existia num ficheiro chamado blazor.boot.json, foi incorporada no script dotnet.js. Isso afeta apenas os desenvolvedores que estão interagindo diretamente com o blazor.boot.json arquivo, como quando os desenvolvedores estão:

  • Verificação da integridade dos ficheiros para ativos publicados com o script PowerShell de resolução de problemas de integridade, de acordo com as orientações em Blazor WebAssembly.
  • Alterar a extensão de nome de arquivo de arquivos DLL quando não estiver usando o formato de arquivo Webcil padrão de acordo com as orientações em Host 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 persistência do estado de componentes e serviços

Agora você pode especificar declarativamente o estado a ser persistido em componentes e serviços usando o atributo [PersistentState]. As propriedades com esse atributo são automaticamente persistidas usando o serviço PersistentComponentState durante a pré-renderização. O estado é recuperado quando o componente é renderizado interativamente ou o serviço é instanciado.

Em versões anteriores Blazor, a persistência do estado do componente durante a pré-renderizaçã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();
}

Este 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 {
    [PersistentState]
    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 pode-se estabelecer o estado declarativo num serviço para utilização em toda a aplicação chamando RegisterPersistentService no construtor de componentes Razor (AddRazorComponents) com um tipo de serviço personalizado e modo de renderização. Para obter mais informações, consulte Persistência de estado pré-renderizado do núcleo ASP.NETBlazor.

Novos recursos de interoperabilidade 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, tanto as propriedades de dados como as de acessador.

Os seguintes métodos assíncronos estão disponíveis em IJSRuntime e IJSObjectReference com o mesmo comportamento de escopo que o método existente IJSRuntime.InvokeAsync :

  • InvokeConstructorAsync(string identifier, object?[]? args): Invoca a função de construtor especificada JS de forma assíncrona. A função é invocada com o new operador. No exemplo a seguir, jsInterop.TestClass é uma classe com uma função de construtor e classRef é um IJSObjectReference:

    var classRef = await JSRuntime.InvokeConstructorAsync("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 apenas uma set-propriedade. É lançada uma JSException se a propriedade não existir. O exemplo a seguir retorna um valor de uma propriedade data:

    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 apenas uma get-propriedade. Se a propriedade não estiver definida no objeto de destino, a propriedade será criada. A JSException é lançado se a propriedade existir mas não puder ser gravada, ou quando uma nova propriedade não puder ser adicionada ao objeto. No exemplo a seguir, num é criado em testObject 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 anteriores que usam um CancellationToken argumento ou TimeSpan argumento de tempo limite.

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 :

  • InvokeConstructor(string identifier, object?[]? args): Invoca a função de construtor especificada JS de forma síncrona. A função é invocada com o new operador. No exemplo a seguir, jsInterop.TestClass é uma classe com uma função de construtor e classRef é um IJSInProcessObjectReference:

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    var classRef = inProcRuntime.InvokeConstructor("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 apenas uma set-propriedade. É lançada uma JSException se a propriedade não existir. O exemplo a seguir retorna um valor de uma propriedade data:

    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 apenas uma get-propriedade. Se a propriedade não estiver definida no objeto de destino, a propriedade será criada. A JSException é lançado se a propriedade existir mas não puder ser gravada, ou quando uma nova propriedade não puder ser adicionada ao objeto. No exemplo a seguir, num é criado em testObject 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 seguintes seções do artigo Chamar funções JavaScript dos métodos .NET :

Blazor WebAssembly Perfil de desempenho e contadores de diagnóstico

Novos contadores de diagnóstico e perfil de desempenho estão disponíveis para Blazor WebAssembly aplicativos. Para obter mais informações, consulte os seguintes artigos:

Ativos estáticos da estrutura pré-carregados Blazor

Em Blazor Web Apps, os ativos estáticos da estrutura são pré-carregados automaticamente usando Link cabeçalhos, o que permite que o navegador pré-carregue recursos antes que a página inicial seja buscada e renderizada. Nas aplicações autónomas Blazor WebAssembly, os recursos do framework são programados para download e cache de alta prioridade logo no início do processamento da página pelo navegador index.html.

Para mais informações, consulte ASP.NET Core Blazor ficheiros estáticos.

Opte por evitar uma NavigationException renderização estática do lado do servidor com NavigationManager.NavigateTo

A chamada NavigationManager.NavigateTo durante a renderização estática do lado do servidor (SSR estático) gera um NavigationException, interrompendo a execução antes de ser convertido em uma resposta de redirecionamento. Isso pode causar confusão durante a depuração e é inconsistente com o comportamento de renderização interativa, onde o código depois NavigateTo continua a ser executado normalmente.

No .NET 10, você pode definir a <BlazorDisableThrowNavigationException> propriedade MSBuild como true no arquivo de projeto do aplicativo para evitar lançar a exceção durante o SSR estático:

<PropertyGroup>
  <BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>

Com a propriedade MSBuild definida, a chamada NavigationManager.NavigateTo durante o SSR estático não gera mais um NavigationExceptionarquivo . Em vez disso, comporta-se de forma consistente com a renderização interativa, executando a navegação sem gerar uma exceção. O código após NavigationManager.NavigateTo é executado antes que o redirecionamento ocorra.

O modelo de projeto .NET 10 Blazor Web App define a propriedade MSBuild como true por padrão. Recomendamos que os aplicativos que atualizam para o .NET 10 usem a nova propriedade MSBuild e evitem o comportamento anterior.

Se a propriedade MSBuild for usada, o código que dependia de NavigationException ser lançado deve ser atualizado. Na interface do usuário padrão BlazorIdentity do Blazor Web App modelo de projeto antes do lançamento do .NET 10, o IdentityRedirectManager lança uma InvalidOperationException chamada RedirectTo após para garantir que o método não foi invocado durante a renderização interativa. Essa exceção e os [DoesNotReturn] atributos agora devem ser removidos quando a propriedade MSBuild é usada. Para obter mais informações, consulte Migrar do ASP.NET Core no .NET 9 para o ASP.NET Core no .NET 10.

Blazor roteador tem um NotFoundPage parâmetro

Blazor agora fornece uma maneira aprimorada de exibir uma página "Não encontrado" ao navegar para uma página inexistente. Você pode especificar uma página para renderizar quando NavigationManager.NotFound (descrito na próxima seção) for invocado, passando um tipo de página para o componente Router usando o parâmetro NotFoundPage. O recurso suporta encaminhamento, funciona através do middleware de reexecução de páginas de códigos de estado e é compatível mesmo em cenários não Blazor.

O NotFound fragmento de renderização (<NotFound>...</NotFound>) não é suportado no .NET 10 ou posterior.

<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. Esta página é renderizada automaticamente sempre que NavigationManager.NotFound é chamada em seu aplicativo, facilitando o tratamento de rotas ausentes com uma experiência de usuário consistente.

Para mais informações, consulte ASP.NET Core Blazor navegação.

Respostas não encontradas usando NavigationManager para SSR estático e renderização interativa global

O NavigationManager agora 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): a chamada NotFound define o código de status HTTP como 404.

  • Renderização interativa: sinaliza o Blazor router (Router componente) para renderizar o conteúdo Não Encontrado.

  • Renderização de streaming: se a navegação aprimorada estiver ativa, a renderização de streaming renderizará o conteúdo Não Encontrado sem recarregar a página. Quando a navegação aprimorada é bloqueada, a estrutura redireciona para o conteúdo Não encontrado com uma atualização de página.

A renderização de streaming só pode renderizar componentes que tenham uma rota, como uma NotFoundPage atribuição (NotFoundPage="...") ou uma atribuição de página de middleware de reexecução de páginas de código de status (UseStatusCodePagesWithReExecute). DefaultNotFound O conteúdo 404 ("Not found" texto simples) não tem uma rota, portanto, não pode ser usado durante a renderização de streaming.

NavigationManager.NotFound A renderização de conteúdo usa o seguinte, independentemente de a resposta ter sido iniciada ou não (na ordem):

  • Se NotFoundEventArgs.Path estiver definido, renderize o conteúdo da página atribuída.
  • Se Router.NotFoundPage estiver definido, renderize a página atribuída.
  • Uma página de middleware de reexecução de páginas de código de status, se configurada.
  • Nenhuma ação se nenhuma das abordagens anteriores for adotada.

O middleware de reexecução de páginas de código de status com UseStatusCodePagesWithReExecute tem precedência sobre problemas de roteamento de endereços baseados em navegador, como um URL incorreto digitado na barra de endereço do navegador ou a seleção de um link que não tenha ponto de extremidade no aplicativo.

Você pode usar o NavigationManager.OnNotFound evento para notificações quando NotFound é invocado.

Para mais informações e exemplos, veja ASP.NET Core navegaçãoBlazor.

Suporte para respostas de não encontrado (404) em apps sem roteador

As aplicações que implementam um roteador personalizado podem usar NavigationManager.NotFound. Há duas maneiras de informar ao renderizador qual página deve ser renderizada quando NavigationManager.NotFound é chamada:

A abordagem recomendada que funciona independentemente do estado de resposta é chamar UseStatusCodePagesWithReExecute. Quando NavigationManager.NotFound é chamado, o middleware renderiza o caminho passado para o método.

app.UseStatusCodePagesWithReExecute(
    "/not-found", createScopeForStatusCodePages: true);

Se não quiser usar UseStatusCodePagesWithReExecute, a aplicação ainda suporta NavigationManager.NotFound para respostas que já foram iniciadas. Assine OnNotFoundEvent no roteador e atribua o caminho da página Não encontrado para NotFoundEventArgs.Path informar ao renderizador qual conteúdo renderizar quando NavigationManager.NotFound for chamado.

CustomRouter.razor:

@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Http
@implements IDisposable
@inject NavigationManager NavigationManager

@code {
    protected override void OnInitialized() =>
        NavigationManager.OnNotFound += OnNotFoundEvent;

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

    private void OnNotFoundEvent(object sender, NotFoundEventArgs e)
    {
        // Only execute the logic if HTTP response has started
        // because setting NotFoundEventArgs.Path blocks re-execution
        if (HttpContext?.Response.HasStarted == false)
        {
            return;
        }

        e.Path = GetNotFoundRoutePath();
    }

    // Return the path of the Not Found page that you want to display
    private string GetNotFoundRoutePath()
    {
        ...
    }

    public void Dispose() => NavigationManager.OnNotFound -= OnNotFoundEvent;
}

Se utilizar ambas as abordagens na sua aplicação, o caminho Não Encontrado especificado no OnNotFoundEvent manipulador terá precedência sobre o caminho configurado no middleware de nova execução.

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, consulte ASP.NET Core Blazor práticas recomendadas de desempenho.

Suporte ao empacotador JavaScript

BlazorA saída de compilação não é compatível com empacotadores JavaScript, como Gulp, Webpack e Rollup. Blazor agora pode produzir uma produção amigável para o bundler no momento da publicação, ao definir a propriedade MSBuild WasmBundlerFriendlyBootConfig como true.

Para obter mais informações, consulte Hospedar e implantar ASP.NET Core Blazor.

Blazor WebAssembly Pré-carregamento de ativos estáticos em Blazor Web Apps

Substituímos <link> cabeçalhos por um ResourcePreloader componente (<ResourcePreloader />) para pré-carregar recursos WebAssembly em Blazor Web Apps. Isso permite que a configuração do caminho base do aplicativo (<base href="..." />) identifique corretamente a raiz do aplicativo.

A remoção do componente desativa o recurso se o aplicativo estiver usando uma loadBootResource rotina de retorno para modificar URLs.

O Blazor Web App modelo adota o recurso por padrão no .NET 10, e os aplicativos que atualizam para o .NET 10 podem implementar o recurso colocando o ResourcePreloader componente após a marca de URL base (<base>) no conteúdo principal do App componente (App.razor):

<head>
    ...
    <base href="/" />
+   <ResourcePreloader />
    ...
</head>

Para mais informações, consulte Blazor.

Validação de formulário melhorada

Blazor Agora tem recursos aprimorados de validação de formulário, incluindo suporte para validação de propriedades de objetos aninhados e itens de coleção.

Para criar um formulário validado, use um DataAnnotationsValidator componente dentro de um EditForm componente, como antes.

Para optar pelo novo recurso de validação:

  1. Chame o método de extensão AddValidation no ficheiro Program onde os serviços são registados.
  2. Declare os tipos de modelo de formulário em um arquivo de classe C#, não em um Razor componente (.razor).
  3. Anote o tipo de modelo de formulário raiz com o [ValidatableType] atributo.

Sem seguir as etapas anteriores, o comportamento de validação permanece o mesmo que nas versões anteriores do .NET.

O exemplo a seguir demonstra pedidos de clientes com a validação de formulário aprimorada (detalhes omitidos para brevidade):

No Program.cs, chame AddValidation a coleção de serviços para habilitar o novo comportamento de validação:

builder.Services.AddValidation();

Na classe a seguir Order , o [ValidatableType] atributo é necessário no tipo de modelo de nível superior. Os outros tipos são descobertos automaticamente. OrderItem e ShippingAddress não são mostrados para simplificar, mas a validação de tipos aninhados e de coleções funciona da mesma maneira nesses tipos, caso fossem apresentados.

Order.cs:

[ValidatableType]
public class Order
{
    public Customer Customer { get; set; } = new();
    public List<OrderItem> OrderItems { get; set; } = [];
}

public class Customer
{
    [Required(ErrorMessage = "Name is required.")]
    public string? FullName { get; set; }

    [Required(ErrorMessage = "Email is required.")]
    public string? Email { get; set; }

    public ShippingAddress ShippingAddress { get; set; } = new();
}

No seguinte componente OrderPage, o DataAnnotationsValidator está presente no EditForm.

OrderPage.razor:

<EditForm Model="Model">
    <DataAnnotationsValidator />

    <h3>Customer Details</h3>
    <div class="mb-3">
        <label>
            Full Name
            <InputText @bind-Value="Model!.Customer.FullName" />
        </label>
        <ValidationMessage For="@(() => Model!.Customer.FullName)" />
    </div>

    @* ... form continues ... *@
</EditForm>

@code {
    public Order? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    // ... code continues ...
}

O requisito para declarar os tipos de modelo fora dos componentes (Razor arquivos) é devido ao fato de que tanto a nova funcionalidade de validação quanto o próprio compilador .razor estão usando um gerador de código-fonte Razor. Atualmente, a saída de um gerador de fonte não pode ser usada como entrada para outro gerador de fonte.

O suporte de validação agora inclui:

  • A validação de objetos complexos aninhados e coleções agora é suportada.
    • Isso inclui regras de validação definidas por atributos de propriedade, atributos de classe e a IValidatableObject implementação.
    • O [SkipValidation] atributo pode excluir propriedades ou tipos da validação.
  • A validação agora usa uma implementação baseada em gerador de código-fonte em vez de implementação baseada em reflexão para melhorar o desempenho e a compatibilidade com a compilação ahead-of-time (AOT).

O DataAnnotationsValidator componente agora tem a mesma ordem de validação e comportamento de curto-circuito que System.ComponentModel.DataAnnotations.Validatoro . As seguintes regras são aplicadas ao validar uma instância do tipo T:

  1. As propriedades de membro de são validadas, incluindo a validação recursiva de T objetos aninhados.
  2. Os atributos de nível de tipo de T são validados.
  3. O IValidatableObject.Validate método é executado, se T implementá-lo.

Se uma das etapas anteriores produzir um erro de validação, as etapas restantes serão ignoradas.

Usar modelos de validação de um assembly diferente

Você pode validar formulários com modelos definidos em um assembly diferente, como uma biblioteca ou o .Client projeto de um Blazor Web App, criando um método na biblioteca ou no projeto .Client que recebe uma instância de IServiceCollection como argumento e chama AddValidation nela.

  • Na aplicação, chame tanto o método como AddValidation.

Para obter mais informações e um exemplo, consulte ASP.NET Validação de formulários principaisBlazor.

Cache personalizado Blazor e BlazorCacheBootResources propriedade MSBuild removida

Agora que todos os Blazor arquivos do lado do cliente são identificados unicamente e armazenados em cache pelo navegador, o mecanismo personalizado de cache de Blazor e a propriedade MSBuild BlazorCacheBootResources foram removidos do framework. Se o arquivo de projeto do lado do cliente contiver a propriedade MSBuild, remova a propriedade, pois ela não terá mais efeito:

- <BlazorCacheBootResources>...</BlazorCacheBootResources>

Para obter mais informações, consulte ASP.NET Core Blazor WebAssembly falhas de caching e verificação de integridade.

Suporte à API de autenticação da Web (chave de acesso) para o ASP.NET Core Identity

O suporte à API de Autenticação da Web (WebAuthn), amplamente conhecido como chaves de acesso, é um método de autenticação moderno e resistente a phishing que melhora a segurança e a experiência do usuário, aproveitando a criptografia de chave pública e a autenticação baseada em dispositivo. ASP.NET Core Identity agora suporta autenticação por chave de acesso baseada nos padrões WebAuthn e FIDO2. Esse recurso permite que os usuários entrem sem senhas, usando métodos de autenticação seguros e baseados em dispositivos, como biometria ou chaves de segurança.

O Blazor Web App modelo de projeto fornece gerenciamento de chaves de acesso e funcionalidade de login prontos para uso.

Para obter mais informações, consulte os seguintes artigos:

Persistência do estado do circuito

Durante a renderização do lado do servidor, Blazor Web Apps agora pode persistir o estado da sessão (circuito) de um usuário quando a conexão com o servidor é perdida por um longo período de tempo ou pausada proativamente, desde que uma atualização de página inteira não seja acionada. Isso permite que os usuários retomem sua sessão sem perder o trabalho não salvo nos seguintes cenários:

  • Limitação de recursos de separadores do navegador
  • Utilizadores de dispositivos móveis que mudam de aplicações
  • Interrupções na rede
  • Gerenciamento proativo de recursos (pausando circuitos inativos)
  • Navegação melhorada

Para obter mais informações, consulte ASP.NET Blazor Core server-side state management.

Hot Reload para Blazor WebAssembly e .NET no WebAssembly

O SDK migrou para um Hot Reload de uso geral para cenários WebAssembly. Há uma nova propriedade WasmEnableHotReload MSBuild que é true por padrão para a configuração (Debug) que habilita o Configuration == "Debug" Hot Reload.

Para outras configurações com nomes de configuração personalizados, defina o valor como true no arquivo de projeto do aplicativo para habilitar o Hot Reload:

<PropertyGroup>
  <WasmEnableHotReload>true</WasmEnableHotReload>
</PropertyGroup>

Para desativar o Hot Reload para a Debug configuração, defina o valor como false:

<PropertyGroup>
  <WasmEnableHotReload>false</WasmEnableHotReload>
</PropertyGroup>

Atualização do registo do service worker do PWA para evitar problemas de cache

O registo do Blazor service worker no modelo de projeto Aplicativo Web Progressivo (PWA) agora inclui a updateViaCache: 'none' opção, que evita problemas de cache durante as atualizações do service worker.

- navigator.serviceWorker.register('service-worker.js');
+ navigator.serviceWorker.register('service-worker.js', { updateViaCache: 'none' });

A opção garante que:

  • O navegador não usa versões em cache do script do service worker.
  • As atualizações do service worker são aplicadas de forma confiável sem serem bloqueadas pelo caching HTTP.
  • Os aplicativos PWA podem atualizar seus trabalhadores de serviço de forma mais previsível.

Isso resolve problemas de cache que podem impedir que as atualizações do service worker sejam aplicadas corretamente, o que é particularmente importante para PWAs que dependem de service workers para a funcionalidade offline.

Recomendamos definir a opção como none em todos os PWAs, incluindo aqueles destinados ao .NET 9 ou versões anteriores.

Extensibilidade de serialização para estado de componente persistente

Implemente um serializador personalizado com PersistentComponentStateSerializer<T>. Sem um serializador personalizado registrado, a serialização retorna à serialização JSON existente.

O serializador personalizado é registrado no arquivo do Program aplicativo. No exemplo a seguir, o CustomUserSerializer é registado para o tipo TUser.

builder.Services.AddSingleton<PersistentComponentStateSerializer<TUser>, 
    CustomUserSerializer>();

O tipo é automaticamente persistido e restaurado com o serializador personalizado:

[PersistentState] 
public User? CurrentUser { get; set; } = new();

OwningComponentBase agora implementa IAsyncDisposable

OwningComponentBase agora inclui suporte para eliminação assíncrona, melhorando o gerenciamento de recursos. Existem métodos novos DisposeAsync e DisposeAsyncCore com um método atualizado Dispose para lidar com a eliminação síncrona e assíncrona do escopo do serviço.

Novo InputHidden componente para lidar com campos de entrada ocultos em formulários

O novo InputHidden componente fornece um campo de entrada oculto para armazenar valores de cadeia de caracteres.

No exemplo a seguir, um campo de entrada oculto é criado para a propriedade do Parameter formulário. Quando o formulário é enviado, o valor do campo oculto é exibido:

<EditForm Model="Parameter" OnValidSubmit="Submit" FormName="InputHidden Example">
    <InputHidden id="hidden" @bind-Value="Parameter" />
    <button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
    <p>Hello @Parameter!</p>
}

@code {
    private bool submitted;

    [SupplyParameterFromForm] 
    public string Parameter { get; set; } = "stranger";

    private void Submit() => submitted = true;
}

Suporte de estado de componente persistente para navegação aprimorada

Blazor agora suporta a manipulação do estado persistente do componente durante a navegação aprimorada. O estado persistido durante a navegação aprimorada pode ser lido por componentes interativos na página.

Por padrão, o estado do componente persistente só é carregado por componentes interativos quando eles são inicialmente carregados na página. Isso impede que estados importantes, como dados em um formulário da Web editado, sejam substituídos se eventos de navegação aprimorados adicionais para a mesma página ocorrerem após o carregamento do componente.

Se os dados forem somente leitura e não forem alterados com frequência, opte por permitir atualizações durante a navegação aprimorada definindo AllowUpdates = true o [PersistentState] atributo. Isso é útil para cenários como a exibição de dados armazenados em cache que são caros para buscar, mas não mudam com frequência. O exemplo a seguir demonstra o uso de para dados de AllowUpdates previsão do tempo:

[PersistentState(AllowUpdates = true)]
public WeatherForecast[]? Forecasts { get; set; }

protected override async Task OnInitializedAsync()
{
    Forecasts ??= await ForecastService.GetForecastAsync();
}

Para ignorar a restauração do estado durante a pré-renderização, defina RestoreBehavior como SkipInitialValue:

[PersistentState(RestoreBehavior = RestoreBehavior.SkipInitialValue)]
public string NoPrerenderedData { get; set; }

Para ignorar a restauração do estado durante a reconexão, defina RestoreBehavior como SkipLastSnapshot. Isso pode ser útil para garantir dados atualizados após a reconexão:

[PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)]
public int CounterNotRestoredOnReconnect { get; set; }

Chamada PersistentComponentState.RegisterOnRestoring para registrar um retorno de chamada para controlar imperativamente como o estado é restaurado, semelhante a como PersistentComponentState.RegisterOnPersisting fornece controle total de como o estado é persistente.

Blazor WebAssembly respeita a configuração atual da cultura da interface de utilizador

No .NET 9 ou anterior, os aplicativos autônomos Blazor WebAssembly carregam recursos de globalização da interface do usuário com base no CultureInfo.DefaultThreadCurrentCulture. Se você quiser carregar adicionalmente dados de globalização para sua cultura de localização definida pelo CultureInfo.DefaultThreadCurrentUICulture, atualize o aplicativo para .NET 10 ou posterior.

Blazor Hybrid

Esta seção descreve as novas funcionalidades para Blazor Hybrid.

Novo .NET MAUIBlazor Hybrid com artigo e exemplo de Blazor Web App e ASP.NET Core Identity

Um novo artigo e aplicativo de exemplo foi adicionado para .NET MAUIBlazor Hybrid e Web App usando ASP.NET Core Identity.

Para obter mais informações, consulte os seguintes recursos:

SignalR

Esta seção descreve as novas funcionalidades para SignalR.

APIs mínimas

Esta secção descreve novas funcionalidades para APIs Mínimas.

Tratar a cadeia de caracteres vazia no envio do formulário como nula para tipos de valor anuláveis

Ao usar o [FromForm] atributo com um objeto complexo em APIs Mínimas, os valores de string vazios num post de formulário são agora convertidos para null , em vez de causarem uma falha na análise sintática. Este comportamento corresponde à lógica de processamento para posts de formulário não associados 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 esta mudança!

Suporte de validação em APIs mínimas

O suporte para validação em APIs mínimas já está disponível. Esse recurso permite que você solicite a validação dos dados enviados para seus endpoints de API. Habilitar a validação permite que o tempo de execução do ASP.NET Core execute quaisquer validações definidas no:

  • Query
  • Header
  • Corpo de solicitação

As validações são definidas usando atributos no DataAnnotations namespace. Os desenvolvedores personalizam o comportamento do sistema de validação:

  • Criação de implementações de atributos personalizados [Validation] .
  • Implementação da interface IValidatableObject para lógica de validação complexa.

Se a validação falhar, o ambiente de execução retornará uma resposta 400 Bad Request com detalhes dos erros de validação.

Habilite o suporte de validação interno para APIs mínimas

Habilite o suporte de validação interno 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 os tipos que são definidos em manipuladores de API mínimos ou como tipos base de tipos definidos em manipuladores de API mínima. Um filtro de ponto de extremidade realiza a validação nos referidos tipos e é adicionado a 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();

Note

Várias pequenas melhorias e correções foram feitas no gerador de validação de APIs mínimas introduzido no ASP.NET Core para .NET 10. Para oferecer suporte a aprimoramentos futuros, as APIs de resolvedor de validação subjacentes 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 registo

APIs mínimas também oferecem 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 utilizar tipos de registo como parâmetros em endpoints de API minimalista, os atributos de validação são aplicados automaticamente da mesma forma que os tipos de classe.

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

Integração mínima de validação de API com IProblemDetailsService

As respostas a erro da lógica de validação para APIs Mínimas podem agora ser personalizadas por uma IProblemDetailsService implementação fornecida na coleção de serviços de aplicação (contentor de Injeção de Dependências). Isso permite respostas de erro mais consistentes e específicas do usuário.

Suporte para Server-Sent Events (SSE)

ASP.NET Core agora oferece suporte ao retorno de um resultado ServerSentEvents usando a API TypedResults.ServerSentEvents . Esse recurso é suportado em APIs mínimas e aplicativos baseados em controlador.

Server-Sent Events é uma tecnologia de push de servidor que permite que um servidor envie um fluxo de mensagens de eventos para um cliente através 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 uma carga útil 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 para o cliente.

O exemplo a seguir ilustra como usar a TypedResults.ServerSentEvents API para retornar um fluxo de eventos de frequê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:

APIs de validação movidas para Microsoft.Extensions.Validation

As APIs de validação foram movidas para o Microsoft.Extensions.Validation namespace e pacote NuGet. Essa alteração torna as APIs utilizáveis fora de ASP.NET cenários HTTP principais. As APIs públicas e o comportamento permanecem inalterados — apenas o pacote e o namespace são diferentes. Os projetos existentes não exigem alterações de código, pois as referências antigas redirecionam para a nova implementação.

Validação aprimorada para classes e registros

Os atributos de validação agora podem ser aplicados a classes e registros com geração de código consistente e comportamento de validação. Esse aprimoramento melhora a flexibilidade ao projetar modelos usando registros em aplicativos ASP.NET Core.

Contribuição da comunidade: Graças a @marcominerva!

OpenAPI

Esta seção descreve os novos recursos para OpenAPI.

Suporte a OpenAPI 3.1

ASP.NET Core adicionou suporte para gerar documentos OpenAPI versão 3.1 no .NET 10. Apesar da mudança na versão secundária, o OpenAPI 3.1 é uma atualização significativa da especificação OpenAPI, em particular devido ao suporte total para JSON Schema draft 2020-12.

Algumas das alterações que você verá no documento OpenAPI gerado incluem:

  • Os 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-chave type cujo valor é uma matriz que inclui null como um dos tipos.
  • Propriedades ou parâmetros definidos como um C# int ou long agora aparecem no documento OpenAPI gerado sem o type: integer campo e têm um pattern campo limitando o valor a dígitos. Isso acontece quando a propriedade NumberHandling no JsonSerializerOptions é definida como AllowReadingFromString, que é o padrão para aplicativos Web ASP.NET Core. Para habilitar o C# int e long a serem representados no documento OpenAPI como type: integer, defina a propriedade NumberHandling como Strict.

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 delegate de AddOpenApi:

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

Ao gerar o documento OpenAPI em tempo de compilação, a versão OpenAPI pode ser selecionada definindo o --openapi-versionOpenApiGenerateDocumentsOptions no item 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 a OpenAPI 3.1 foi principalmente adicionado nos seguintes PR .

Alterações significativas na 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 em relação à versão anterior. As alterações disruptivas podem ter impacto nas suas aplicações se estas tiverem algum documento, operação ou transformador de esquema. As alterações significativas nesta iteração incluem o seguinte:

  • As entidades dentro do documento OpenAPI, como operações e parâmetros, são digitadas como interfaces. Existem implementações concretas para as variantes integradas e referenciadas de uma entidade. Por exemplo, um IOpenApiSchema pode ser um OpenApiSchema embutido ou um OpenApiSchemaReference que aponta para um esquema definido em outro lugar do documento.
  • A propriedade Nullable foi removida do tipo OpenApiSchema. Para determinar se um tipo é anulável, avalie se a propriedade OpenApiSchema.Type define JsonSchemaType.Null.

Uma das mudanças mais significativas é que a classe OpenApiAny foi abandonada em favor do uso JsonNode diretamente. Os transformadores que usam OpenApiAny precisam ser atualizados para usar JsonNode. A diferenciação abaixo 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 quando apenas se está a configurar a versão OpenAPI para 3.0.

OpenAPI em YAML

ASP.NET agora suporta servir o documento OpenAPI gerado no formato YAML. O YAML pode ser mais conciso do que o JSON, eliminando chavetas e aspas quando estas podem ser inferidas. O YAML também suporta cadeias de caracteres de várias linhas, o que pode ser útil para descrições longas.

Para configurar um aplicativo para servir o 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 está disponível apenas para a OpenAPI servida a partir do endpoint OpenAPI.
  • A geração de documentos OpenAPI no formato YAML durante a compilação será adicionada numa pré-visualização futura.

Veja este PR, que adicionou suporte para disponibilizar o documento OpenAPI gerado em formato YAML.

Descrição da resposta em ProducesResponseType para controladores de API

O ProducesAttribute, ProducesResponseTypeAttribute, e ProducesDefaultResponseTypeAttribute agora aceita um parâmetro de cadeia de caracteres opcional, Description, que define a descrição da resposta:

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

Dados OpenAPI gerados:

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

Essa funcionalidade é suportada em controladores de API e APIs mínimas. Para APIs mínimas, a Description propriedade é definida corretamente mesmo quando o tipo do atributo e o tipo de retorno inferido não são uma correspondência exata.

Contribuição comunitária (dotnet/aspnetcore #58193) por Sander ten Brinke.

Preencher comentários em documentos XML no documento OpenAPI

A geração do documento OpenAPI no ASP.NET Core agora incluirá metadados provenientes dos comentários XML referentes às definições de método, classe e membro no documento OpenAPI. Você deve habilitar comentários de documentos XML em seu arquivo de projeto para usar esse recurso. Você pode fazer isso adicionando a seguinte propriedade ao seu arquivo de projeto:

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

Em tempo de construção, o pacote OpenAPI aproveitará um gerador de código-fonte para descobrir comentários XML no assembly do aplicativo atual e quaisquer referências de projeto e emitirá código-fonte para inseri-los no documento por meio de um transformador de documentos OpenAPI.

Note que o processo de compilação em C# não capta comentários de documentos XML colocados em expressões lambda, por isso, para usar comentários de documentos XML para adicionar metadados a um endpoint de API Minimal, deve definir o handler de endpoint como um método, colocar os comentários do documento XML no método e depois referenciar esse método a partir desse MapXXX método. Por exemplo, para usar comentários de documentos XML para adicionar metadados a um endpoint Minimal API 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 documentos 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 <summary>, <remarks>e <param> comentários do documento XML. Para obter mais informações sobre comentários de documentos XML, incluindo todas as tags suportadas, consulte a documentação do C#.

Como a funcionalidade principal é fornecida por meio de um gerador de código-fonte, ela pode ser desabilitada adicionando o seguinte MSBuild ao seu 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 código-fonte processa 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 ASP.NET Core (AOT nativa)

O modelo de projeto ASP.NET Core Web API (Native AOT) (nome webapiaotabreviado) agora inclui suporte para 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.

Contribuição comunitária (dotnet/aspnetcore #60337) por Sander ten Brinke.

Suporte para IOpenApiDocumentProvider no contêiner DI.

ASP.NET Core no .NET 10 oferece suporte a IOpenApiDocumentProvider no contêiner de injeção de dependência (DI). 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 podia ser feita usando HostFactoryResolver com uma implementação no-op IServer . O novo recurso simplifica esse processo ao fornecer uma API simplificada inspirada pelo AspireIDistributedApplicationPublisher, que faz parte do framework do 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 ampla gama de tipos.
  • Ele lida com cenários mais complexos.
  • Ele graciosamente ignora 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, de erros de compilação para metadados ausentes.

Além disso, o processamento de comentários de documentos XML agora pode ser configurado para acessar comentários XML em outras bibliotecas. Isso é útil para gerar documentação para tipos que são definidos fora do assembly atual, como o ProblemDetails tipo no Microsoft.AspNetCore.Http namespace.

Essa configuração é feita com diretivas no arquivo de construção do projeto. O exemplo a seguir mostra como configurar o gerador de comentários XML para acessar comentários XML para tipos no Microsoft.AspNetCore.Http assembly, que inclui a ProblemDetails classe.

<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.

Tratamento unificado de IDs de documentação no gerador de comentários OpenAPI XML

Comentários de documentação XML de assemblies referenciados são mesclados corretamente, mesmo quando suas IDs de documentação incluem sufixos de tipo de retorno. Como resultado, todos os comentários XML válidos são incluídos de forma confiável na documentação OpenAPI gerada, melhorando a precisão e integridade da documentação para APIs usando assemblies referenciados.

Os parâmetros de enumeração dos dados de formulário utilizam o tipo de enumeração real no OpenAPI.

Os parâmetros de dados de formulário em ações do controlador MVC agora geram metadados OpenAPI usando o tipo enum real, em vez de usar como padrão a string.

Contribuição da comunidade: Graças a @ascott18!

Suporte para geração de OpenApiSchemas em transformadores

Os desenvolvedores agora podem gerar um esquema para um tipo C# usando a mesma lógica de geração de documentos OpenAPI do ASP.NET Core e adicioná-lo ao documento OpenAPI. O esquema pode então ser referenciado de outro lugar no documento OpenAPI.

O contexto passado para 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 do transformador Operation e Schema. Isso permite que qualquer transformador adicione um esquema ao documento OpenAPI usando o método do AddComponent documento.

Example

Para usar este recurso em um documento, operação ou transformador de esquema, crie o esquema utilizando o método GetOrCreateSchemaAsync fornecido no contexto e adicione-o ao documento OpenAPI utilizando o método do documento AddComponent.

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)
                }
            }
        };
    });
});

Transformadores de operações OpenAPI específicos de endpoints

Os transformadores de operação específicos do ponto de extremidade permitem a personalização refinada da documentação OpenAPI para pontos de extremidade de rota individuais. Esse recurso permite que os desenvolvedores personalizem metadados e descrições Swagger/OpenAPI por ação ou por rota, aprimorando a extensibilidade para cenários avançados de API.

Para obter detalhes de implementação e exemplos de código, consulte Personalizar documentos OpenAPI.

Atualize Microsoft.OpenApi para 2.0.0

A Microsoft.OpenApi biblioteca usada para a geração de documentos OpenAPI no ASP.NET Core foi atualizada para a versão 2.0.0 (GA).

Alterações significativas na versão 2.0.0

As seguintes alterações de quebra foram introduzidas nas versões de pré-visualização e permanecem na versão GA. Isso afeta principalmente os usuários que implementam transformadores de documento, operação ou esquema:

Com a atualização para a versão GA, não são esperadas mais alterações na geração de documentos OpenAPI.

Aprimoramentos na geração de esquema OpenAPI

Tipos anuláveis de modelo usando oneOf no esquema OpenAPI

A geração de esquema OpenAPI para tipos anuláveis foi melhorada usando o oneOf padrão em vez da propriedade nullable para tipos e coleções complexos. A implementação:

  • Usa oneOf com null e o esquema de tipo real para tipos complexos anuláveis em esquemas de solicitação e resposta.
  • Deteta a anulabilidade de parâmetros, propriedades e tipos de retorno usando reflexão e NullabilityInfoContext.
  • Remove tipos nulos de esquemas componentizados para evitar duplicação.

Correções e melhorias na resolução de referência de esquema

Esta versão melhora a manipulação de esquemas JSON para geração de documentos OpenAPI, resolvendo corretamente as referências de esquema JSON relativas ($ref) no documento de esquema raiz.

Incluir descrições de propriedades ao lado de $ref no esquema OpenAPI

Antes do .NET 10, o ASP.NET Core descartava descrições em propriedades definidas com $ref no documento OpenAPI gerado porque o OpenAPI v3.0 não permitia propriedades irmãs ao lado $ref em definições de esquema. OpenAPI 3.1 agora permite que se possam incluir descrições ao lado de $ref. RC1 adiciona suporte para incluir descrições de propriedade como irmãos no $ref esquema OpenAPI gerado.

Esta foi uma contribuição da comunidade. Obrigado @desjoerd!

Adicionar metadados de comentários XML sobre [AsParameters] tipos ao esquema OpenAPI

A geração de esquemas OpenAPI agora processa os comentários XML nas propriedades das classes de parâmetros para extrair metadados para a documentação.

Excluir métodos HTTP desconhecidos da OpenAPI

A geração de esquema OpenAPI agora exclui métodos HTTP desconhecidos do documento OpenAPI gerado. Os métodos de consulta, que são métodos HTTP padrão, mas não reconhecidos pela OpenAPI, agora são graciosamente excluídos do documento OpenAPI gerado.

Esta foi uma contribuição da comunidade. Obrigado @martincostello!

Melhorar a descrição dos corpos de solicitação do JSON Patch

A geração de esquema OpenAPI para operações JSON Patch agora aplica corretamente o tipo de mídia application/json-patch+json aos corpos de solicitação que usam JSON Patch. Isso garante que o documento OpenAPI gerado reflita com precisão o tipo de mídia esperado para operações de patch JSON. Além disso, o corpo da solicitação do Patch JSON tem um esquema detalhado que descreve a estrutura do documento do Patch JSON, incluindo as operações que podem ser executadas.

Esta foi uma contribuição da comunidade. Obrigado @martincostello!

Use a cultura invariante para a geração de documentos OpenAPI

A geração do documento OpenAPI agora utiliza a cultura invariante para a formatação de números e datas no documento OpenAPI gerado. Isso garante que o documento gerado seja consistente e não varie com base nas configurações de cultura do servidor.

Esta foi uma contribuição da comunidade. Obrigado @martincostello!

Autenticação e autorização

Métricas de autenticação e autorização

Foram adicionadas métricas 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:

  • Authentication:
    • Duração do pedido autenticado
    • Contagem dos desafios
    • Proibir a contagem
    • Contagem de inscrições
    • Contagem de logouts
  • Authorization:
    • Contagem de pedidos que requerem autorização

A seguinte imagem mostra um exemplo da métrica de duração da solicitação autenticada no Aspire painel de instrumentos.

Duração da solicitação autenticada Aspire no painel

Para obter mais informações, consulte Métricas internas do ASP.NET Core.

ASP.NET Principais Identity métricas

ASP.NET Núcleo Identity a observabilidade foi melhorada no .NET 10 com métricas. As métricas são contadores, histogramas e medidores que fornecem medições de séries temporais do comportamento do sistema ou do aplicativo.

Por exemplo, use as novas métricas do ASP.NET Core Identity para observar:

  • Gerenciamento de usuários: novas criações de usuários, alterações de senha e atribuições de função.
  • Tratamento de login/sessão: tentativas de login, login, logouts e usuários usando autenticação de dois fatores.

As novas métricas estão no Microsoft.AspNetCore.Identity medidor:

  • aspnetcore.identity.user.create.duration
  • aspnetcore.identity.user.update.duration
  • aspnetcore.identity.user.delete.duration
  • aspnetcore.identity.user.check_password_attempts
  • aspnetcore.identity.user.generated_tokens
  • aspnetcore.identity.user.verify_token_attempts
  • aspnetcore.identity.sign_in.authenticate.duration
  • aspnetcore.identity.sign_in.check_password_attempts
  • aspnetcore.identity.sign_in.sign_ins
  • aspnetcore.identity.sign_in.sign_outs
  • aspnetcore.identity.sign_in.two_factor_clients_remembered
  • aspnetcore.identity.sign_in.two_factor_clients_forgotten

Para obter mais informações sobre como usar métricas no ASP.NET Core, consulte ASP.NET métricas principais.

Por padrão, pedidos não autenticados e não autorizados feitos a endpoints conhecidos de API protegidos por autenticação cookie agora resultam nas respostas 401 e 403, em vez de redirecionar para um URI de login ou de acesso negado.

Essa alteração foi altamente solicitada, porque o redirecionamento de solicitações não autenticadas para uma página de login geralmente não faz sentido para pontos de extremidade de API que normalmente dependem de códigos de status 401 e 403 em vez de redirecionamentos HTML para comunicar falhas de autenticação.

Os pontos de extremidade de API conhecidos são identificados usando a nova IApiEndpointMetadata interface e os metadados que implementam a nova interface foram adicionados automaticamente ao seguinte:

  • [ApiController] parâmetros de avaliação final
  • Pontos de extremidade de API mínimos que leem corpos de solicitação JSON ou escrevem respostas JSON
  • Endpoints que utilizam tipos de retorno TypedResults
  • SignalR parâmetros de avaliação final

Quando IApiEndpointMetadata estiver presente, o cookie manipulador de autenticação agora retorna códigos de status HTTP apropriados (401 para solicitações não autenticadas, 403 para solicitações proibidas) em vez de redirecionar.

Se pretender evitar este novo comportamento e redirecionar sempre para os URIs de login e de acesso negado para pedidos não autenticados ou não autorizados, independentemente do endpoint de destino, poderá substituir os eventos RedirectToLogin e RedirectToAccessDenied da seguinte maneira:

builder.Services.AddAuthentication()
    .AddCookie(options =>
    {
        options.Events.OnRedirectToLogin = context =>
        {
            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };

        options.Events.OnRedirectToAccessDenied = context =>
        {
            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };
    });

Para obter mais informações sobre esta alteração significativa, consulte o anúncio de alterações significativas no ASP.NET Core.

Miscellaneous

Esta seção descreve vários novos recursos no .NET 10.

Configurar a supressão de diagnósticos do manipulador de exceções

Uma nova opção de configuração foi adicionada ao middleware do manipulador de exceções ASP.NET Core para controlar a saída de diagnóstico: ExceptionHandlerOptions.SuppressDiagnosticsCallback. Esse retorno de chamada é fornecido com o contexto sobre a solicitação e a exceção, permitindo que você adicione lógica que determine se o middleware deve gravar os logs de exceção e a telemetria.

Essa configuração é útil quando você sabe que uma exceção é transitória ou foi manipulada pelo middleware do manipulador de exceções e não deseja que os logs de erros sejam gravados em sua plataforma de observabilidade.

O comportamento padrão do middleware também mudou: ele não grava mais diagnósticos de exceção para exceções manipuladas pelo IExceptionHandler. Com base nos comentários dos utilizadores, o registo de exceções manipuladas ao nível de erro era frequentemente indesejável quando IExceptionHandler.TryHandleAsync retornava true.

Você pode reverter para o comportamento anterior configurando SuppressDiagnosticsCallback:

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    SuppressDiagnosticsCallback = context => false;
});

Para obter mais informações sobre essa alteração disruptiva, consulte https://github.com/aspnet/Announcements/issues/524.

Suporte para o domínio de Top-Level .localhost

O .localhost domínio de topo (TLD) é definido em RFC2606 e RFC6761 como sendo reservado para fins de teste e disponível para os utilizadores utilizarem localmente como fariam com qualquer outro nome de domínio. Isso significa que usar um nome como myapp.localhost localmente que resolve para o endereço de loopback IP é permitido e esperado de acordo com essas RFCs. Além disso, os navegadores evergreen modernos já resolvem automaticamente qualquer *.localhost nome para o endereço de loopback IP (127.0.0.1/::1), tornando-os efetivamente um alias para qualquer serviço já hospedado na localhost máquina local, ou seja, qualquer serviço que http://localhost:6789 responda também responderáhttp://anything-here.localhost:6789, supondo que nenhuma verificação ou imposição de nome de host específico esteja sendo realizada pelo serviço.

ASP.NET Core foi atualizado no .NET 10 Preview 7 para oferecer melhor suporte ao .localhost TLD, de modo que agora pode ser facilmente usado ao criar e executar aplicativos ASP.NET Core em seu ambiente de desenvolvimento local. Ter diferentes aplicativos em execução localmente resolúveis por meio de nomes diferentes permite uma melhor separação de alguns ativos de sites associados a nomes de domínio, por exemplo, cookies, e facilita a identificação de qual aplicativo você está navegando por meio do nome exibido na barra de endereço do navegador.

O servidor HTTP integrado do ASP.NET Core, Kestrel, agora tratará corretamente qualquer nome definido por meio de mecanismos de configuração de ponto final suportados como o endereço de loopback local e, portanto, ligará a ele em vez de todos os endereços externos (ou seja, ligar a *.localhost127.0.0.1 em vez de /::10.0.0.0). Isso inclui a "applicationUrl" propriedade em perfis de inicialização configurados em um arquivo launchSettings.jsone a ASPNETCORE_URLS variável de ambiente. Quando configurado para escutar num(a) endereço, irá registar uma mensagem de informação para os endereços e , para deixar claro que ambos os nomes podem ser usados.

Enquanto os navegadores da Web resolvem *.localhost automaticamente nomes para o endereço de loopback local, outros aplicativos podem tratar *.localhost nomes como nomes de domínio regulares e tentar resolvê-los por meio de sua pilha DNS correspondente. Se a configuração de DNS não resolver *.localhost nomes para um endereço, eles não conseguirão se conectar. Você pode continuar a usar o nome normal localhost para abordar seus aplicativos quando não estiver em um navegador da Web.

O certificado de desenvolvimento ASP.NET Core HTTPS (incluindo o dotnet dev-certs https comando) foi atualizado para garantir que o certificado seja válido para uso com o nome de *.dev.localhost domínio. Depois de instalar o .NET 10 SDK Preview 7, confie no novo certificado de desenvolvedor executando dotnet dev-certs https --trust na linha de comando para garantir que seu sistema esteja configurado para confiar no novo certificado.

O certificado lista o nome como um SAN (Nome Alternativo da Entidade) e não *.dev.localhost porque o uso de um certificado curinga *.localhost para um nome de domínio de nível superior é inválido.

Os modelos de projeto para ASP.NET Core Empty (web) e Blazor Web App (blazor) foram atualizados com uma nova opção que, quando especificada, configura o projeto criado para usar o sufixo de nome de .dev.localhost domínio, combinando-o com o nome do projeto para permitir que o aplicativo seja navegado em um endereço como https://myapp.dev.localhost:5036:

$ dotnet new web -n MyApp --localhost-tld
The template "ASP.NET Core Empty" was created successfully.

Processing post-creation actions...
Restoring D:\src\MyApp\MyApp.csproj:
Restore succeeded.

$ cd .\MyApp\
$ dotnet run --launch-profile https
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://myapp.dev.localhost:7099
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7099/
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://myapp.dev.localhost:5036
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5036/
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\src\local\10.0.1xx\MyApp

Suporte à desserialização Json+PipeReader em APIs MVC e Minimal

PR: https://github.com/dotnet/aspnetcore/pull/62895

Ver https://github.com/dotnet/core/blob/dotnet10-p7-libraries/release-notes/10.0/preview/preview7/libraries.md#pipereader-support-for-json-serializer

MVC, APIs mínimas e os HttpRequestJsonExtensions.ReadFromJsonAsync métodos foram todos atualizados para usar o novo suporte Json+PipeReader sem exigir nenhuma alteração de código dos aplicativos.

Para a maioria dos aplicativos, a adição desse suporte não tem efeito sobre seu comportamento. No entanto, se o aplicativo estiver usando um personalizado JsonConverter, há uma chance de que o conversor não manipule Utf8JsonReader.HasValueSequence corretamente. Isso pode resultar em dados ausentes e erros, como ArgumentOutOfRangeException, ao desserializar.

A solução rápida (especialmente se você não possui o personalizado JsonConverter que está sendo usado) é definir o "Microsoft.AspNetCore.UseStreamBasedJsonParsing"AppContext switch como "true". Esta deve ser uma solução temporária e deve JsonConverter ser atualizada para dar suporte HasValueSequenceao .

Para corrigir JsonConverter implementações, há uma correção rápida que aloca uma matriz do ReadOnlySequence e seria semelhante ao exemplo a seguir:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
    // previous code
}

Há também uma correção mais complicada (mas com desempenho), que envolveria ter um caminho de código separado para o ReadOnlySequence manipulamento:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.HasValueSequence)
    {
        reader.ValueSequence;
        // ReadOnlySequence optimized path
    }
    else
    {
        reader.ValueSpan;
        // ReadOnlySpan optimized path
    }
}

Expulsão automática do pool de memória

Os pools de memória usados pelo Kestrel, IIS e HTTP.sys agora removem automaticamente os blocos de memória quando o aplicativo está ocioso ou sob menos carga. O recurso é executado automaticamente e não precisa ser ativado ou configurado manualmente.

Por que o despejo de memória é importante

Anteriormente, a memória alocada pelo pool permanecia reservada, mesmo quando não estava em uso. Esse recurso libera memória de volta para o sistema quando o aplicativo está ocioso por um período de tempo. Essa remoção reduz o uso geral de memória e ajuda os aplicativos a permanecerem responsivos sob cargas de trabalho variáveis.

Usar métricas de remoção de memória

As métricas foram adicionadas ao pool de memória padrão usado por nossas implementações de servidor. As novas métricas estão sob o nome "Microsoft.AspNetCore.MemoryPool".

Para obter informações sobre métricas e como usá-las, consulte ASP.NET métricas principais.

Gerenciar pools de memória

Além de usar pools de memória de forma mais eficiente, removendo blocos de memória desnecessários, o .NET 10 melhora a experiência de criação de pools de memória. Ele faz isso fornecendo um IMemoryPoolFactory interno e uma MemoryPoolFactory implementação. Ele torna a implementação disponível para seu aplicativo por meio de injeção de dependência.

O exemplo de código a seguir mostra um serviço em segundo plano simples que usa a implementação de fábrica do pool de memória interno para criar pools de memória. Estas piscinas beneficiam da funcionalidade de despejo automático:

public class MyBackgroundService : BackgroundService
{
    private readonly MemoryPool<byte> _memoryPool;

    public MyBackgroundService(IMemoryPoolFactory<byte> factory)
    {
        _memoryPool = factory.Create();
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await Task.Delay(20, stoppingToken);
                // do work that needs memory
                var rented = _memoryPool.Rent(100);
                rented.Dispose();
            }
            catch (OperationCanceledException)
            {
                return;
            }
        }
    }
}

Para usar sua própria fábrica de pool de memória, crie uma classe que implemente IMemoryPoolFactory e registre-a com injeção de dependência, como faz o exemplo a seguir. Os pools de memória criados dessa forma também se beneficiam do recurso de remoção automática:

services.AddSingleton<IMemoryPoolFactory<byte>,
CustomMemoryPoolFactory>();

public class CustomMemoryPoolFactory : IMemoryPoolFactory<byte>
{
    public MemoryPool<byte> Create()
    {
        // Return a custom MemoryPool implementation
        // or the default, as is shown here.
        return MemoryPool<byte>.Shared;
    }
}

Descritores de segurança personalizáveis para HTTP.sys

Agora pode especificar um descritor de segurança personalizado para filas de solicitação HTTP.sys. A nova propriedade RequestQueueSecurityDescriptor em HttpSysOptions permite um controle mais granular sobre os direitos de acesso para a fila de solicitações. Esse controle granular permite que você personalize a segurança para as necessidades do seu aplicativo.

O que pode fazer com o novo imóvel

Uma fila de solicitações no HTTP.sys é uma estrutura de nível de 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 o acesso de usuários ou grupos específicos à fila de solicitações. Isso é útil em cenários em que você deseja restringir ou delegar HTTP.sys tratamento de solicitações no nível do sistema operacional.

Como usar o novo imóvel

A RequestQueueSecurityDescriptor propriedade se aplica somente ao criar uma nova fila de solicitações. 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 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 de 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 fazer referência ao Program class. public partial class Program foi necessário porque a funcionalidade de declaração de nível superior no C# 9 gerou um Program class declarado como interno.

No .NET 10, um gerador de código-fonte é usado para gerar a public partial class Program declaração se o programador não a declarou explicitamente. Além disso, um analisador foi adicionado para detetar quando public partial class Program é declarado explicitamente e aconselhar o desenvolvedor a removê-lo.

Image

Os seguintes PRs contribuíram para esta funcionalidade:

Nova implementação de patch JSON com System.Text.Json

Patch JSON:

  • É um formato padrão para descrever 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 pode ser aplicada para modificar um documento JSON.

Em aplicativos Web, o Patch JSON é comumente 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 patches reduz o tamanho do payload e melhora a eficiência.

Esta versão introduz uma nova implementação de Microsoft.AspNetCore.JsonPatch baseada na serialização System.Text.Json. Este recurso:

  • Alinha-se com as práticas modernas do .NET aproveitando a System.Text.Json biblioteca, que é otimizada para .NET.
  • Fornece melhor desempenho e uso reduzido de memória em comparação com a implementação baseada em Newtonsoft.Json de legado.

Os benchmarks a seguir comparam o desempenho da nova System.Text.Json implementação com a implementação herdada Newtonsoft.Json .

Scenario Implementation Mean Memória alocada
Medições de Desempenho de Aplicações Newtonsoft.JsonPatch 271,924 μs 25 KB
System.Text.JsonPatch 1,584 μs 3 KB
Benchmarks de Desserialização Newtonsoft.JsonPatch 19,261 μs 43 KB
System.Text.JsonPatch 7,917 μs 7 KB

Esses benchmarks destacam ganhos significativos de desempenho e redução do uso de memória com a nova implementação.

Notes:

  • A nova implementação não substitui a implementação herdada. Em particular, a nova implementação não suporta tipos dinâmicos, por exemplo, ExpandoObject.
  • O padrão JSON Patch tem riscos de segurança inerentes. Como esses riscos são inerentes ao padrão JSON Patch, a nova implementação não tenta mitigar os riscos de segurança inerentes. É responsabilidade do desenvolvedor garantir que o documento do patch JSON seja seguro para ser aplicado ao objeto de destino. Para obter mais informações, consulte a seção Mitigando riscos de segurança .

Usage

Para habilitar o suporte ao 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 do 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: Aplicando um JsonPatchDocument

O exemplo a seguir demonstra:

  1. As operações add, replace e remove.
  2. Operações em propriedades aninhadas.
  3. Adicionar um novo item a uma matriz.
  4. Usando um conversor JSON String Enum em um documento JSON Patch.
// 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 processar o JsonPatchDocument, incluindo o comportamento controlado pelas seguintes opções:

  • NumberHandling: Se as propriedades numéricas são lidas a partir 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 são corrigidas.
  • System.Text.Json A desserialização depende do tipo declarado para identificar propriedades elegíveis.

Exemplo: Aplicando 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.

JSON Patch também suporta a test operação. 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.

Important

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 de segurança

Ao usar o Microsoft.AspNetCore.JsonPatch.SystemTextJson pacote, é fundamental entender e mitigar 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.

Important

Esta não é uma lista exaustiva de ameaças. Os desenvolvedores de aplicativos devem conduzir suas próprias revisões de modelo de ameaça 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 enquanto seguem as mitigações recomendadas abaixo, os consumidores desses pacotes podem integrar a funcionalidade JSON Patch em seus aplicativos, minimizando os riscos de segurança.

Os consumidores desses pacotes podem integrar a funcionalidade JSON Patch em seus aplicativos enquanto minimizam os riscos de segurança, incluindo:

  • Execute modelos de ameaças abrangentes para seus próprios aplicativos.
  • Abordar as ameaças identificadas.
  • Siga as atenuações recomendadas nas seções a seguir.
Negação de Serviço (DoS) via amplificação de memória
  • Cenário: Um cliente mal-intencionado envia uma copy operação que duplica gráficos de objetos grandes várias vezes, levando ao consumo excessivo de memória.
  • Impacto: condições potenciais de Out-Of-Memory (OOM), causando interrupções no serviço.
  • Mitigation:
    • Valide os documentos de patch JSON recebidos quanto ao tamanho e estrutura antes de chamar ApplyTo.
    • A validação deve ser específica do aplicativo, mas um exemplo de validação pode ser semelhante ao seguinte:
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 da lógica de negócios
  • Cenário: as operações de patch podem manipular campos com invariantes implícitos (por exemplo, sinalizadores internos, IDs ou campos computados), violando restrições de negócios.
  • Impacto: problemas de integridade de dados e comportamento não intencional do aplicativo.
  • Mitigation:
    • Use objetos POCO com propriedades explicitamente definidas que são seguras para modificar.
    • Evite expor propriedades confidenciais ou críticas de segurança no objeto de destino.
    • Se nenhum objeto POCO for usado, valide o objeto corrigido depois de aplicar operações para garantir que as regras de negócios e invariantes não sejam violadas.
Autenticação e autorização
  • Cenário: Clientes não autenticados ou não autorizados enviam solicitações maliciosas de patch JSON.
  • Impacto: acesso não autorizado para modificar dados confidenciais ou interromper o comportamento do aplicativo.
  • Mitigation:
    • Proteja endpoints que aceitam solicitações de patch JSON com mecanismos de autenticação e autorização adequados.
    • Restrinja o acesso a clientes ou usuários confiáveis com permissões apropriadas.

Detetar se o URL é local usando RedirectHttpResult.IsLocalUrl

Use o novo método auxiliar de RedirectHttpResult.IsLocalUrl(url) para detetar se uma URL é local. Um URL é considerado local se as seguintes condições forem verdadeiras:

As URLs que usam caminhos virtuais"~/" também são locais.

IsLocalUrl é útil para validar URLs antes de redirecioná-los para evitar ataques de redirecionamento abertos.

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

Obrigado @martincostello por esta contribuição!

Alterações de grande impacto

Use os artigos em Alterações significativas no .NET para encontrar alterações significativas que podem ser aplicadas ao atualizar um aplicativo para uma versão mais recente do .NET.