Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing filterContext.Result in OnResultExecuting

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
    }
}
like image 583
Bobby Tables Avatar asked Apr 17 '15 07:04

Bobby Tables


1 Answers

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.

like image 72
Daniel J.G. Avatar answered Sep 20 '22 12:09

Daniel J.G.