I have a controller action that has an attribute applied in which if the ModelState has errors it sets them as the JsonResult
in the OnResultExecuting
method.
I set the value in MyAction
. I change it in the attribute in OnResultExecuting
but in the OnResultExecuted
which is in the controller the result is the one from the controller not the one which was set in the attribute.
So my question is why does the value in OnResultExecuted
remain unchanged and how do i make it stop doing that ?
public class MyController:Controller
{
[ValidateDatedObject(SkipActionExecution = true, LeaveJustModelState = true)]
public JsonResult MyAction(ViewModel viewModel)
{
return new JsonResult { Data = new { Success = false }}; // Setting the initial value
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);//filterContext.Result here is the on from the controller instead of the one from the attribute
}
}
public class ValidateDatedObject : ModelValidationFilter
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
}//filterContext.Result here is the one from the attribute
}
public abstract class ModelValidationFilter : ActionFilterAttribute
{
private JsonResult getModelStateAsJsonResult(ModelStateDictionary modelState)
{
return new JsonResult { Data = new { modelState = SerializeErrors(modelState) } };
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.Result = getModelStateAsJsonResult(filterContext.Controller.ViewData.ModelState); //Setting filterContext.Result here
}
}
That's because in the OnResultExecuting
you are replacing the current result with a new instance. That will modify the result in the ResultExecutingContext
but will leave the overall result unchanged.
You could however modify the result instead of replacing it
public abstract class ModelValidationFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
//Modify the values in the current filterContext.Result instead of replacing it with a new instance
var jsonResult = filterContext.Result as JsonResult;
if(jsonResult == null) return;
//possibly replace Data only under certain conditions
jsonResult.Data = new { modelState = SerializeErrors(modelState) };
}
}
The reason for this is the way ResultFilters are executed by MVC. You can check the implementation of ControllerActionInvoker.InvokeActionResultFilterRecursive
. This is the code calling OnResultExecuting
on each filter, executing the action and then calling OnResultExecuted
in reverse order.
If you look carefully, you will notice that ResultExecutedContext
is created with a reference to the original actionResult object, not with a reference to ResultExecutingContext.Result
. (Unless you set ResultExecutingContext.Cancel=true
which will stop processing additional filters and return whatever result it has at that moment, but that also means controller OnResultExecute won't be executed)
So there is an assumption in this code in that ResultFilters may modify the values of the properties in ResultExecutingContext.Result
but not entirely replace it with a new instance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With