основные рекомендации по ASP.NET

Автор: Майк Роусос (Mike Rousos)

В этой статье приведены рекомендации по повышению производительности и надежности приложений ASP.NET Core.

Кэш агрессивно

Кэширование рассматривается в нескольких частях этой статьи. Дополнительные сведения см. в разделе "Обзор кэширования" в ASP.NET Core.

Общие сведения о путях горячего кода

В этой статье горячий путь кода определяется как путь кода, который часто вызывается и где происходит большая часть времени выполнения. Пути к горячему коду обычно ограничивают масштабирование приложения и производительность и рассматриваются в нескольких частях этой статьи.

Избегайте блокирующих вызовов

ASP.NET приложения Core должны быть разработаны для одновременной обработки множества запросов. Асинхронные API позволяют небольшому пулу потоков работать с тысячами одновременных запросов, не дожидаясь блокировки вызовов. Вместо ожидания завершения длительной синхронной задачи поток может работать с другим запросом.

Распространенная проблема производительности в приложениях ASP.NET Core блокирует вызовы, которые могут быть асинхронными. Множество синхронных вызовов блокировки может привести к истощению ресурсов пула потоков и увеличении времени отклика.

Не блокируйте асинхронное выполнение путем вызова Task.Wait или Task<TResult>.Result. Не получайте блокировки в общих путях кода. ASP.NET Приложения Core лучше всего выполняются при разработке кода параллельно. Не звонить Task.Run и сразу ожидать его. ASP.NET Core уже запускает код приложения в обычных потоках пула потоков, поэтому вызов Task.Run приводит только к дополнительным ненужным планированию пула потоков. Даже если запланированный код блокирует поток, Task.Run это не предотвращается.

  • Выполните асинхронные асинхронные пути к горячему коду.
  • Асинхронно вызывайте доступ к данным, операции ввода-вывода и длительные операции, если доступен асинхронный API.
  • Не используйте Task.Run для асинхронного асинхронного API.
  • Выполните асинхронные действия контроллера илиRazor страницы. Весь стек вызовов является асинхронным, чтобы использовать преимущества шаблонов async/await.

Можно использовать профилировщик, например PerfView, чтобы находить потоки, часто добавляемые в пул потоков. Событие Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start указывает поток, добавленный в пул потоков.

Возврат больших коллекций на нескольких небольших страницах

Веб-страница не должна загружать большие объемы данных одновременно. При возврате коллекции объектов следует учитывать, может ли она привести к проблемам с производительностью. Определите, может ли проект получить следующие плохие результаты:

  • OutOfMemoryException или большое потребление памяти
  • Голодание пула потоков (см. следующие замечания)IAsyncEnumerable<T>
  • Время медленного отклика
  • Частая сборка мусора

Добавьте разбиение на страницы, чтобы устранить описанные выше сценарии. Используя параметры размера страницы и индекса страницы, разработчики должны пользоваться проектом возврата частичного результата. Если требуется исчерпывающий результат, следует использовать разбиение на страницы для асинхронного заполнения пакетов результатов, чтобы избежать блокировки ресурсов сервера.

Дополнительные сведения о разбиении по страницам и ограничении количества возвращаемых записей см. в следующих статье:

Возврат IEnumerable<T> или IAsyncEnumerable<T>

IEnumerable<T> Возвращение из действия приводит к синхронной итерации коллекции сериализатором. В результате вызовы блокируются, что может стать причиной перегрузки пула потоков. Чтобы избежать синхронного перечисления, используйте ToListAsync перед возвратом перечисления.

Начиная с ASP.NET Core 3.0, IAsyncEnumerable<T> можно использовать как альтернативу IEnumerable<T> перечислению асинхронно. Дополнительные сведения см. в разделе "Возвращаемые типы действий контроллера".

Минимизация выделения больших объектов

Сборщик мусора .NET Core автоматически управляет выделением и выпуском памяти в приложениях ASP.NET Core. Автоматическая сборка мусора обычно означает, что разработчикам не нужно беспокоиться о том, как или когда память освобождается. Однако очистка неуправляемых объектов занимает время ЦП, поэтому разработчики должны свести к минимуму выделение объектов в путях горячего кода. Сборка мусора особенно дорого для больших объектов (>= 85 000 байт). Большие объекты хранятся в куче больших объектов и требуют полной сборки мусора (поколения 2) для очистки. В отличие от коллекций поколения 0 и поколения 1, для коллекции поколения 2 требуется временная приостановка выполнения приложения. Частое выделение и отмена выделения больших объектов может привести к несогласованности производительности.

