Как мы уже знаем, запросы в ASP.NET Core обрабатываются по принципу конвейера. В самом простейшем приложении ASP.NET Core встраивается один делегат запроса, обрабатывающий все входящие запросы. В этом случае конвейер запросов как таковой отсутствует. Вместо этого в ответ на каждый HTTP-запрос вызывается одна анонимная функция.
Терминальный компонент middleware. Метод Run
Терминальным называется компонент middleware завершающий обработку запроса. Самый простой способ создания middleware представляет метод расширения Run()
, Этот метод добавляет терминальный middleware и, соответственно, Run()
должен использоваться в самом конце — перед запуском приложения. Например, создадим такой терминальный компонент middleware:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.Run(async (context) => await context.Response.WriteAsync(DateTime.Now.ToShortDateString()));//терминальный middleware app.Run(); }
Разберемся с тем, что мы здесь написали.
Во-первых, в коде используется два разных метода Run
. Первый Run()
— метод расширения с помощью которого мы добавляем терминальный middleware, второй Run()
— запускает наше приложение. Как только мы запускаем приложение методом Run()
, то всё идёт после вызова этого метода не имеет смысла. Например, в следующем коде мы никогда не дойдем до запуска терминального middleware:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.Run(); //весь код после этой строки не имеет смысла app.Run(async (context) => await context.Response.WriteAsync(DateTime.Now.ToShortDateString()));//терминальный middleware }
Во вторых, при создании middleware мы использовали async
/await
. Так как мы создаем web-приложения, то к этому приложению, в теории, могут обращаться сотни и тысячи пользователей и, чтобы наш сервер мог обрабатывать всю эту массу запросов мы используем методы асинхронного программирования. Наш компонент middleware, по сути, выполняет одну задачу — записывает в тело ответа сервера строку содержащую текущую дату. Таким образом, если запустить приложение, то мы увидим следующее:
Если предполагается, то наш компонент middleware будет выполнять несколько операций, то мы можем вынести его, например, в отдельный метод:
{ public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.Run((context)=>DateTimeWriter(context));//терминальный middleware app.Run(); } static async Task DateTimeWriter(HttpContext context) { context.Response.Headers.ContentLanguage = "ru-RU"; context.Response.Headers.ContentType = "text/plain; charset=utf-8"; await context.Response.WriteAsync(DateTime.Now.ToLongDateString()); } }
Здесь мы вынесли наш middleware в отдельный метод и добавили в ответ заголовки, чтобы корректно отображать русские символы. В итоге, вид приложение после запуска будет таким:
Метод Use
В отличие от метода Run
, Use
позволяет добавить в конвейер запросов ASP.NET Core компонент, после которого запрос будет передаваться дальше по конвейеру. Следовательно, такой компонент middleware должен получать ссылку на следующий компонент в конвейере. Например, создадим конвейер запроса, который будет формировать ответ сервера в виде небольшого html-документа, используя два middleware компонента:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); //первый middleware app.Use(async (context, next) => { await context.Response.WriteAsync("<html><body> Welcome! <br>"); await next.Invoke(); }); //второй middleware app.Run(async context => { await context.Response.WriteAsync($"Current date: {DateTime.Now.ToShortDateString()}.</body></html>"); }); app.Run(); }
Таким образом, в первом компоненте к ответ добавляются открывающие html-теги и строка приветствия, а во второй — строка с датой и закрывающие теги. Конечно, такой конвейер с практической точки зрения малополезен, но помогает наглядно увидеть работу нескольких middleware
Итого
Сегодня мы научились создавать собственный конвейер запросов в ASP.NET Core, используя методы Use
и Run
для добавления очередного и замыкающего (терминального) компонента middleware в конвейер. На этом тема работы с middleware в ASP.NET Core не заканчивается. В следующей части рассмотрим вопросы, касающиеся работы с заголовками ответа в ASP.NET Core.