I have an ActionFilter with an override on the OnActionExecuted method. The filterContext.Controller.ViewData.Model is always null on a POST operation. I did find the following article that seems to be saying that it should not be null but this must have been an earlier version of MVC. This is MVC3. What should I be getting?
Model availability inside ActionFilter
UPDATE:
I've figured out the answer to the original question. I had a custom ActionResult that outputs JSON with a custom date formatter. The problem was that the model is not being set in the controller.
In my custom ActionResult the ExecuteResult method get passed the ControllerContext which would be nice if I could set the Model there:
context.Controller.ViewData.Model = _data;
But this is to late in the cycle and the result is still null in the ActionFilter. This seems to mean that I need to manually set the model in the controller:
ControllerContext.Controller.ViewData.Model = model;
Or
View(model);
Which then means I need to remember to do this every time I use this custom ActionResult. Is there a more elegant way?
YET ANOTHER UPDATE:
I found a way to do this it just isn't as elegant as I hoped.
In my constructor for the comstom ActionResult I sending in the controller, that way at least it will alway be consistent:
public JsonNetResult(object data, Controller controller) {
SerializerSettings = new JsonSerializerSettings();
_data = data;
controller.ControllerContext.Controller.ViewData.Model = _data;
}
Another approach is to use a base controller to automatically handle the storing of the action parameters collection for later use:
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Items["ActionParms"] = filterContext.ActionParameters.ToDictionary(p => p.Key, p => p.Value);
base.OnActionExecuting(filterContext);
}
}
then in your attribute:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var dictionary = filterContext.HttpContext.Items["ActionParms"] as Dictionary<string, object>;
if (dictionary != null)
{
foreach (var o in dictionary.Keys)
{
// do something here
}
}
base.OnActionExecuted(filterContext);
}
It uses HttpContext items which is not very nice but I don't know that you can access your ViewBag or ViewData in the attribute.
In order to decide whether you want to handle the request in your attribute, you can interrogate the action name and other parameter information:
var action = filterContext.ActionDescriptor.ActionName;
var parms = filterContext.ActionDescriptor.GetParameters();
foreach (var parameterDescriptor in parms)
{
// do something here
}
I found a solution like yours using the OnModelUpdated event to set that property before.
I have the ModelBinder:
public class CustomModelBinder: DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
controllerContext.Controller.ViewData.Model = bindingContext.Model;
base.OnModelUpdated(controllerContext, bindingContext);
}
}
After that, you need to set the default binder to your new model binder in Application_Start() section in Global.asax:
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
Finally you can access your Model in an ActionFilter:
public class TraceLog : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//filterContext.Controller.ViewData.Model now isn't null
base.OnActionExecuted(filterContext);
}
}