Рекомендации.

  • Рекомендуется кэширование больших объектов, которые часто используются. Кэширование больших объектов предотвращает дорогостоящие выделения.
  • Выполните буферы пула с помощью ArrayPool<T> хранилища больших массивов.
  • Не выделяете множество коротких больших объектов на пути к горячему коду.

Проблемы с памятью, такие как предыдущие, можно диагностировать, просмотрев статистику сборки мусора (GC) в PerfView и проверив:

  • Время приостановки сборки мусора.
  • Какой процент времени процессора тратится на сборку мусора.
  • Сколько сборок мусора — поколение 0, 1 и 2.

Дополнительные сведения см. в разделе "Сборка мусора" и "Производительность".

Оптимизация доступа к данным и ввода-вывода

Взаимодействие с хранилищем данных и другими удаленными службами часто являются самыми медленными частями приложения ASP.NET Core. Эффективное чтение и запись данных имеет решающее значение для хорошей производительности.

Рекомендации.

  • Асинхронно вызывайте все API доступа к данным.
  • Не извлекайте больше данных, чем необходимо. Напишите запросы, чтобы вернуть только данные, необходимые для текущего HTTP-запроса.
  • Рекомендуется кэширование часто доступных данных из базы данных или удаленной службы, если данные слегка устарели. В зависимости от сценария используйте MemoryCache или DistributedCache. Дополнительные сведения см. в статье Кэширование ответов в ASP.NET Core.
  • Свести к минимуму круглые поездки по сети. Цель состоит в том, чтобы получить необходимые данные в одном вызове, а не несколько вызовов.
  • Не отслеживайтезапросы в Entity Framework Core при доступе к данным только для чтения. EF Core может более эффективно возвращать результаты запросов без отслеживания.
  • Выполняйте фильтрацию и агрегирование запросов LINQ (например, с .Where.Selectоператорами или .Sum операторами), чтобы фильтрация выполнялась базой данных.
  • Рекомендуется разрешить EF Core некоторые операторы запросов на клиенте, что может привести к неэффективному выполнению запроса. Дополнительные сведения см. в статье о проблемах с производительностью оценки клиентов.
  • Не используйте запросы проекции для коллекций, что может привести к выполнению запросов SQL "N + 1". Дополнительные сведения см. в разделе "Оптимизация сопоставленных вложенных запросов".

Следующие подходы могут повысить производительность в высокомасштабируемых приложениях:

Перед фиксацией базы кода рекомендуется измерять влияние предыдущих высокопроизводительных подходов. Дополнительная сложность скомпилированных запросов может не оправдать улучшение производительности.

Проблемы с запросами можно обнаружить, просмотрив время, затраченное на доступ к данным с помощью приложения Аналитика или с помощью средств профилирования. Большинство баз данных также предоставляют статистику по часто выполняемым запросам.

Пул HTTP-подключений с помощью HttpClientFactory

Хотя HttpClient реализует интерфейс, он предназначен для повторного IDisposable использования. Закрытые HttpClient экземпляры покидают сокеты в TIME_WAIT состоянии в течение короткого периода времени. Если путь кода, создающий и удаляющий HttpClient объекты, часто используется, приложение может исчерпать доступные сокеты. HttpClientFactory появилась в ASP.NET Core 2.1 в качестве решения этой проблемы. Он обрабатывает пул HTTP-подключений для оптимизации производительности и надежности. Дополнительные сведения см. в статье "Использование HttpClientFactory для реализации устойчивых HTTP-запросов".

Рекомендации.

Быстрый общий путь к коду

Вы хотите, чтобы весь код был быстрым. Часто называемые пути кода наиболее важными для оптимизации. Например:

  • Компоненты ПО промежуточного слоя в конвейере обработки запросов приложения, особенно по промежуточному слоям, выполняются рано в конвейере. Эти компоненты оказывают большое влияние на производительность.
  • Код, выполняемый для каждого запроса или несколько раз за запрос. Например, пользовательские журналы, обработчики авторизации или инициализация временных служб.

Рекомендации.

  • Не используйте пользовательские компоненты по промежуточного слоя с длительными задачами.
  • Используйте средства профилирования производительности, такие как средства диагностики Visual Studio или PerfView), чтобы определить пути к горячему коду.

Выполнение длительных задач за пределами HTTP-запросов

Большинство запросов к приложению ASP.NET Core можно обрабатывать контроллером или моделью страницы, вызывая необходимые службы и возвращая HTTP-ответ. Для некоторых запросов, связанных с длительными задачами, лучше сделать весь процесс ответа запроса асинхронным.

