Много букв
Когда приходит запрос, то он проходит через конвейер обработчиков запроса. Они идут друг за другом, передавая запрос друг другу по очереди, как эстафетную палку. Каждый конвейер состоит из нескольких обработчиков. Другое название обработчика - middleware
.
Когда вы используете метод Use()
, то вы добавляете ещё один middleware
в конвейер. Этих middleware
может быть множество.
Я так никогда не делаю
app.Use(async(context, next) =>
{
await next.Invoke();
});
app.Use(async(context, next) =>
{
await next.Invoke();
});
app.Use(async(context, next) =>
{
await next.Invoke();
});
Здесь я подключил три midlleware
.
Теперь разберёмся, что значит context
и next
.
context
- экземпляр класса HttpContext. Именно его midlleware
передают друг другу.
next
- ссылка на следующий middleware
.
А так я делаю всегда
Теперь я покажу, как я создаю свои собственные middleware
. Для этого я создаю отдельный класс моего будущего middleware
. Я считаю это лучшей практикой.
public class ExceptionHandlerMiddleware
{
private readonly JsonSerializerOptions serializerOptions;
private readonly RequestDelegate _next;
public ExceptionHandlerMiddleware(RequestDelegate next)
{
_next = next;
serializerOptions = new JsonSerializerOptions { WriteIndented = true };
}
public async Task Invoke(HttpContext context)
{
try
{
// Это часть будет выполнятся до того как HttpContext будет передан следующему middleware
await _next(context); // <--- Передаёт HttpContext следующему middleware и ждёт
// Это часть выполняется, когда следующий middleware завершил свою работу
}
catch (Exception exception)
{
switch (exception)
{
case ObjectNotFoundException:
context.Response.StatusCode = StatusCodes.Status404NotFound;
break;
case BadRequestException:
context.Response.StatusCode = StatusCodes.Status400BadRequest;
break;
default:
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
break;
}
context.Response.ContentType = "application/json";
string result = JsonSerializer.Serialize(
new { message = exception.Message,
status = context.Response.StatusCode
}
);
await context.Response.WriteAsync(result);
}
}
}
Это класс middleware
, который вылавливает все исключения, которые возникают внутри контроллеров. На основе исключения, он отдаёт клиенту соответствующий код ответа.
Как в предыдущем примере:
context
в методе Invoke()
- экземпляр класса HttpContext. Именно его midlleware
передают друг другу.
next
в конструкторе класса ExceptionHandlerMiddleware
- ссылка на следующий middleware
.
Теперь я его подключу к своему конвейеру
namespace src
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<ExceptionHandlerMiddleware>(); // <--- Подключил middleware
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Это класс Startup
. Он используется для настройки приложения. В этом месте подключаются различные сервисы, базы данных, middleware
, настраивается контейнер зависимостей.
Обратите внимание на строчку
app.UseMiddleware<ExceptionHandlerMiddleware>();
Эта строчка добавляет мой middleware
в конвейер обработчиков запроса. Важно соблюдать порядок подключения.
Глоссарий
Middleware
- обработчик запроса. Когда он окончит обработку запроса, то он отдаёт экземпляр класса HttpContext
следующему middleware
.
HttpContext
- это класс, который хранит в себе информацию о запросе. Именно экземпляры этого класса middleware
передают друг другу. Когда доходит очередь последнего middleware
, то он отдаёт его в соответствующий Controller
.
RequestDelegate
- это делегат. Делегат хранит ссылку на метод. В данном случае он хранит ссылку на метод Invoke()
следующего middleware
.