Метод расширения Use() добавляет компонент middleware, который позволяет передать обработку запроса далее следующим в конвейере компонентам. Этот метод реализован как метод расширения для типа IApplicationBuilder, соответственно мы можем вызвать данный метод у объекта WebApplication для добавления middleware в приложение.
Метод Use() имеет следующие версии
Use(middleware As Func(Of HttpContext, Func(Of Task), Task)) Use(middleware As Func(Of HttpContext, RequestDelegate, Task))
В обоих версиях метод Use принимает некоторое действие, которое имеет два параметра и возвращает объект Task.
Первый параметр делегата Func, который передается в метод Use(), представляет объект HttpContext. Этот объект позволяет получить данные запроса и управлять ответом клиенту.
Второй параметр делегата представляет другой делегат - Func(Of Task) или RequestDelegate. Этот делегат представляет следующий в конвейере компонент middleware, которому будет передаваться обработка запроса.
В общем случае применение метода Use() выглядит следующим образом:
app.Use(Async Function(context As HttpContext, nextMiddleware As Func(Of Task)) As Task 'действия перед передачи запроса в следующий middleware Await nextMiddleware.Invoke() 'действия после обработки запроса следующим middleware End Function)
Работа middleware разбивается на две части:
Middleware выполняет некоторую начальную обработку запроса до вызова Await nextMiddleware.Invoke()
Затем вызывается метод nextMiddleware.Invoke()
, который передает обработку запроса следующему компоненту в конвейере
Когда следующий в конвейере компонент закончил обработку запрос возвращается в обратно в текущий компонент,
и выполняются действия, которые идут после вызова await nextMiddleware.Invoke(
Таким образом, middleware в методе Use выполняет действия до следующего в конвейере компонента и после него.
Рассмотрим метод Use() на примере:
var builder = WebApplication.CreateBuilder(); var app = builder.Build(); string date = ""; app.Use(async(context, next) => { date = DateTime.Now.ToShortDateString(); await nextMiddleware.Invoke(); // вызываем middleware из app.Run Console.WriteLine($"Current date: {date}"); // Current date: 08.12.2021 }); app.Run(async(context) => await context.Response.WriteAsync($"Date: {date}")); app.Run();
В данном случае мы используем перегрузку метода Use, которая в качестве параметров принимает контекст запроса - объект HttpContext и
делегат Func<Task>
, который представляет собой ссылку на следующий в конвейере компонент middleware.
Middleware в методе app.Use()
реализует простейшую задачу - присваивает переменной date
текущую дату в виде строки и затем передает обработку запроса
следующим компонентам middleware в конвейере. То есть при вызове Await nextMiddleware.Invoke()
обработка запроса перейдет к тому компоненту, который установлен в методе app.Run()
.
В итоге обработка запроса будет выглядеть следующим образом:
Вызов компонента app.Use()
Установка значения переменной date:
currentDate = Now.ToShortDateString()
Вызов Await nextMiddleware.Invoke()
. Управление переходит следующему компоненту в конвейере - к app.Run.
В middleware из app.Run()
отравляет клиенту текущую дату в качестве ответа с помощью метода context.Response.WriteAsync()
:
Await context.Response.WriteAsync($"Date: {currentDate}")
Метод app.Run закончил свою работу, и управление обработкой возвращается к middleware в методе app.Use. Начинает выполняться та часть кода, которая
идет после Await nextMiddleware.Invoke()
. В этой части выполняется условное логгирование - на консоль выводится значение переменной date:
Console.WriteLine($"Current date: {currentDate}")
После этого обработка запроса завершена
В итоге в веб-браузере мы увидим следующее сообщение:
А в консоли запущенного приложения мы увидим значение переменной date, которое выводится в middleware из метода app.Use:
При использовании метода Use и передаче выполнения следующему делегату следует учитывать, что не рекомендуется вызывать метод
nextMiddleware.Invoke
после метода Response.WriteAsync()
. Компонент middleware должен либо генерировать ответ с помощью Response.WriteAsync,
либо вызывать следующий делегат посредством nextMiddleware.Invoke
, но не выполнять оба этих действия одновременно. Так как согласно документации
последующие изменения объекта Response могут привести к нарушению протокола, например, будет послано больше байт, чем указано в заголовке Content-Length, либо
могут привести к нарушению тела ответа, например, футер страницы HTML запишется в CSS-файл.
То есть к примеру следующая обработка запроса не рекомендуется:
Imports Microsoft.AspNetCore.Builder Imports Microsoft.AspNetCore.Http Module Program Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) Dim app = builder.Build() ' Первый middleware app.Use(Async Function(context As HttpContext, nextMiddleware As Func(Of Task)) As Task Await context.Response.WriteAsync("<p>Hello world!</p>") Await nextMiddleware.Invoke() End Function) ' Второй middleware app.Run(Async Function(context As HttpContext) As Task Await Task.Delay(10000) 'можно поставить задержку Await context.Response.WriteAsync("<p>Good bye, World...</p>") End Function) app.Run() End Sub End Module
В примере выше использовалась версия метода Use(), которая использует делегат Func(Of Task). Подобным образом можно использовать и другую версию, где используется делегат RequestDelegate. Единственное - при вызове делегата ( то есть фактически следующего в конвейере компонента) необходимо передавать делегату объект HttpContext:
Imports Microsoft.AspNetCore.Builder Imports Microsoft.AspNetCore.Http Module Program Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) Dim app = builder.Build() Dim currentDate = "" ' текущая дата app.Use(Async Function(context As HttpContext, nextMiddleware As RequestDelegate) As Task currentDate = Now.ToShortDateString() Await nextMiddleware.Invoke(context) Console.WriteLine($"Current date: {currentDate}") ' Current date: 15.05.2022 End Function) app.Run(Async Function(context As HttpContext) As Task Await context.Response.WriteAsync($"Date: {currentDate}") End Function) app.Run() End Sub End Module
Middleware в методе Use() необязательно должен вызывать к следующему в конвейере компоненту. Вместо этого он может завершить обработку запроса. В этом случае он может выступать в роли такого же терминального компонента middleware, а и компоненты из метода Run(). Например:
Imports Microsoft.AspNetCore.Builder Imports Microsoft.AspNetCore.Http Module Program Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) Dim app = builder.Build() app.Use(Async Function(context As HttpContext, nextMiddleware As RequestDelegate) As Task Dim path = context.Request.Path.Value?.ToLower() If path = "/date" Then Await context.Response.WriteAsync($"Date: {Now.ToShortDateString()}") Else Await nextMiddleware.Invoke(context) End If End Function) app.Run(Async Function(context As HttpContext) As Task Await context.Response.WriteAsync("Hello METANIT.COM") End Function) app.Run() End Sub End Module
Здесь middleware в app.Use проверяет запрошенный адрес - если он содержит "/date", то клиенту отправляется текущая дата. Иначае обработка запроса передается дальше в app.Run.
Причем в принципе мы можем использовать компонент в app.Use как единственный и соответственно терминальный:
Imports Microsoft.AspNetCore.Builder Imports Microsoft.AspNetCore.Http Module Program Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) Dim app = builder.Build() app.Use(Async Function(context As HttpContext, nextMiddleware As RequestDelegate) As Task Await context.Response.WriteAsync("Hello Work!") End Function) app.Run() End Sub End Module
Однако в данном случае для большей производительости лучше использовать app.Run(), если нам надо определить лишь один компонент, который в принципе не передает запрос дальше по конвейеру.
Также можно вынести все inline-компоненты в отдельные методы:
Imports Microsoft.AspNetCore.Builder Imports Microsoft.AspNetCore.Http Module Program Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) Dim app = builder.Build() app.Use(AddressOf GetDate) app.Run(AddressOf Hello) app.Run() End Sub Async Function GetDate(context As HttpContext, nextMiddleware As RequestDelegate) As Task Dim path = context.Request.Path.Value?.ToLower() If path = "/date" Then Await context.Response.WriteAsync($"Date: {Now.ToShortDateString()}") Else Await nextMiddleware.Invoke(context) End If End Function Async Function Hello(context As HttpContext) As Task Await context.Response.WriteAsync("Hello METANIT.COM") End Function End Module