Рекомендации.

  • Не дождитесь выполнения длительных задач в рамках обычной обработки HTTP-запросов.
  • Рекомендуется обрабатывать длительные запросы с фоновыми службамиили вне процесса с помощью функции Azure. Завершение работы над процессом особенно полезно для задач с большим объемом ЦП.
  • Используйте параметры взаимодействия в режиме реального времени, например SignalR, для асинхронного взаимодействия с клиентами.

Минифицировать клиентские ресурсы

ASP.NET Приложения Core с сложными интерфейсами часто служат многим файлам JavaScript, CSS или изображения. Производительность начальных запросов на загрузку можно улучшить следующими способами:

  • Объединение, которое объединяет несколько файлов в один.
  • Minifying, который уменьшает размер файлов, удаляя пробелы и комментарии.

Рекомендации.

  • Используйтерекомендации по объединением и минификации, которые упоминание совместимых инструментов и показано, как использовать тег ASP.NET Core environment для обработки обоих Development сред и Production сред.
  • Рассмотрите другие сторонние инструменты, такие как Webpack, для комплексного управления клиентскими ресурсами.

Сжатие ответов

Уменьшение размера ответа обычно увеличивает скорость реагирования приложения, часто резко. Одним из способов уменьшения размера полезных данных является сжатие ответов приложения. Дополнительные сведения см. в разделе "Сжатие ответов".

Использование последнего выпуска ASP.NET Core

Каждый новый выпуск ASP.NET Core включает улучшения производительности. Оптимизация в .NET Core и ASP.NET Core означает, что более новые версии обычно превысят старые версии. Например, .NET Core 2.1 добавил поддержку скомпилированных регулярных выражений и выиграл от Span<T>. ASP.NET Core 2.2 добавлена поддержка HTTP/2. ASP.NET Core 3.0 добавляет множество улучшений , которые сокращают использование памяти и повышают пропускную способность. Если производительность является приоритетом, рассмотрите возможность обновления до текущей версии ASP.NET Core.

Свести к минимуму исключения

Исключения должны быть редкими. Создание и перехват исключений происходит медленно относительно других шаблонов потока кода. Из-за этого исключения не следует использовать для управления обычным потоком программы.

Рекомендации.

  • Не используйте вызовы или перехват исключений в качестве средства нормального потока программы, особенно в путях горячего кода.
  • Включите логику в приложение для обнаружения и обработки условий, вызывающих исключение.
  • Создание или перехват исключений для необычных или непредвиденных условий.

Средства диагностики приложений, такие как приложение Аналитика, могут помочь определить распространенные исключения в приложении, которые могут повлиять на производительность.

Избегайте синхронного чтения или записи в тексте HttpRequest/HttpResponse

Все ввода-вывода в ASP.NET Core являются асинхронными. Серверы реализуют Stream интерфейс, который имеет синхронные и асинхронные перегрузки. Асинхронные должны быть предпочтительнее, чтобы избежать блокировки потоков пула потоков. Блокировка потоков может привести к нехватке пула потоков.

Не делайте этого: в следующем примере используется ReadToEnd. Он блокирует текущий поток для ожидания результата. Это пример синхронизации с асинхронной синхронизацией.

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

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

В приведенном выше коде Get синхронно считывает весь текст HTTP-запроса в память. Если клиент медленно загружается, приложение выполняет синхронизацию с асинхронной. Приложение синхронизируется с асинхронным режимом, так как Kestrelне поддерживает синхронные операции чтения.

Сделайте это: в следующем примере используется ReadToEndAsync и поток не блокируется при чтении.

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

}

Предыдущий код асинхронно считывает весь текст HTTP-запроса в память.

Предупреждение

Если запрос велик, чтение всего текста HTTP-запроса в память может привести к состоянию нехватки памяти (OOM). OOM может привести к отказу в обслуживании. Дополнительные сведения см. в статье о том, как избежать чтения больших тел запросов или тел ответа в память в этой статье.

Сделайте следующее: следующий пример полностью асинхронен с помощью не буферизованного текста запроса:

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

Предыдущий код асинхронно десериализирует текст запроса в объект C#.

Предпочитать ReadFormAsync по запросу.Form

Используйте HttpContext.Request.ReadFormAsync вместо HttpContext.Request.Form. HttpContext.Request.Form можно безопасно прочитать только с помощью следующих условий:

  • Форма была прочитана вызовом ReadFormAsyncи
  • Кэшированное значение формы считывается с помощью HttpContext.Request.Form

Не делайте этого: в следующем примере используется HttpContext.Request.Form. HttpContext.Request.Form использует синхронизацию с асинхронной функцией и может привести к нехватке пула потоков.

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

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

        return Accepted();
    }

