Partilhar via


ASP.NET principais práticas recomendadas

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Por Mike Rousos

Este artigo fornece diretrizes para maximizar o desempenho e a confiabilidade dos aplicativos ASP.NET Core.

Cache agressivamente

O cache é discutido em várias partes deste artigo. Para obter mais informações, consulte Visão geral do cache no ASP.NET Core.

Compreender os caminhos de código ativo

Neste artigo, um de caminho de código quente é definido como um caminho de código que é chamado com freqüência e onde ocorre grande parte do tempo de execução. Os caminhos de código ativo normalmente limitam a expansão e o desempenho do aplicativo e são discutidos em várias partes deste artigo.

Evite bloquear chamadas

ASP.NET aplicativos principais devem ser projetados para processar muitas solicitações simultaneamente. As APIs assíncronas permitem que um pequeno pool de threads lide com milhares de solicitações simultâneas, sem precisar esperar por chamadas bloqueantes. Em vez de aguardar a conclusão de uma tarefa síncrona de longa duração, o thread pode trabalhar em outra solicitação.

Um problema comum de desempenho em aplicativos ASP.NET Core é o bloqueio de chamadas que podem ser assíncronas. Muitas chamadas de bloqueio síncronas levam a esgotamento do Pool de Threads e tempos de resposta degradados.

Não bloqueie execução assíncrona chamando Task.Wait ou Task<TResult>.Result. Não adquira travas em caminhos de código habituais. ASP.NET aplicativos principais têm melhor desempenho quando projetados para executar código em paralelo. Não ligueTask.Run e aguarde imediatamente. ASP.NET Core já executa o código da aplicação em threads normais do Pool de Threads, por isso, chamar Task.Run resulta apenas em um agendamento extra e desnecessário do Pool de Threads. Mesmo que o código agendado bloqueie um thread, Task.Run não impede isso.

  • tornar caminhos de código quente assíncronos.
  • Chame APIs de acesso a dados, E/S e operações de longa execução de forma assíncrona se uma API assíncrona estiver disponível.
  • Não useTask.Run para converter uma API síncrona em assíncrona.
  • tornar as ações do controlador/Razor página assíncronas. Toda a pilha de chamadas é assíncrona para se beneficiar de padrões de async/await.
  • Considere optar por usar agentes de mensagens como o Azure Service Bus para aliviar chamadas demoradas.

Um criador de perfil, como PerfView, pode ser usado para localizar threads frequentemente adicionados ao Pool de Threads. O evento Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start indica um thread adicionado ao pool de threads.

Retornar coleções grandes em várias páginas menores

Uma página da Web não deve carregar grandes quantidades de dados de uma só vez. Ao retornar uma coleção de objetos, considere se isso pode levar a problemas de desempenho. Determine se o design pode produzir os seguintes resultados ruins:

  • OutOfMemoryException ou alto consumo de memória
  • Falta de recursos no pool de threads (ver as seguintes observações sobre IAsyncEnumerable<T>)
  • Tempos de resposta lentos
  • Recolha frequente de lixo

Adicione paginação para atenuar cenários anteriores. Ao usarem os parâmetros de tamanho e índice da página, os desenvolvedores devem privilegiar a conceção que possibilita retornar um resultado parcial. Quando um resultado exaustivo é necessário, a paginação deve ser usada para preencher lotes de resultados de forma assíncrona para evitar o bloqueio de recursos do servidor.

Para obter mais informações sobre paginação e limitação do número de registros retornados, consulte:

Devolver IEnumerable<T> ou IAsyncEnumerable<T>

O retorno de IEnumerable<T> a partir de uma ação resulta na iteração síncrona da coleção pelo serializador. O resultado é o bloqueio de chamadas e um potencial de excesso de carga no pool de threads. Para evitar a enumeração síncrona, use ToListAsync antes de retornar o enumerável.

A partir do ASP.NET Core 3.0, IAsyncEnumerable<T> pode ser usado como uma alternativa ao IEnumerable<T> que enumera de forma assíncrona. Para obter mais informações, consulte Tipos de retorno das ações do controlador.

Minimizar alocações de objetos grandes

