В предыдущей части мы на основании данных запроса создали простейшую систему маршрутизации в нашем приложении ASP.NET Core. При этом, в ASP.NET Core имеются возможности создания веток конвейера запросов. В частности, сегодня мы рассмотрим создание конвейера запросов с использованием метода Map
.
Метод Map
Метод Map
позволяет создать ветку конвейера, которая будет обрабатываться по определенному пути. Этот метод является методом расширения для IApplicationBuilder
и имеет ряд версий, среди которых наиболее часто используется следующая:
Map(IApplicationBuilder, String, Action<IApplicationBuilder>)
pathMatch
— путь запроса с которым будет сопоставляться ветка конвейера.configuration
— делегат, в котором будет создаваться ветка конвейера
Чтобы показать как работает эта версия метода Map
, перепишем наше предыдущее приложение. Вот как оно должно выглядеть на данный момент:
public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.Run(async context => { //добавляем заголовок, чтобы корректно отображать кириллицу if (context.Response.HasStarted == false) context.Response.Headers.ContentType = "text/plain; charset=utf-8"; //анализируем путь запроса switch (context.Request.Path) { case "/": { await context.Response.WriteAsync("Hello, Anonymous"); break; } case "/date": { //получаем значение параметра format var format = context.Request.Query["format"].ToString(); if ((format == "") || (format == "short")) await context.Response.WriteAsync($"Date: {DateTime.Now.ToShortDateString()}"); else if (format == "full") await context.Response.WriteAsync($"Date: {DateTime.Now.ToLongDateString()}"); else await context.Response.WriteAsync($"Error: Wrong format"); break; } case "/time": { await context.Response.WriteAsync($"Time: {DateTime.Now.ToShortTimeString()}"); break; } default: { await context.Response.WriteAsync($"Error: Wrong path"); break; } } }); app.Run(); } }
С использованием метода Map
это приложение можно переписать следующим образом:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); //передаем в ответ кодировку текста app.Use(async (context, next) => { context.Response.OnStarting( () => { context.Response.ContentType = "text/plain; charset=utf-8"; return Task.CompletedTask; }); await next.Invoke(); }); //ветка для date app.Map("/date", (app) => { app.Run ( async context => { //получаем значение параметра format var format = context.Request.Query["format"].ToString(); if ((format == "") || (format == "short")) await context.Response.WriteAsync($"Date: {DateTime.Now.ToShortDateString()}"); else if (format == "full") await context.Response.WriteAsync($"Date: {DateTime.Now.ToLongDateString()}"); else await context.Response.WriteAsync($"Error: Wrong format"); } ); }); //ветка для time app.Map("/time", (app) => { app.Run ( async context => { await context.Response.WriteAsync($"Time: {DateTime.Now.ToShortTimeString()}"); } ); }); //обрабатываем корень и все прочие пути app.Run(async context => { if (context.Request.Path == "/") await context.Response.WriteAsync("Hello, Anonymous"); else await context.Response.WriteAsync($"Error: Wrong path"); }); app.Run(); } }
Здесь стоит обратить внимание на то, что в метод Map
нельзя передавать корневой путь, то есть "/"
так как это вызовет ошибку, поэтому корень и все прочие пути по которым приложение должно выдать пользователю ошибку были вынесены в метод Run()
. В остальном же, если сравнить код, использующий метод Map()
и предыдущую версию приложения, то можно увидеть, что мы, по сути, для каждой ветки из switch
создали ветку конвейера в методе Map()
. При этом, мы можем выносить создание ветки в отдельные методы. Например, так:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.Map("/date", Date); app.Map("/time", Time); //обрабатываем корень и все прочие пути app.Run(async context => { if (context.Request.Path == "/") await context.Response.WriteAsync("Hello, Anonymous"); else await context.Response.WriteAsync($"Error: Wrong path"); }); app.Run(); } static void Date(IApplicationBuilder app) { app.Run ( async context => { //получаем значение параметра format var format = context.Request.Query["format"].ToString(); if ((format == "") || (format == "short")) await context.Response.WriteAsync($"Date: {DateTime.Now.ToShortDateString()}"); else if (format == "full") await context.Response.WriteAsync($"Date: {DateTime.Now.ToLongDateString()}"); else await context.Response.WriteAsync($"Error: Wrong format"); } ); } static void Time(IApplicationBuilder app) { app.Run ( async context => { await context.Response.WriteAsync($"Time: {DateTime.Now.ToShortTimeString()}"); } ); } }
Однако, этот код не до конца показывает суть использования Map()
. Дело в том, что внутри Map()
мы также можем «разветвлять» наш конвейер самыми разными способами. Например, добавим в наше приложение ещё два пути:
date/description
— будет возвращать дополнительную информацию по текущей дате — день в году, день недели и т.д.date/yesterday
— будет возвращать предыдущую дату
Вот как может выглядеть вызов Map()
для пути /date
:
public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); //ветка для date app.Map("/date", (appBuilder) => { appBuilder.Map("/description", DateDescription); //путь - /date/description appBuilder.Map("/yesterday" , DateYesterday); //путь - /date/yesterday appBuilder.Run(async context => { if (context.Request.Path=="") await DateRootPath(context); else await context.Response.WriteAsync($"Error: Wrong path"); }); } ); //тут другие вызовы Map(), Use(), Run() app.Run(); void DateYesterday(IApplicationBuilder app) { app.Run(async context => { var date = DateTime.Now.AddDays(-1); await context.Response.WriteAsync($"Вчера было: {date.ToShortDateString()} \r\n"); await context.Response.WriteAsync($" День в году: {date.DayOfYear} \r\n"); await context.Response.WriteAsync($" День в месяце: {date.Day} \r\n"); await context.Response.WriteAsync($" День недели: {(int)date.DayOfWeek}"); }); } void DateDescription(IApplicationBuilder app) { app.Run(async context => { var date = DateTime.Now; await context.Response.WriteAsync($"Текущая дата: {date.ToShortDateString()} \r\n"); await context.Response.WriteAsync($" День в году: {date.DayOfYear} \r\n"); await context.Response.WriteAsync($" День в месяце: {date.Day} \r\n"); await context.Response.WriteAsync($" День недели: {(int)date.DayOfWeek}"); }); } async Task DateRootPath(HttpContext context) { var format = context.Request.Query["format"].ToString(); if ((format == "") || (format == "short")) await context.Response.WriteAsync($"Date: {DateTime.Now.ToShortDateString()}"); else if (format == "full") await context.Response.WriteAsync($"Date: {DateTime.Now.ToLongDateString()}"); else await context.Response.WriteAsync($"Error: Wrong format"); } } }
Результат работы приложение представлен на рисунках ниже.
Обработка пути /date
обработка пути
/date/description
обработка пути /date/yesterday
обработка всех прочих путей
/date/xxxx
Ветка в конвейере создается один раз (пример)
Когда мы используем метод Map
для ветвления конвейера запросов, то стоит учитывать, что действия, в делегате выполняются немедленно и один раз, но, при этом middleware в этой ветке будут выполняться каждый раз. Чтобы наглядно продемонстрировать этот момент обратимся к нашему коду приложения и посмотрим на делегат, который мы передаем в метод Map
для вывода текущего времени:
//ветка для time app.Map("/time", Time); void Time(IApplicationBuilder app) { app.Run ( async context => { await context.Response.WriteAsync($"Time: {DateTime.Now.ToShortTimeString()}"); } ); }
Здесь в middleware производится простая операция — клиенту возвращается текущее время. Изменим делегат таким образом, чтобы чтение значения времени осуществлялось до middleware, например, так:
void Time(IApplicationBuilder app) { var time = DateTime.Now;//это действие выполнится немедленно и один раз app.Run ( async context => { await context.Response.WriteAsync($"Time: {time.ToShortTimeString()}"); } ); }
Теперь запустим приложение, подождем немного и попробуем перейти по пути /time
. Вопреки ожиданиям, что мы увидим текущее время, мы увидим время, когда была создана ветка конвейера, т.е., по сути, время запуска приложения:
Итого
Сегодня мы разобрались с третьим методом встраивания различных middleware в конвейер запросов ASP.NET Core — Map()
. С использованием метода Map()
мы можем создавать ветки конвейера, каждая из которых будет обрабатывать запросы по определенному пути. При этом, действия, указанные в делегате для метода Map()
выполняются немедленно и один раз, в то время, как действия во встраиваемом в ветку middleware выполняются каждый раз.