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 (开发者_C百科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...
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();
}
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.
Why don't you define a static BusinessValidator helper and do something like:
[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);
}
精彩评论