O coletor de lixo do .NET Core gerencia a alocação e a liberação de memória automaticamente em aplicativos ASP.NET Core. A coleta automática de lixo geralmente significa que os desenvolvedores não precisam se preocupar com como ou quando a memória é liberada. No entanto, a limpeza de objetos não referenciados leva tempo de CPU, portanto, os desenvolvedores devem minimizar a alocação de objetos em caminhos de código em uso intensivo. A coleta de lixo é especialmente cara em objetos grandes (>= 85.000 bytes). Objetos grandes são armazenados no heap de objetos grandes e exigem uma coleta de lixo completa (geração 2) para serem limpos. Ao contrário das coleções da geração 0 e da geração 1, uma coleção da geração 2 requer uma suspensão temporária da execução do aplicativo. A alocação e a desalocação frequentes de objetos grandes podem causar um desempenho inconsistente.

Recomendações:

  • Considere armazenar em cache objetos grandes que são usados com freqüência. O armazenamento em cache de objetos grandes evita alocações dispendiosas.
  • Faça buffers de agrupamento usando um ArrayPool<T> para armazenar grandes matrizes.
  • Não aloque muitos objetos grandes de curta duração em caminhos de código ativo.

Problemas de memória, como os anteriores, podem ser diagnosticados revisando as estatísticas de coleta de lixo (GC) em PerfView e examinando:

  • Tempo de pausa na recolha de lixo.
  • Qual é a percentagem do tempo do processador que é gasta na recolha de lixo.
  • Quantas coleções de lixo existem nas gerações 0, 1 e 2?

Para obter mais informações, consulte recolha de lixo e desempenho.

Otimize o acesso aos dados e E/S

As interações com um armazenamento de dados e outros serviços remotos geralmente são as partes mais lentas de um aplicativo ASP.NET Core. Ler e gravar dados de forma eficiente é fundamental para um bom desempenho.

Recomendações:

  • chamar todas as APIs de acesso a dados de forma assíncrona.
  • Não recupere mais dados do que o necessário. Escreva consultas para retornar apenas os dados necessários para a solicitação HTTP atual.
  • Considere armazenar em cache os dados que são frequentemente acedidos e que são obtidos de um banco de dados ou serviço remoto, se for aceitável que esses dados estejam ligeiramente desatualizados. Dependendo do cenário, use um MemoryCache ou um DistributedCache. Para obter mais informações, consulte cache de resposta no ASP.NET Core.
  • minimizar as viagens de ida e volta da rede. O objetivo é recuperar os dados necessários em uma única chamada, em vez de várias chamadas.
  • use consultas sem rastreamento no Entity Framework Core ao acessar dados para fins somente leitura. EF Core pode retornar os resultados de consultas sem rastreamento de forma mais eficiente.
  • Faça filtrar e agregar consultas LINQ (com instruções .Where, .Selectou .Sum, por exemplo) para que a filtragem seja executada pelo banco de dados.
  • considere que EF Core resolve alguns operadores de consulta no cliente, o que pode levar a uma execução de consulta ineficiente. Para obter mais informações, consulte Problemas de desempenho na avaliação do cliente.
  • Não use consultas de projeção em coleções, o que pode resultar na execução de consultas SQL "N + 1". Para obter mais informações, consulte Otimização de subconsultas correlacionadas.

As seguintes abordagens podem melhorar o desempenho em aplicativos de alta escala:

Recomendamos medir o impacto das abordagens de alto desempenho anteriores antes de confirmar a base de código. A complexidade adicional das consultas compiladas pode não justificar a melhoria do desempenho.

Os problemas de consulta podem ser detetados revisando o tempo gasto acessando dados com Application Insights ou com ferramentas de criação de perfil. A maioria das bases de dados também disponibiliza estatísticas relativas a consultas frequentemente executadas.

Conexões HTTP de pool com HttpClientFactory

