Прежде всего, извините за мой английский.
Ну, я разрабатываю .Net Core Rest Api (я разрабатывал это раньше для asp.net mvc, но в net core - впервые), и я столкнулся со странной вещью.
Приложение разработано в различных слоях: WebApi -> Core <- Инфраструктура. </p>
Дело в том, что в службах основного уровня я решил использовать некоторые исключения для некоторых бизнес-правил. Например, есть некоторые переводимые сущности, когда вы хотите создать новую сущность, вы должны предоставить перевод для каждой культуры, определенной в приложении. Если пропущен перевод, я выбрасываю исключение.
Пример метода:
public async Task<CompanyResource> Add(CompanyResource companyResource)
{
var cultures = await _cultureRepository.GetAllAsync();
if (cultures.Any(culture => companyResource.Translations.All(t => t.CultureCode != culture.Code)))
throw new MissingTranslationsException();
await _companyResourceRepository.AddAsync(companyResource);
return companyResource;
}
Теперь мне было интересно, как обрабатывать исключения в контроллере для отправки клиенту правильного httpStatusCode в зависимости от каждого типа исключения и удобочитаемого сообщения.
В поисках я нашел 3 основных способа:
- Попробуй / поймай в методе в контроллере
- Пользовательский фильтр исключений
- Пользовательское исключение Midleware
Я решил использовать промежуточное ПО, так как это, кажется, лучший способ - иметь центрированное место во всем приложении для обработки исключений и отправки сообщений клиентам, поэтому я сделал эту реализацию:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var code = HttpStatusCode.InternalServerError;
if (exception is InvalidPermissionsException) code = HttpStatusCode.Forbidden;
else if (exception is DuplicateEntityException) code = HttpStatusCode.Conflict;
else if (exception is MissingTranslationsException) code = HttpStatusCode.BadRequest;
else if (exception is ApplicationException) code = HttpStatusCode.BadRequest;
var result = JsonConvert.SerializeObject(new []{ exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
await context.Response.WriteAsync(result);
}
}
Затем я подключил промежуточное ПО к конвейеру:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...other code
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseHttpsRedirection();
app.UseMvc();
}
Ну, в этот момент все работало отлично, buuuuuut, я просто заметил, что время отклика далеко не приемлемо, когда исключения обрабатываются промежуточным ПО.
Время отклика при использовании промежуточного программного обеспечения:
Проверено несколько раз, и среднее время ответа составляет 460 мс + -.
Ну, я твердо решил, что должен быть способ идти быстрее, а затем я попробовал другие 2 метода. За исключением атрибута фильтра:
public class HandleExceptionAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var code = HttpStatusCode.InternalServerError;
var exception = context.Exception;
if (exception is InvalidPermissionsException) code = HttpStatusCode.Forbidden;
else if (exception is DuplicateEntityException) code = HttpStatusCode.Conflict;
else if (exception is MissingTranslationsException) code = HttpStatusCode.BadRequest;
else if (exception is ApplicationException) code = HttpStatusCode.BadRequest;
context.HttpContext.Response.StatusCode = (int)code;
context.Result = new JsonResult(new[] { exception.Message });
}
}
А при использовании фильтра время отклика уменьшилось до 250 мс + - в среднем.
Я все еще крутой, это слишком медленно .. Поэтому я выбрал последний вариант: использование try catch внутри метода действия:
[HttpPost]
public async Task<IActionResult> Post([FromBody] CompanyResourceDto companyResourceDto)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
var companyresource = _mapper.Map<CompanyResource>(companyResourceDto);
try
{
companyresource = await _resourcesService.Add(companyresource);
}
catch (Exception e)
{
return BadRequest(new[] {e.Message});
}
return CreatedAtRoute("Getresource", new { id = companyresource.Id }, _mapper.Map<CompanyResourceDto>(companyresource));
}
И, черт возьми, среднее время отклика в случае исключения уменьшилось до 85 мс + - (который я исключал с самого начала, с промежуточным программным обеспечением).
Честно говоря, я считаю, что для обеспечения безопасности и разделения интересов лучше всего использовать промежуточное программное обеспечение, потому что я не хочу, чтобы мои контроллеры знали о чем-либо, кроме нормального потока ..
Итак, мои вопросы:
Я что-то не так делаю с промежуточным ПО?
Если нет, то почему так медленно и какой подход я должен принять, принимая во внимание, что время отклика имеет решающее значение в этом приложении?
Если да, что я должен изменить?
Спасибо и всего наилучшего,