So, I defined a master model that is shared by every view in my MVC site. This model defines a bunch of properties that are always used on every page, such as a page title, page description, the google tag manager code, etc.
I need this model to be filled before the action result, so that I can use those properties on the view.
In order to do this, I created a custom Action Filter Attribute that is used by every action:
public class SetupMasterModelActionFilter : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext filterContext) { ViewResultBase result = filterContext.Result as ViewResultBase; if (result == null) { return; } MasterModel model = result.Model as MasterModel; if (model == null) { return; } SetupMasterModel(model); SetupViewBag(result, model); } private void SetupMasterModel(MasterModel model) { ... } private void SetupViewBag(ViewResultBase result, MasterModel model) { ... } }
In order to execute this filter before every action I had to register it in the GlobalFilters at the Global.asax.cs file:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); GlobalFilters.Filters.Add(new SetupMasterModelActionFilter()); RouteConfig.RegisterRoutes(RouteTable.Routes); }
There are, however, some pages that don’t use this master model, so I need to exclude the use of this filter on certain actions. In order to do this, I created a new Filter Provider that looks for an exclude filter and filters out the one targeted:
public class ExcludeFilterProvider : IFilterProvider { private FilterProviderCollection filterProviders; public ExcludeFilterProvider(IFilterProvider[] filters) { this.filterProviders = new FilterProviderCollection(filters); } public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { Filter[] filters = this.filterProviders.GetFilters(controllerContext, actionDescriptor).ToArray(); IEnumerable<ExcludeFilterAttribute> excludeFilters = (from f in filters where f.Instance is ExcludeFilterAttribute select f.Instance as ExcludeFilterAttribute); List<Type> filterTypesToRemove = new List<Type>(); foreach (ExcludeFilterAttribute excludeFilter in excludeFilters) { filterTypesToRemove.Add(excludeFilter.FilterType); } IEnumerable<Filter> res = (from filter in filters where !filterTypesToRemove.Contains(filter.Instance.GetType()) select filter); return res; } }
The exclude filter attribute that will decorate the excluded actions:
public class ExcludeFilterAttribute : FilterAttribute { private Type filterType; public ExcludeFilterAttribute(Type filterType) { this.filterType = filterType; } public Type FilterType { get { return this.filterType; } } }
And we add the new provider at the start of the application and remove the old ones:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); GlobalFilters.Filters.Add(new SetupMasterModelActionFilter()); IFilterProvider[] providers = FilterProviders.Providers.ToArray(); FilterProviders.Providers.Clear(); FilterProviders.Providers.Add(new ExcludeFilterProvider(providers)); RouteConfig.RegisterRoutes(RouteTable.Routes); }
With this, every action will setup the master model properties except the ones that implement the exclude filter:
public class HomeController : Controller { [ExcludeFilter(typeof(SetupMasterModelActionFilter))] public ActionResult Index() { HomeModel model = new HomeModel(); return View(model); } }