Embora HttpClient implemente a interface IDisposable, ele foi projetado para reutilização. Instâncias HttpClient fechadas deixam os soquetes abertos no estado TIME_WAIT por um curto período de tempo. Se um caminho de código que cria e descarta objetos HttpClient for usado com freqüência, o aplicativo poderá esgotar os soquetes disponíveis. HttpClientFactory foi introduzido no ASP.NET Core 2.1 como uma solução para esse problema. Ele lida com o pool de conexões HTTP para otimizar o desempenho e a confiabilidade. Para obter mais informações, consulte Usar HttpClientFactory para implementar solicitações HTTP resilientes.

Recomendações:

Mantenha caminhos de código comuns rápidos

Você quer que todo o seu código seja rápido. Os caminhos de código mais utilizados são os mais críticos para otimizar. Estes incluem:

  • Os componentes de middleware no pipeline de processamento de solicitações do aplicativo, especialmente o middleware, são executados no início do pipeline. Estes componentes têm um grande impacto no desempenho.
  • Código que é executado para cada solicitação ou várias vezes por solicitação. Por exemplo, log personalizado, manipuladores de autorização ou inicialização de serviços transitórios.

Recomendações:

Concluir tarefas de longa execução fora de solicitações HTTP

A maioria das solicitações para um aplicativo ASP.NET Core pode ser tratada por um controlador ou modelo de página chamando os serviços necessários e retornando uma resposta HTTP. Para algumas solicitações que envolvem tarefas de longa execução, é melhor tornar todo o processo de solicitação-resposta assíncrono.

Recomendações:

  • Não espere que tarefas de longa execução sejam concluídas como parte do processamento de solicitação HTTP comum.
  • considerar lidar com solicitações de longa execução com serviços em segundo plano ou fora de processo, possivelmente com uma Função Azure e/ou usando um agente de mensagens como o Barramento de Serviço do Azure. Concluir o trabalho fora do processo é especialmente benéfico para tarefas com uso intensivo de CPU.
  • usar opções de comunicação em tempo real, como SignalR, para se comunicar com clientes de forma assíncrona.

Reduzir os ativos dos clientes

ASP.NET aplicativos principais com front-ends complexos freqüentemente servem muitos arquivos JavaScript, CSS ou de imagem. O desempenho das solicitações de carga inicial pode ser melhorado por:

  • Agrupamento, que combina vários ficheiros num só.
  • Minifying, que reduz o tamanho dos arquivos removendo espaços em branco e comentários.

Recomendações:

  • Para usar as diretrizes de agrupamento e minificação de , que mencionam ferramentas compatíveis e mostram como usar a tag environment do ASP.NET Core para lidar com ambientes Development e Production.
  • considere outras ferramentas de terceiros, como Webpack, para gerenciamento complexo de ativos de clientes.

Comprimir respostas

Reduzir o tamanho da resposta geralmente aumenta a capacidade de resposta de um aplicativo, muitas vezes dramaticamente. Uma maneira de reduzir o tamanho da carga útil é compactar as respostas de um aplicativo. Para obter mais informações, consulte compactação de resposta.

Use a versão mais recente do ASP.NET Core

Cada nova versão do ASP.NET Core inclui melhorias de desempenho. As otimizações no .NET Core e no ASP.NET Core significam que as versões mais recentes geralmente superam as versões mais antigas. Por exemplo, o .NET Core 2.1 adicionou suporte para expressões regulares compiladas e se beneficiou do Span<T>. ASP.NET Core 2.2 adicionou suporte para HTTP/2. ASP.NET Core 3.0 adiciona muitas melhorias que reduzem o uso de memória e melhoram a taxa de transferência. Se o desempenho for uma prioridade, considere atualizar para a versão atual do ASP.NET Core.

Minimizar exceções

As exceções devem ser raras. Lançar e capturar exceções é lento em relação a outros padrões de fluxo de código. Por isso, as exceções não devem ser usadas para controlar o fluxo normal do programa.

Recomendações:

  • Não use lançar ou capturar exceções como um meio de fluxo normal do programa, especialmente em caminhos de código quente.
  • Inclua lógica no aplicativo para detetar e manipular condições que causariam uma exceção.
  • lançar ou pegar exceções para condições incomuns ou inesperadas.

As ferramentas de diagnóstico de aplicativos, como o Application Insights, podem ajudar a identificar exceções comuns em um aplicativo que podem afetar o desempenho.

