Содержание
Для создания компонентов middleware используется делегат RequestDelegate
, который выполняет некоторое действие и принимает контекст запроса. который представлен объектом HttpContext
. Рассмотрим использование этого объекта в приложениях ASP.NET Core Web API.
Использование RequestDelegate для построения конвейера обработки запросов
Делегат RequestDelegate
выглядит следующим образом:
public delegate Task RequestDelegate(HttpContext context);
Используя этот делегат мы можем организовать конвейер обработки запросов. При этом, для встраивания компонента middleware в конвейер используются методы расширения IApplicationBuilder
основные из которых — это:
Use()
. Метод используется в том случае, если предполагается, что запрос, после обработки в middleware, должен передаваться в следующий в конвейере компонент. Например, middleware может добавить в запрос какой-либо заголовок, чтобы следующие в конвейере компоненты могли этот заголовок использовать в работеRun()
. Метод используется для добавления в конвейер терминального middleware, то есть такого компонента, который замыкает конвейер обработки запроса.Map()
. Метод используется в том случае, если необходимо создать отдельную ветку конвейера обработки запросов.
Каждый из этих методов имеет ряд перегруженных версий. Также, для ветвления конвейера могут использования такие методы как MapWhen()
и UseWhen()
, которые могут также использоваться для ветвления конвейера обработки запросов, используя некоторое условие. Чтобы создать очередной компонент middleware мы можем использовать лямбда-выражения, например, так:
app.Use(async (context, next) => { if (token == "12345") await next(); else await context.Response.WriteAsync($"Попытка доступа {time}"); });
или же вынести код компонента middleware в отдельный метод. Например, так:
app.Use(CheckToken); app.MapControllers(); app.Run(); async Task CheckToken(HttpContext context, RequestDelegate next) { if (token == "12345") await next(context); else await context.Response.WriteAsync($"Попытка доступа {time}"); }
В данном случае, мы вынесли middleware в отдельный метод CheckToken()
. В более «развернутом» виде использование middleware в виде отдельного метода мы могли бы записать так, используя лямбда-выражение:
app.Use((context, next) => CheckToken(context,next));
но, Visual Studio всё равно предложит убрать такое лямбда-выражение и использовать запись как показано в коде выше. Так как мы использовали метод Use()
для встраивания middleware в конвейер, то предполагается, что запрос должен передаваться дальше по конвейеру. Поэтому в методе CheckToken()
мы, помимо контекста запроса, также получаем делегат запроса следующего в конвейере и осуществляем, при необходимости, его вызов.
Отдельное внимание здесь стоит уделить объекту типа HttpContext
, который содержит всю информацию по запросу пользователя и ответу сервера.
Контекст запроса (HttpContext)
Контекст запроса, представленный в ASP.NET Core в виде объекта типа HttpContext
предоставляет нам исчерпывающую информацию о запросе пользователя и ответе сервера. Так, класс HttpContext
содержит следующие свойства:
Connection |
Возвращает сведения о базовом подключении для этого запроса. |
Features |
Возвращает коллекцию функций HTTP, предоставляемых сервером и middleware, доступных в этом запросе. |
Items |
Возвращает или задает коллекцию пар «ключ-значение», которую можно использовать для совместного использования данных в области запроса. |
Request |
Объект HttpRequest , содержащий данные запроса. |
Request |
Уведомляет приложение о прерывании подключения, лежащего в основе этого запроса, и поэтому операции запроса должны быть отменены. |
Request |
Возвращает или задает объект , IServiceProvider предоставляющий доступ к контейнеру службы запроса. |
Response |
Объект HttpResponse , с помощью которого можно управлять данными ответа сервера. |
Session |
Возвращает или задает объект , используемый для управления данными сеанса пользователя для этого запроса. |
Trace |
Возвращает или задает уникальный идентификатор для представления этого запроса в журналах трассировки. |
User |
Возвращает или задает пользователя для этого запроса. |
Web |
Возвращает объект , который управляет установлением подключений WebSocket для этого запроса. |
На данный момент, многие из этих свойств нам пока ещё не известны, а свойством Response
для формирования ответа сервера мы воспользовались, можно сказать, интуитивно. Однако, мы можем попробовать использовать, например, свойство Items
для создания простенького примера, демонстрирующего использование конвейера обработки запросов в ASP.NET Core.
Для демонстрации, создадим новое приложение ASP.NET Core Web API на основе контроллеров и добавим в конвейер обработки запросов вот такой простенький компонент middleware:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.UseHttpsRedirection(); app.UseAuthorization(); int countElements = 10; //свой MIDDLEWARE app.Use(async (context, next) => { context.Items.Add("count", countElements); await next(context); }); app.MapControllers(); app.Run();
Компонент middleware, по сути, не делает ничего, кроме добавления к коллекцию Items
нового элемента с именем count
и значением равным countElements
, а затем передает модифицированный запрос дальше по конвейеру. В итоге, этот запрос будет передан в единственный в приложении контроллер WeatherForecastController
, кол которого располагается в папке Controllers
. Откроем файл WeatherForecastController.cs. На данный момент, контроллер содержит метод Get()
со следующим содержанием:
public IEnumerable<WeatherForecast> Get() { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); }
то есть, на запрос пользователя этот метод всегда возвращает 5 элементов массива. Изменим код метода следующим образом:
public IEnumerable<WeatherForecast> Get() { int count = 5; if (HttpContext.Items.TryGetValue("count", out object? cnt)) { count = (int)cnt; } return Enumerable.Range(1, count).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); }
здесь мы пытаемся получить значение элемента count
из коллекции Items
и, если значение удалось получить, то используем его для указания количества элементов возвращаемого массива. Теперь можно запустить приложение и убедиться, что в ответ на запрос сервер возвращает заданное в count
количество элементов. Этот простой пример демонстрирует то, как мы можем использовать контекст запроса для изменения работы приложения. Аналогичным образом мы можем, например, модифицировать заголовки запроса или ответа, проверять их наличие в запросе и т.д — для этого используются свойства Request
и Response
объекта HttpContext
.
Итого
Для построения конвейера обработки запросов используется делегат RequestDelegate
, который в качестве параметра принимает контекст запроса, представленный объектом типа HttpContext
. Используя свойства контекста запроса, мы можем каким-либо образом модифицировать запрос пользователя и ответ сервера, а также, в зависимости от заданных значений, изменять работу приложения. В следующей части мы продолжим разбираться с компонентами middleware и изучим вопрос о том, какие способы можно применять для создания таких компонентов.