В предыдущей части мы на основании данных запроса создали простейшую систему маршрутизации в нашем приложении 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 выполняются каждый раз.