Evite leitura ou gravação síncrona no corpo HttpRequest/HttpResponse

Todas as E/S no ASP.NET Core são assíncronas. Os servidores implementam a interface Stream, que tem sobrecargas síncronas e assíncronas. Deve-se preferir as operações assíncronas para evitar o bloqueio dos threads do pool de execução. O bloqueio de threads pode levar ao esgotamento do pool de threads.

Não faça isso: O exemplo a seguir usa o ReadToEnd. Ele bloqueia o thread atual para aguardar o resultado. Este é um exemplo de sincronização sobre assíncrono.

public class BadStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public ActionResult<ContosoData> Get()
    {
        var json = new StreamReader(Request.Body).ReadToEnd();

        return JsonSerializer.Deserialize<ContosoData>(json);
    }
}

No código anterior, Get lê de forma síncrona todo o corpo da solicitação HTTP na memória. Se o cliente estiver carregando lentamente, o aplicativo está fazendo a sincronização por meio de assíncrono. O aplicativo sincroniza sobre assíncrono porque Kestrel NÃO suporta leituras síncronas.

Faça isso: O exemplo a seguir usa ReadToEndAsync e não bloqueia o thread durante a leitura.

public class GoodStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public async Task<ActionResult<ContosoData>> Get()
    {
        var json = await new StreamReader(Request.Body).ReadToEndAsync();

        return JsonSerializer.Deserialize<ContosoData>(json);
    }

}

O código anterior lê de forma assíncrona todo o corpo da solicitação HTTP na memória.

Advertência

Se a solicitação for grande, ler todo o corpo da solicitação HTTP na memória pode levar a uma condição de falta de memória (OOM). OOM pode resultar em uma negação de serviço. Para mais informações, consulte a secção Evitar ler grandes corpos de pedido ou de resposta na memória neste artigo.

Faça isso: O exemplo a seguir é totalmente assíncrono usando um corpo de solicitação sem buffer:

public class GoodStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public async Task<ActionResult<ContosoData>> Get()
    {
        return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body);
    }
}

O código anterior desserializa assincronamente o corpo da solicitação em um objeto C#.

Prefira ReadFormAsync em vez do Request.Form

Use HttpContext.Request.ReadFormAsync em vez de HttpContext.Request.Form. HttpContext.Request.Form pode ser lido com segurança apenas com as seguintes condições:

  • O formulário foi lido através de uma chamada para ReadFormAsync, e
  • O valor do formulário armazenado em cache está sendo lido usando HttpContext.Request.Form

Não faça isso: O exemplo a seguir usa HttpContext.Request.Form. HttpContext.Request.Form usa sincronização sobre assíncrona e pode levar à exaustão do pool de threads.

public class BadReadController : Controller
{
    [HttpPost("/form-body")]
    public IActionResult Post()
    {
        var form =  HttpContext.Request.Form;

        Process(form["id"], form["name"]);

        return Accepted();
    }

Faça isso: O exemplo a seguir usa HttpContext.Request.ReadFormAsync para ler o corpo do formulário de forma assíncrona.

public class GoodReadController : Controller
{
    [HttpPost("/form-body")]
    public async Task<IActionResult> Post()
    {
       var form = await HttpContext.Request.ReadFormAsync();

        Process(form["id"], form["name"]);

        return Accepted();
    }

Evite carregar grandes volumes de dados de solicitação ou resposta na memória.

No .NET, cada alocação de objetos superior ou igual a 85.000 bytes termina no grande heap de objetos (LOH) . Objetos grandes são caros de duas maneiras:

