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>);
}
}
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" });
}
}