Содержание
До этого момента мы создавали компоненты middleware, не заостряя внимание на важном моменте, а именно на том, что порядок, в котором компоненты middleware добавляются в файл Program.cs, определяет порядок их вызова при запросах и соблюдать этот порядок крайне важно для обеспечения безопасности, производительности и функциональности. Сегодня рассмотрим построение конвейера запросов с учётом порядка компонентов middleware в конвейере.
Стоит напомнить, что каждый компонент middleware может обрабатывать запрос до и после последующих в конвейере компонентов. Отсюда и проистекает важность установления порядка компонентов middleware в конвейер обработки запроса ASP.NET Core. Один из примеров того, когда порядок играет важную роль — авторизация пользователя. Рассмотрим простой пример.
Простейшая проверка авторизации пользователя в ASP.NET Core
Опустим пока вопросы обеспечения безопасности и того, как организовать аутентификацию и авторизацию пользователей, используя возможности ASP.NET Core и договоримся, что данные логинов/паролей хранятся где-то в нашем приложении, например, в базе данных. Для доступа к нашему приложению пользователь должен будет передавать в запросе ключ (токен доступа) и приложение будет определять давать доступ к определенному ресурсу или нет.
Вначале, напишем класс middleware для проверки токена.
public class AuthorizationMiddleware
{
private RequestDelegate _next;
public AuthorizationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Query["token"];
if (string.IsNullOrWhiteSpace(token))
context.Response.StatusCode = 401;
else
{
//псевдо-проверка правильности токена
//если передается 123, то считаем, что токен устарел - запрещаем доступ
if (token == "123")
context.Response.StatusCode = 419;//419 - Authentication Timeout
else
await _next(context);//все остальные токену считаем "хорошими" - передаем управление следующему компоненту
}
}
}
Здесь, если параметр token не передан в запросе, то устанавливается код статуса 401 Unauthorized. Если передается значение 123, то считаем такой токен устаревшим и устанавливаем код статуса 419 Authentication Timeout. Все прочие значения токена считаем действительными и передаем управление следующему компоненту middleware в конвейере обработки запроса.
Второй компонент middleware будет возвращать пользователю строку, в зависимости от того, какой путь будет задан:
public class RouterMiddleware
{
public RouterMiddleware(RequestDelegate _)
{
}
public async Task InvokeAsync(HttpContext context)
{
var path = context.Request.Path;
if (path == "/")
{
await context.Response.WriteAsync("Index Page");
}
else
{
if (path == "/admin")
{
await context.Response.WriteAsync("Admin Panel");
}
else
{
context.Response.StatusCode = 404;
}
}
}
}
Этот middleware является терминальным так как не передает управление следующему компоненту. Если пользователь задает путь отличный от /admin и / , то код статуса устанавливается как 404 Not Found.
Очевидно, что, если пользователь не авторизован, то выполнение всех прочих middleware бесполезно. Следовательно в конвейер обработки запроса мы включим наши компоненты следующим образом:
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseMiddleware<AuthorizationMiddleware>();
app.UseMiddleware<RouterMiddleware>();
app.Run();
}
Запустим приложение и посмотрим на результат:
сразу после запуска приложения в URL отсутствуют какие-либо параметры:
вводим «устаревший» токен
вводим «живой» токен и загружаем страницу admin
Демонстрация прохождения запроса от последнего middleware к первому
Выше было сказано, что middleware может обрабатывать запрос как до, так и после следующих в конвейере компонентов. Продемонстрируем обработку запроса компонентом middleware после того, как он пройдет всю последующую цепочку. Добавим в приложение следующий компонент:
public class ErrorMiddleware
{
private RequestDelegate _next;
public ErrorMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
await _next(context); //сначала пропускаем запрос в следующий middleware
switch (context.Response.StatusCode)
{
case 401:
{
await context.Response.WriteAsync("Unauthorized");
break;
}
case 419:
{
await context.Response.WriteAsync("Authentication Timeout");
break;
}
default:
{
await context.Response.WriteAsync($"\r\n Token {context.Request.Query["token"]} is valid");
break;
}
}
}
}
Здесь стоит обратить внимание на то, как организован метод InvokeAsync — вначале мы передаем запрос на обработку следующему в конвейере компоненту:
await _next(context);
таким образом, запрос пройдет сразу в AuthorizationMiddleware, а из этого компонента либо пройдет дальше в RouterMiddleware, либо, если пользователь не будет авторизован вернется снова в ErrorMiddleware и будет обработан частою кода которая располагается ниже вызова await _next(context).
Таким образом организуется обработка запроса в двух направлениях: весь код перед await _next(context) выполняется при движении запроса по конвейеру в прямом направлении, а весь код который стоит после await _next(context) — при движении запроса по конвейеру в обратном направлении.
Теперь приложение будет выглядеть следующим образом:
при ошибке авторизации:
при успешной авторизации
UseExceptionHandler();Итого
Сегодня мы рассмотрели построение конвейера обработки запросов ASP.NET Core с учётом порядка включения компонентов в конвейер, а также разобрались с тем, как компонент middleware может обрабатывать запрос при его движении от последнего компонента к первому.




