Search code examples
c#action-filter

How to set submodel contents from OnResultExecuting?


I am trying to create a custom ActionFilter which will run before the action result is executed. This attribute will take error information stored in TempData and insert it into a submodel (ErrorModel) within the main strongly typed model. However, I want to be able to apply this to actions that use different model types, which all contain the error submodel. For this reason I cannot/don't want to tie the filter to a particular ViewModel.

If relevant, the ErrorModel is an IEnumerable< ErrorViewModel>.

This is my first time working with ActionFilters, so I feel like I'm shooting in the dark. Also pretty new to reflection. I have a feeling the secret is simply navigating appropriately.

With the code as shown below, I am getting an error on the last line:

Exception Details: System.Reflection.TargetException: Object does not match target type.

How can I use an ActionFilter to insert values into a submodel that is shared across various models? Or is there a better way?

public class SetErrorModelAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        var model = filterContext.Controller.ViewData.Model.GetType();
        var errorModel = model.GetProperty("ErrorModel");
        errorModel.SetValue(errorModel, filterContext.Controller.TempData["Errors"] as IEnumerable<ErrorViewModel>);
    }
}

Solution

  • What you essentially have is

    Type model = filterContext.Controller.ViewData.Model.GetType();
    PropertyInfo errorModel = model.GetProperty("ErrorModel");
    errorModel = tempData as IEnumerable<ErrorViewModel>
    

    Exception Details: System.Reflection.TargetException: Object does not match target type

    But you can skip Reflection. You could use a common interface for your view models and make sure your view models implements the interface.

    public interface IErrorViewModel
    {
        IEnumerable<ErrorViewModel> Errors { get; set; }
    }
    
    public MyViewModel : IErrorViewModel
    {
        public string Name { get; set; }
        public IEnumerable<ErrorViewModel> Errors { get; set; }
    }
    
    
    public class SetErrorModelAttribute : ActionFilterAttribute
    {
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            IErrorViewModel model = filterContext.Controller.ViewData.Model as IErrorViewModel;
            model.Errors = filterContext.Controller.TempData["Errors"] as IEnumerable<ErrorViewModel>;
        }
    }
    

    And usage

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            TempData["Errors"] = new List<ErrorViewModel>
            {
                new ErrorViewModel { ErrorDescription = "Not enough cowbell" },
                new ErrorViewModel { ErrorDescription = "Too much cowbell" }
            };
    
            return RedirectToAction("Error");
        }
    
        [SetErrorModel]
        public ActionResult Error()
        {
            return View("Index", new MyViewModel { Name = "Cowbell" });
        }
    }