Сделайте следующее: в следующем примере используется HttpContext.Request.ReadFormAsync для асинхронного чтения текста формы.

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

Избегайте чтения больших тел запросов или тел ответа в память

В .NET каждое выделение объектов больше или равно 85 000 байтам заканчивается в куче больших объектов (LOH). Крупные объекты дороги двумя способами:

  • Стоимость выделения высока, так как память для недавно выделенного большого объекта должна быть очищена. Среда CLR гарантирует очистку памяти для всех вновь выделенных объектов.
  • LoH собирается с остальной частью кучи. Для loH требуется полная сборка мусора или коллекция 2-го поколения.

В этой записи блога описывается проблема кратко:

При выделении большого объекта он помечается как объект 2-го поколения. Не 0-го поколения как для небольших объектов. Последствия того, что при нехватке памяти в LOH GC очищает всю управляемую кучу, а не только LOH. Таким образом, он очищает 0-го поколения, 1-го поколения и 2-го поколения, включая LOH. Это называется полной сборкой мусора и является самой многовременной сборкой мусора. Для многих приложений это может быть приемлемо. Но определенно не для высокопроизводительных веб-серверов, где для обработки среднего веб-запроса требуется несколько больших буферов памяти (чтение из сокета, распаковки, декодирование JSON и многое другое).

Хранение большого текста запроса или ответа в одном byte[] или string:

  • Может привести к быстрому выходу из пространства в LOH.
  • Может вызвать проблемы с производительностью для приложения из-за полного запуска GCs.

Работа с API синхронной обработки данных

При использовании сериализатора или десериализатора, поддерживающего синхронные операции чтения и записи (например, Json.NET):

  • Асинхронно буферизуйте данные в память перед передачей в сериализатор или десериализатор.

Предупреждение

Если запрос большой, он может привести к состоянию не памяти (OOM). OOM может привести к отказу в обслуживании. Дополнительные сведения см. в статье о том, как избежать чтения больших тел запросов или тел ответа в память в этой статье.

ASP.NET Core 3.0 используется System.Text.Json по умолчанию для JSсериализации ON. System.Text.Json:

  • асинхронно считывает и записывает JSON;
  • оптимизирован для текста UTF-8;
  • Как правило, это более высокая производительность, чем Newtonsoft.Json.

Не сохраняйте IHttpContextAccessor.HttpContext в поле

IHttpContextAccessor.HttpContext возвращает HttpContext активный запрос при доступе к потоку запроса. Не IHttpContextAccessor.HttpContext следует хранить в поле или переменной.

Не делайте этого: в следующем примере сохраняется HttpContext поле, а затем пытается использовать его позже.

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

Предыдущий код часто фиксирует значение NULL или неправильно HttpContext в конструкторе.

Сделайте следующее: в следующем примере:

  • IHttpContextAccessor Сохраняет поле.
  • HttpContext Использует поле в правильное время и проверка для 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");
        }
    }
}

Не обращаться к HttpContext из нескольких потоков

HttpContextне является потокобезопасной. HttpContext Доступ из нескольких потоков параллельно может привести к непредвиденному поведению, таким как зависание, сбои и повреждение данных.

Не делайте этого: в следующем примере выполняются три параллельных запроса и регистрируются путь входящих запросов до и после исходящего HTTP-запроса. Путь запроса осуществляется из нескольких потоков, потенциально параллельно.

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

Сделайте это: в следующем примере копируются все данные из входящего запроса, прежде чем выполнять три параллельных запроса.

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

Не используйте HttpContext после завершения запроса

HttpContext допустимо только до тех пор, пока в конвейере ASP.NET Core существует активный HTTP-запрос. Весь конвейер ASP.NET Core — это асинхронная цепочка делегатов, выполняющих каждый запрос. Task После завершения HttpContext возврата из этой цепочки перезапускается.

Не делайте этого: в следующем примере используется async void http-запрос завершен при достижении первого await .

  • Использование async void ВСЕГДА является плохой практикой в приложениях ASP.NET Core.
  • Пример кода обращается к HttpResponse коду после завершения HTTP-запроса.
  • Поздний доступ завершает процесс.
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");
    }
}

Сделайте следующее: следующий пример возвращает платформу Task , поэтому HTTP-запрос не завершается до завершения действия.

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

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

Не захватывайте HttpContext в фоновых потоках

Не делайте этого: в следующем примере показано, что закрытие фиксируется HttpContext из Controller свойства. Это плохая практика, так как рабочий элемент может:

  • Выполнение за пределами область запроса.
  • Попытайтесь прочитать ошибку 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();
}

