Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I return an action result from an action filter?

Usually I am validating my model in the action method before committing data to the database.

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

But in some very rare instances I need to perform some extra validation in the business layer while the model is being committed. If a validation error occurs, I'd like to raise an exception in the business layer and use that exception to return a view with validation errors.

I'm looking for a way to implement this without altering any code in my controller. So I'm looking for a way to avoid something this:

[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);
}

Is there any way to do this?

I was thinking of an action filter that catches ValidationExceptions and returns the suitable view with validation errors before the regular [HandleError] filter kicks in. Is something like this possible?

Edit: I just found the solution (see below), but I won't be able to mark this as the correct answer until 48 hours have passed...

like image 936
Adrian Grigore Avatar asked May 14 '11 10:05

Adrian Grigore


2 Answers

I just found the solution after searching a bit in the ASP.NET MVC source code:

It can't be done with an action filter because that is called before and after the action method is called, but it does not actually wrap the action method call.

However, it can be done with a custom 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);
        }
    }
}

And then, on the controller:

protected override IActionInvoker CreateActionInvoker()
{
    return new CustomActionInvoker();
}
like image 118
Adrian Grigore Avatar answered Sep 21 '22 12:09

Adrian Grigore


You can obviously set the action result in action filter. But if you are using the ActionExecuting (filterContext.Result)to set the action result then your controller code will not be invoked. I think instead of ActionFilter, if the extra validation logic is tied with the model, a better solution would to use a Custom Model binder.

Hope that helps.

like image 45
Kazi Manzur Rashid Avatar answered Sep 22 '22 12:09

Kazi Manzur Rashid