  • O custo de alocação é alto porque a memória de um objeto grande recém-alocado precisa ser limpa. O CLR garante que a memória de todos os objetos recém-alocados seja limpa.
  • O LOH é recolhido junto com o resto da pilha. LOH requer um de coleta de lixo completo ou de coleta Gen2.

Esta postagem de blog descreve o problema de forma sucinta:

Quando um objeto grande é alocado, ele é marcado como objeto Gen 2. Não é a Geração 0 como acontece com objetos pequenos. As consequências são que, se você ficar sem memória no LOH, o GC limpa todo o monte gerenciado, não apenas o LOH. Assim, ele limpa Gen 0, Gen 1 e Gen 2, incluindo LOH. Isso é chamado de coleta completa de lixo e é a coleta de lixo mais demorada. Para muitas aplicações, pode ser aceitável. Mas certamente não para servidores web de alto desempenho, nos quais poucos buffers de memória grandes são necessários para processar um pedido web médio (ler a partir de um socket, descompactar, descodificar JSON e muito mais).

Armazenar um grande corpo de solicitação ou resposta em um único byte[] ou string:

  • Pode resultar em ficar rapidamente sem espaço no LOH.
  • Pode causar problemas de desempenho para o aplicativo devido à execução de GCs completos.

Trabalhando com uma API de processamento de dados síncrona

Ao usar um serializador/desserializador que ofereça suporte apenas a leituras e gravações síncronas (por exemplo, Json.NET):

  • Armazene os dados em buffer na memória de forma assíncrona antes de passá-los para o serializador/desserializador.

Advertência

Se a solicitação for grande, isso pode levar a uma condição de falta de memória (OOM). OOM pode resultar em uma negação de serviço. Para obter mais informações, consulte Evitar ler grandes corpos de solicitação ou corpos de resposta na memória no deste artigo.

ASP.NET Core 3.0 usa System.Text.Json por padrão para serialização JSON. System.Text.Json:

  • Lê e grava JSON de forma assíncrona.
  • É otimizado para texto UTF-8.
  • Normalmente tem um desempenho mais elevado do que o Newtonsoft.Json.

Não armazene IHttpContextAccessor.HttpContext em um campo

O IHttpContextAccessor.HttpContext retorna o HttpContext da solicitação ativa quando acessado a partir do thread de solicitação. O IHttpContextAccessor.HttpContext não deve ser armazenado num campo ou variável.

Não faça isso: O exemplo a seguir armazena o HttpContext em um campo e tenta usá-lo mais tarde.

public class MyBadType
{
    private readonly HttpContext _context;
    public MyBadType(IHttpContextAccessor accessor)
    {
        _context = accessor.HttpContext;
    }

    public void CheckAdmin()
    {
        if (!_context.User.IsInRole("admin"))
        {
            throw new UnauthorizedAccessException("The current user isn't an admin");
        }
    }
}

O código anterior freqüentemente captura um HttpContext nulo ou incorreto no construtor.

Faça isso: O seguinte exemplo:

  • Armazena o IHttpContextAccessor num campo.
  • Usa o campo HttpContext no momento correto e verifica se há null.
public class MyGoodType
{
    private readonly IHttpContextAccessor _accessor;
    public MyGoodType(IHttpContextAccessor accessor)
    {
        _accessor = accessor;
    }

    public void CheckAdmin()
    {
        var context = _accessor.HttpContext;
        if (context != null && !context.User.IsInRole("admin"))
        {
            throw new UnauthorizedAccessException("The current user isn't an admin");
        }
    }
}

Não deves aceder ao HttpContext a partir de várias threads

HttpContext não é seguro para threads. Acessar HttpContext de vários threads em paralelo pode resultar em um comportamento inesperado, como o servidor parar de responder, falhas e corrupção de dados.

Não faça isso: O exemplo a seguir faz três solicitações paralelas e registra o caminho da solicitação de entrada antes e depois da solicitação HTTP de saída. O caminho da solicitação é acedido a partir de várias linhas de execução, possivelmente em paralelo.

public class AsyncBadSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
        var query1 = SearchAsync(SearchEngine.Google, query);
        var query2 = SearchAsync(SearchEngine.Bing, query);
        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);

        await Task.WhenAll(query1, query2, query3);

        var results1 = await query1;
        var results2 = await query2;
        var results3 = await query3;

        return SearchResults.Combine(results1, results2, results3);
    }       

    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)
    {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.", 
                                    HttpContext.Request.Path);
            searchResults = _searchService.Search(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", 
                                    HttpContext.Request.Path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", 
                             HttpContext.Request.Path);
        }

        return await searchResults;
    }

