geek yapping

Twitter












IJoinedFilter Part 3: IFilterPriority

Note: This article is a continuation of the series on IJoinedFilter:

  1. IJoinedFilter
  2. AutoMapFilter meet IJoinedFilter 

In-lining mapping and injection aspects – yuck!

After implementing several aspects of controller actions as compos-able filters, it became apparent that controlling the order would be import. One of the latest additions is a filter to build up a view model property with data aspects. An example would be a select list for states on a person edit view. Normally, the states would be fetched and set on the model in the controller action:

	
	public class PriorityController : Controller
	{
		public ActionResult Injected()
		{
			var person = new Person
			             {
			             	Name = "John Doe",
			             	BirthDate = new DateTime(1980, 1, 1),
			             	State = StatesService.GetStates()[1]
			             };
			var personViewModel = new PersonViewModel(person);
			person.AllStates = StatesService.GetStates();
			return View(person);
		}
	}

	public class StatesService
	{
		public static IList<State> GetStates()
		{
			return new List<State>
			       {
			       	new State {Name = "Nebraska", Id = 1},
			       	new State {Name = "Iowa", Id = 2},
			       	new State {Name = "Kansas", Id = 3}
			       };
		}
	}

	public class Person
	{
		public string Name { get; set; }
		public DateTime BirthDate { get; set; }
		public State State { get; set; }
	}

	public class State
	{
		public int Id { get; set; }
		public string Name { get; set; }
	}

	public class PersonViewModel
	{
		public string Name { get; set; }
		public string BirthDate { get; set; }
		public string StateId { get; set; }
		public IList<State> AllStates { get; set; }
	}

Extracting the injection aspect

This leads to repetitive code every time a view model requires this data. Furthermore, we are forced to create the appropriate view model inside the controller action instead of relying on the mapping filter. To separate concerns we created a filter to find these data aspects and inject the values. Now, we simply add a property to the view model and the filter will take care of the rest!

	
	public class InjectStatesFilter : ViewModelInjectFilter
	{
		protected override Func<PropertyInfo,bool> InjectProperty()
		{
			return p => p.PropertyType == typeof (IList<State>);
		}

		protected override object WithValue()
		{
			return StatesService.GetStates();
		}
	}

	public abstract class ViewModelInjectFilter : IActionFilter
	{
		public virtual void OnActionExecuting(ActionExecutingContext filterContext)
		{
		}

		public virtual void OnActionExecuted(ActionExecutedContext filterContext)
		{
			var viewResult = filterContext.Result as ViewResultBase;
			if (viewResult == null || viewResult.ViewData.Model == null)
			{
				return;
			}
			var model = viewResult.ViewData.Model;
			var property = model.GetType().GetProperties().FirstOrDefault(InjectProperty());
			if (property == null)
			{
				return;
			}
			var value = property.GetValue(model, null);
			if (value == null)
			{
				property.SetValue(model, WithValue(), null);
			}
		}

		/// <summary>
		/// The criteria to use to find the property to inject.0
		/// </summary>
		/// <returns></returns>
		protected abstract Func<PropertyInfo,bool> InjectProperty();

		/// <summary>
		/// The value to inject, only queried if a matching, null property is found.
		/// </summary>
		/// <returns></returns>
		protected abstract object WithValue();
	}

Going back to mapping and injecting filters with this new approach helps remove two aspects of duplication from the controller action:

	public class PriorityController : Controller
	{
		public ActionResult Injected()
		{
			var person = new Person
			             {
			             	Name = "John Doe",
			             	BirthDate = new DateTime(1980, 1, 1),
			             	State = StatesService.GetStates()[1]
			             };

			return View(person);
		}
	}

IFilterPriority - Back to the aspect!

In the example, PersonViewModel can be built up with a list of states, if the mapping filter is executed first. To guarantee this, I have added an interface IFilterPriority with a routine GetOrder() to the IJoinedFilter framework. It returns an integer representing the priority of the filter in the execution chain. I chose an integer to mirror FilterAttribute.Order in the MVC framework, which sadly was not made into a compo-sable extensibility point.

	public interface IFilterPriority
	{
		int GetOrder();
	}

