Могу ли я вернуть результат действия из фильтра действий? - PullRequest
6 голосов
/ 14 мая 2011

Обычно я проверяю свою модель в методе действия перед передачей данных в базу данных.

[HttpPost]
public ActionResult MyActionMethod(MyModelType model){
if (ModelState.IsValid){
   //commit changes to database...
   return View("SuccessView",model);
}
return View(model);
}

Но в некоторых очень редких случаях мне нужно провести дополнительную проверку на бизнес-уровне во время фиксации модели. Если возникает ошибка проверки, я хотел бы вызвать исключение на бизнес-уровне и использовать это исключение для возврата представления с ошибками проверки.

Я ищу способ реализовать это без изменения какого-либо кода в моем контроллере. Поэтому я ищу способ избежать чего-то такого:

[HttpPost]
public ActionResult MyActionMethod(MyModelType model){
if (ModelState.IsValid){
   try {
   //commit changes to database...
   } catch (ValidationException e){
      ModelState.AddModelError(...);
      return View(model);
   }
   return View("SuccessView",model);

}
return View(model);
}

Есть ли способ сделать это?

Я думал о фильтре действий, который перехватывает ValidationExceptions и возвращает подходящее представление с ошибками проверки до того, как включится обычный фильтр [HandleError]. Возможно ли что-то подобное?

Редактировать: Я только что нашел решение (см. Ниже), но я не смогу пометить это как правильный ответ, пока не пройдет 48 часов ...

Ответы [ 3 ]

6 голосов
/ 14 мая 2011

Я только что нашел решение после небольшого поиска в исходном коде ASP.NET MVC:

Это невозможно сделать с помощью фильтра действий, потому что он вызывается до и после вызова метода действия,но на самом деле он не переносит вызов метода действия.

Однако это можно сделать с помощью пользовательского ActionMethodInvoker:

public class CustomActionInvoker : ControllerActionInvoker
{
    protected override ActionResult InvokeActionMethod(
        ControllerContext controllerContext, 
        ActionDescriptor actionDescriptor, 
        System.Collections.Generic.IDictionary<string, object> parameters)
    {
        try
        {
            //invoke the action method as usual
            return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);
        }
        catch(ValidationException e)
        {
            //if some validation exception occurred (in my case in the business layer) 
            //mark the modelstate as not valid  and run the same action method again
            //so that it can return the proper view with validation errors. 
            controllerContext.Controller.ViewData.ModelState.AddModelError("",e.Message);
            return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);
        }
    }
}

И затем на контроллере:

protected override IActionInvoker CreateActionInvoker()
{
    return new CustomActionInvoker();
}
3 голосов
/ 14 мая 2011

Очевидно, вы можете установить результат действия в фильтре действий. Но если вы используете ActionExecuting (filterContext.Result) для установки результата действия, тогда ваш код контроллера не будет вызываться. Я думаю, что вместо ActionFilter, если дополнительная логика проверки связана с моделью, лучшим решением было бы использование связывателя пользовательской модели.

Надеюсь, это поможет.

1 голос
/ 14 мая 2011

Почему бы вам не определить статический помощник BusinessValidator и сделать что-то вроде:

[HttpPost]
public ActionResult MyActionMethod(MyModelType model){
var businessErrors = null;
if ((ModelState.IsValid) && (BusinessValidator<MyModelType>.IsValid(model, out businesErrors)){
   //commit changes to database...
   return View("SuccessView",model);
}

if (businessErrors != null)
{
 // TODO: add errors to the modelstate
}

return View(model);
}
...