Faça isso: O exemplo a seguir copia todos os dados da solicitação de entrada antes de fazer as três solicitações paralelas.

public class AsyncGoodSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
        string path = HttpContext.Request.Path;
        var query1 = SearchAsync(SearchEngine.Google, query,
                                 path);
        var query2 = SearchAsync(SearchEngine.Bing, query, path);
        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);

        await Task.WhenAll(query1, query2, query3);

        var results1 = await query1;
        var results2 = await query2;
        var results3 = await query3;

        return SearchResults.Combine(results1, results2, results3);
    }

    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query,
                                                  string path)
    {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.",
                                   path);
            searchResults = await _searchService.SearchAsync(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", path);
        }

        return await searchResults;
    }

Não use o HttpContext após a conclusão da solicitação

HttpContext só é válido enquanto houver uma solicitação HTTP ativa no pipeline ASP.NET Core. Todo o pipeline do ASP.NET Core é uma cadeia assíncrona de delegados que executa todas as solicitações. Quando o Task retornado dessa cadeia é concluído, o HttpContext é reciclado.

Não faça isso: O exemplo a seguir usa async void que torna a solicitação HTTP concluída quando o primeiro await é atingido:

  • Usar async void é SEMPRE uma má prática em aplicações ASP.NET Core.
  • O código de exemplo acessa o HttpResponse após a conclusão da solicitação HTTP.
  • O acesso tardio trava o processo.
public class AsyncBadVoidController : Controller
{
    [HttpGet("/async")]
    public async void Get()
    {
        await Task.Delay(1000);

        // The following line will crash the process because of writing after the 
        // response has completed on a background thread. Notice async void Get()

        await Response.WriteAsync("Hello World");
    }
}

Faça isso: O exemplo a seguir retorna um Task para a estrutura, para que a solicitação HTTP não seja concluída até que a ação seja concluída.

public class AsyncGoodTaskController : Controller
{
    [HttpGet("/async")]
    public async Task Get()
    {
        await Task.Delay(1000);

        await Response.WriteAsync("Hello World");
    }
}

Não capture o HttpContext em threads em segundo plano

Não faça isso: O exemplo a seguir mostra que uma clausura está a capturar o HttpContext da propriedade Controller. Esta é uma prática incorreta porque o item de trabalho pode:

  • Executar fora do escopo da solicitação.
  • Tente ler a seção errada HttpContext.
[HttpGet("/fire-and-forget-1")]
public IActionResult BadFireAndForget()
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        var path = HttpContext.Request.Path;
        Log(path);
    });

    return Accepted();
}

Faça isso: O seguinte exemplo:

  • Copia os dados necessários para a tarefa em segundo plano durante a solicitação.
  • Não faz referência a nada do controlador.
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
    string path = HttpContext.Request.Path;
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        Log(path);
    });

    return Accepted();
}

As tarefas em segundo plano devem ser implementadas como serviços hospedados. Para obter mais informações, consulte Tarefas em segundo plano com serviços hospedados.

Não capture serviços injetados nos controladores em threads em segundo plano

Não faça isso: O exemplo a seguir mostra um fecho que captura o DbContext a partir do parâmetro de ação Controller. Trata-se de uma má prática. O item de trabalho pode ser executado fora do escopo da solicitação. O ContosoDbContext é delimitado para a solicitação, resultando em um ObjectDisposedException.

[HttpGet("/fire-and-forget-1")]
public IActionResult FireAndForget1([FromServices]ContosoDbContext context)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        context.Contoso.Add(new Contoso());
        await context.SaveChangesAsync();
    });

    return Accepted();
}

Faça isso: O seguinte exemplo:

  • Injeta um IServiceScopeFactory para criar um âmbito num item de trabalho em segundo plano. IServiceScopeFactory é um singleton.
  • Cria um novo escopo de injeção de dependência no thread em segundo plano.
  • Não faz referência a nada do controlador.
  • Não captura o ContosoDbContext da solicitação de entrada.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                    serviceScopeFactory)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        await using (var scope = serviceScopeFactory.CreateAsyncScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();

            context.Contoso.Add(new Contoso());

            await context.SaveChangesAsync();                                        
        }
    });

    return Accepted();
}

