C# ASP.NET Существует ли альтернатива дублированию проверки ошибок при каждом действии контроллера

#c# #asp.net-mvc #asp.net-core

Вопрос:

Если у меня есть контроллер с довольно большим количеством действий, выполняющий аналогичные действия, например, Получает данные из api, что-то делает с ним, возвращает представление с обновленной моделью. Есть ли лучший способ обработки ошибок? В настоящее время я делаю это с помощью ряда методов действий, очевидно, что это дублирование кажется неправильным, но я не могу придумать альтернативу. Спасибо

 public async Task<IActionResult> method(string id)
{
  var result = await _flightRepository.GetLightById(id);
  if (!result.Valid)
  {
    return View("ErrorPage", result.Error.Message);
  }

  var viewModel = new FlightViewModel
  {
    Flight = result.Result
  };
  return View(viewModel);
}
 

В принципе, я хочу каким-то образом инкапсулировать логику обработки ошибок, чтобы возвращать представление об ошибке, если значение valid равно false, в противном случае заполнить viewmodel и вернуть представление. Свойство valid возвращает значение true, если с запросом произошла ошибка (это делается на уровне api).

Спасибо за любую помощь

Комментарии:

1. У вас есть базовый тип для ответа, возвращаемого GetLightById ? Что-то вроде IReturn<T> » а » или что-то похожее?

2. метод расширения может хорошо подойти.

3. @A. Chiesa yeh возвращает репозиторий Result<T>, результат репозитория содержит модель T, а также свойство ошибки (с msg и httpstatuscode). Это заполняется в слое хранилища, если возникает проблема с получением данных

Ответ №1:

Вы должны использовать фильтры для повторного использования кода в своих действиях.

Например, в вашем случае ваш фильтр может быть:

 public class FlightValidator: ActionFilterAttribute
{
    private readonly string _flightIdRouteKey; // e.g 23
    private readonly string _errorViewName; // e.g "ErrorPage"
    private readonly IFlightRepository _flightRepo;

    public FlightValidator(string flightIdRouteKey, string errorViewName, IFlightRepository flightRepository)
    {
        _flightIdRouteKey = flightIdRouteKey;
        _errorViewName = errorViewName;
        _flightRepo = flightRepository;
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        int flightId = (int)context.RouteData.Values[_flightIdRouteKey];

        var result = await _flightRepo.GetFlightById(flightId);
        if (!result.Valid)
        {
            context.Result = new ViewResult
            {
                ViewName = _errorViewName, 
                ViewData = new ViewDataDictionary(result.ViewData)
                {
                    Model = model
                }
            };
            return;
        }

        await next();
    }
}
 

Теперь вы можете использовать фильтр следующим образом:

 [TypeFilter(typeof(FlightValidator), Arguments = new object[] { "id", "ErrorPage"})]
public async Task<IActionResult> method(string id)
{
  var viewModel = new FlightViewModel
  {
    Flight = result.Result
  };

  return View(viewModel);
}
 

Я предлагаю вам обратиться к документам Filters для получения дополнительной информации о фильтрах.

Комментарии:

1. Спасибо, я думаю, что и этот, и другой ответ являются жизнеспособными решениями, однако мне интересно, не является ли также излишним и более читабельным просто дублировать код… но СУХО …

2. DRY делает ваш код чистым , ремонтопригодным и масштабируемым, а также экономит много времени при повторном использовании кода в разных местах.

Ответ №2:

Если вы используете статический метод, подобный этому:

 static async Task<IActionResult> ProcessAsync<T>(
    Func<Task<RepositoryResult<T>>> process, 
    Func<T, IActionResult> ifValid
)
{
    var result = await process();
    if (!result.Valid)
    {
        return View("ErrorPage", result.Error.Message);
    }
    
    return ifValid(result.Result);
}
 

Тогда вы можете использовать его вот так:

 public Task<IActionResult> method(string id)
{
    return ProcessAsync(
        () => _flightRepository.GetLightById(id),
        flight =>
        {
            var viewModel = new FlightViewModel
            {
                Flight = flight
            };

            return View(viewModel);
        }
    );
}
 

Статический метод может быть определен в любом месте по вашему желанию — в базовом классе контроллера или в совершенно другом пространстве имен (в этом случае вы импортируете его с using static Some.Namespace.With.Class; помощью .

Кроме того, я предлагаю вам использовать соглашение о Async суффиксе для асинхронных методов — поэтому, поскольку GetLightById возвращает Task<T> , я предлагаю переименовать его GetLightByIdAsync .

Комментарии:

1. Спасибо, я думаю, что и этот, и другой ответ являются жизнеспособными решениями, однако мне интересно, не является ли также излишним и более читабельным просто дублировать код… но СУХО …

2. Да, и, конечно, если вы когда-нибудь захотите добавить дополнительную стандартную проверку, вам тоже придется скопировать/вставить ее, поэтому обычно лучше запускать все с помощью одного метода (или фильтра, как в другом ответе).