JoinedFilterLocator has been modified to add filters based on this order to the FilterInfo result. The actual execution is dependent on the MVC pipeline and follows the following rules when using JoinedFilterLocator:

  • Action Filters
    • High->Low for OnActionExecuting
    • Low->High for OnActionExecuted
  • Authorization Filters - High->Low
  • Exception Filters - High->Low
  • Result Filters
    • High->Low for OnResultExecuting
    • High->Low for OnResultExecuted
	public virtual FilterInfo FindFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
	{
		var filters = new FilterInfo();
		var joinedFilters = JoinedFilters
			.Where(i => i.JoinsTo(controllerContext, actionDescriptor)).ToList();

		if (joinedFilters != null)
		{
			AddFilters(joinedFilters, filters.ActionFilters);
			AddFilters(joinedFilters, filters.ExceptionFilters);
			AddFilters(joinedFilters, filters.AuthorizationFilters);
			AddFilters(joinedFilters, filters.ResultFilters);
		}

		return filters;
	}

	private void AddFilters<T>(IEnumerable<IJoinedFilter> joinedFilters, IList<T> filters)
	{
		var orderedFilters = joinedFilters.OfType<T>()
			.OrderByDescending(f => f is IFilterPriority ? (f as IFilterPriority).GetOrder() : int.MaxValue)
			.ToList();

		orderedFilters.ForEach(filters.Add);
	}

Now, we can set the priority of our mapping filter to 1, ensuring it’s OnActionExecuted is executed first. All other filters in the JoinedFilter project are given an order of Int32.Max by default. This might not be the best way to sort, so watch out for updates in the future. This seems to work well with action filters and exception filters.

	public class OnViewResult_IfModelDoesNotMatchViewModelThenMapWithAutoMapper :
		OnViewResult_ExecuteActionFilter<ReflectedAutoMapFilter>
	{
		public override int GetOrder()
		{
			return 1;
		}
	}

Prioritizing exception aspects

Exception filters are another good example where priority becomes important. I have created a sample set of exceptions NestedException inheriting from ExceptionBase. I added two methods to the PriorityController sample:

	public class PriorityController : Controller
	{
		...
		public void NestedException()
		{
			throw new NestedException();
		}

		public void ExceptionBase()
		{
			throw new ExceptionBase();
		}
	}

	public class ExceptionBase : Exception
	{
	}

	public class NestedException : ExceptionBase
	{
	}

What if we want one handler to catch specific exceptions of type NestedException and another to handle the more general exception ExceptionBase? Without priority, we are at the mercy of JoinedFilterLocator and whatever mechanism it relies on to acquire JoinedFilters. The sample below implements two handlers to send users to an error view with a message reporting what exception handler dealt with the error:

	public class NestedExceptionHandler : ExceptionHandler<NestedException>
	{
		public override int GetOrder()
		{
			return 1;
		}
	}

	public class ExceptionBaseHandler : ExceptionHandler<ExceptionBase>
	{
		public override int GetOrder()
		{
			return 0;
		}
	}

	public abstract class ExceptionHandler<T> : IExceptionFilter, IJoinedFilter, IFilterPriority
		where T : Exception
	{
		public void OnException(ExceptionContext filterContext)
		{
			var exception = filterContext.Exception as T;
			if (exception == null || filterContext.ExceptionHandled)
			{
				return;
			}

			filterContext.Result = ErrorView();
			filterContext.ExceptionHandled = true;
		}

		private ViewResult ErrorView()
		{
			var result = new ViewResult
			             {
			             	ViewName = "Error"
			             };
			result.ViewData["Message"] = string.Format("{0} exception handler caught this", typeof (T));
			return result;
		}

		public bool JoinsTo(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
		{
			return true;
		}

		public abstract int GetOrder();
	}

Run the sample yourself and try changing the priority to see how the ExceptionBaseHandler and NestedExceptionHandler work.  Notice how this allows control over exception handlers from most specific to most general, just like with try/catch statements.

As usual, all code is available at my Google code repository for IJoinedFilter, this set of changes was wrapped up with commit 56.  Please leave me some feedback about any enhancements, specifically I am looking for better ideas to deal with priority of filters, the integer thing kind of bothers me :).

Update: I am thinking that another nice way to do priority would be to use an explicit configuration mechanism, a lot like FubuMvc has so the filters being used are explicit and the order listed is the order applied.  Thoughts?

kick it on DotNetKicks.com Shout it

-Wes



Feedback

No comments posted yet.