O seguinte código destacado:

  • Cria um escopo para o tempo de vida da operação em segundo plano e resolve serviços a partir dela.
  • Usa ContosoDbContext do escopo correto.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                    serviceScopeFactory)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        await using (var scope = serviceScopeFactory.CreateAsyncScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();

            context.Contoso.Add(new Contoso());

            await context.SaveChangesAsync();                                        
        }
    });

    return Accepted();
}

Não modifique o código de status ou os cabeçalhos após o início do corpo da resposta

ASP.NET Core não armazena em buffer o corpo da resposta HTTP. A primeira vez que a resposta é escrita:

  • Os cabeçalhos são enviados junto com esse pedaço do corpo para o cliente.
  • Não é mais possível alterar os cabeçalhos de resposta.

Não faça isso: O código a seguir tenta adicionar cabeçalhos de resposta depois que a resposta já foi iniciada:

app.Use(async (context, next) =>
{
    await next();

    context.Response.Headers["test"] = "test value";
});

No código anterior, context.Response.Headers["test"] = "test value"; lançará uma exceção se next() tiver escrito na resposta.

Faça o seguinte: O exemplo a seguir verifica se a resposta HTTP foi iniciada antes de modificar os cabeçalhos.

app.Use(async (context, next) =>
{
    await next();

    if (!context.Response.HasStarted)
    {
        context.Response.Headers["test"] = "test value";
    }
});

Faça isso: O exemplo a seguir usa HttpResponse.OnStarting para definir os cabeçalhos antes que os cabeçalhos de resposta sejam liberados para o cliente.

Verificar se a resposta não foi iniciada permite registar um callback que será invocado imediatamente antes de os cabeçalhos de resposta serem escritos. Verificar se a resposta não foi iniciada:

  • Permite acrescentar ou substituir cabeçalhos no momento certo.
  • Não requer conhecimento do próximo middleware no pipeline.
app.Use(async (context, next) =>
{
    context.Response.OnStarting(() =>
    {
        context.Response.Headers["someheader"] = "somevalue";
        return Task.CompletedTask;
    });

    await next();
});

Não chame next() se já tiver começado a escrever no corpo da resposta

Os componentes só esperam ser chamados se for possível para eles tratar e manipular a resposta.

Usar hospedagem em processo com o IIS

Usando hospedagem em processo, um aplicativo ASP.NET Core é executado no mesmo processo que seu processo de trabalho do IIS. A hospedagem em processo fornece melhor desempenho em relação à hospedagem fora do processo porque as solicitações não são intermediadas por proxy no adaptador de loopback. O adaptador de loopback é uma interface de rede que retorna o tráfego de rede de saída de volta para a mesma máquina. O IIS trata da gestão de processos com o WAS (Serviço de Ativação de Processos do Windows).

Projetos adotam por padrão o modelo de hospedagem em-processo no ASP.NET Core 3.0 e posterior.

Para obter mais informações, consulte Host ASP.NET Core no Windows com o IIS

Não assuma que HttpRequest.ContentLength não é nulo

HttpRequest.ContentLength é nulo se o cabeçalho Content-Length não for recebido. Nulo, nesse caso, significa que a extensão do corpo do pedido não é conhecida; isso não significa que o comprimento é zero. Como todas as comparações com null (exceto ==) retornam false, a comparação Request.ContentLength > 1024, por exemplo, pode retornar false quando o tamanho do corpo da solicitação for maior que 1024. Não saber disso pode levar a falhas de segurança nos aplicativos. Você pode pensar que está a proteger-se contra solicitações demasiado grandes quando na verdade não está.

Para obter mais informações, consulte esta resposta StackOverflow.

Padrões de aplicativos Web corporativos

Para obter orientação sobre como criar um aplicativo ASP.NET Core confiável, seguro, com desempenho, testável e escalável, consulte Padrões de aplicativos Web corporativos. Está disponível um aplicativo Web de exemplo completo com qualidade de produção que implementa os padrões.