Сделайте следующее: в следующем примере:

  • Копирует данные, необходимые в фоновой задаче во время запроса.
  • Не ссылается ни на что из контроллера.
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
    string path = HttpContext.Request.Path;
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        Log(path);
    });

    return Accepted();
}

Фоновые задачи должны быть реализованы как размещенные службы. Дополнительные сведения см. в статье Background tasks with hosted services in ASP.NET Core (Фоновые задачи с размещенными службами в ASP.NET Core).

Не регистрировать службы, внедренные в контроллеры на фоновых потоках

Не делайте этого: в следующем примере показано закрытие, которое записывается DbContext из Controller параметра действия. Это плохая практика. Рабочий элемент может выполняться вне область запроса. Объект ContosoDbContext область запросу, в результате чего будет выполнено 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();
}

Сделайте следующее: в следующем примере:

  • Внедряет объектIServiceScopeFactory, чтобы создать область в фоновом рабочем элементе. IServiceScopeFactory является одноэлементным.
  • Создает новую область внедрения зависимостей в фоновом потоке.
  • Не ссылается ни на что из контроллера.
  • Не фиксирует ContosoDbContext входящий запрос.
[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();
}

Следующий выделенный код:

  • Создает область для времени существования фоновой операции и разрешает службы из него.
  • Используется ContosoDbContext из правильного область.
[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();
}

Не изменяйте код состояния или заголовки после запуска текста ответа

ASP.NET Core не буферизирует текст ответа HTTP. При первом написании ответа:

  • Заголовки отправляются вместе с этим фрагментом текста клиенту.
  • Изменить заголовки ответов больше невозможно.

Не делайте этого: следующий код пытается добавить заголовки ответов после того, как ответ уже запущен:

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

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

В приведенном выше коде вызовет исключение, context.Response.Headers["test"] = "test value"; если next() оно записано в ответ.

Сделайте следующее: в следующем примере проверка, если http-ответ был запущен перед изменением заголовков.

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

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

Сделайте следующее: в следующем примере используется HttpResponse.OnStarting для задания заголовков перед очисткой заголовков ответа клиенту.

Проверка того, не запущен ли ответ, позволяет регистрировать обратный вызов, который будет вызван непосредственно перед записью заголовков ответов. Проверка того, не запущен ли ответ:

  • Предоставляет возможность добавлять или переопределять заголовки за раз.
  • Не требуется знание следующего по промежуточного слоя в конвейере.
app.Use(async (context, next) =>
{
    context.Response.OnStarting(() =>
    {
        context.Response.Headers["someheader"] = "somevalue";
        return Task.CompletedTask;
    });

    await next();
});

Не вызывайте следующий() если вы уже начали писать в текст ответа

Компоненты должны вызываться только в том случае, если они могут обрабатывать ответ и управлять ими.

Использование внутрипроцессного размещения с iis

При внутрипроцессном размещении приложение ASP.NET Core выполняется в том же процессе, что и рабочий процесс IIS. Размещение в процессе обеспечивает улучшенную производительность по сравнению с размещением вне процесса, так как запросы не обрабатываются по адаптеру петли. Адаптер обратного цикла — это сетевой интерфейс, который возвращает исходящий сетевой трафик обратно на тот же компьютер. IIS обрабатывает управление процессом с помощью службы активации процессов Windows (WAS).

Проекты по умолчанию предназначены для модели размещения в процессе в ASP.NET Core 3.0 и более поздних версий.

Дополнительные сведения см. в разделе "Узел ASP.NET Core" в Windows с помощью IIS

Не предполагайте, что httpRequest.ContentLength не имеет значения NULL

HttpRequest.ContentLength имеет значение NULL, если Content-Length заголовок не получен. Значение NULL в этом случае означает, что длина текста запроса не известна; Это не означает, что длина равна нулю. Так как все сравнения со значением NULL (за исключением ==) возвращают значение false, сравнение Request.ContentLength > 1024, например, может возвращать false , если размер текста запроса превышает 1024. Не зная, что это может привести к нарушениям безопасности в приложениях. Вы можете подумать, что вы защищаете от слишком больших запросов, когда вы не являетесь.

Дополнительные сведения см . в этом ответе StackOverflow.

Надежные шаблоны веб-приложений

См . статью "Шаблон надежных веб-приложений" for.NETвидео и статьи YouTube по созданию современного, надежного, производительного, тестового, экономичного и масштабируемого приложения ASP.NET Core, будь то с нуля или рефакторинг существующего приложения.