geek yapping

Twitter












AutoMapFilter meet IJoinedFilter

A few weeks ago I blogged about the concept of a “joined” filter to apply filters to actions without attributes.  I promised to demonstrate some practical uses, beyond the hello world example in that post, and now it is time to deliver :)

Jimmy Bogard talks about how he does MVC view models using an AutoMapFilter to transform the model from the controller into the view model for the view.  This is an excellent use of AOP to separate the concern of mapping from the concerns in the controller.  When I saw this example, I thought to myself, is it possible to infer the types?  typeof() always makes code ugly!  If this were possible, we could join this filter instead of manually applying it.  Here are the steps we would need to accomplish this:

  1. Determine the source type of the model returned from the controller.
  2. Determine the destination type of the view.
  3. Find a reasonable “join point” to apply this filter using IJoinedFilter.

Note: The sample code is located at the original Google Code repository for the IJoinedFilter project.

ReflectedAutoMapFilter

The ideas listed above rely on reflection to discover the source and destination types, so I named the new filter ReflectedAutoMapFilter.  It takes a dependency to IReflectedAutoMapper which will do the heavy work.  The filter executes after the action, just like Jimmy’s AutoMapFilter:

	public class ReflectedAutoMapFilter : IActionFilter
	{
		public IReflectedAutoMapper ReflectedAutoMapper { get; set; }

		public ReflectedAutoMapFilter(IReflectedAutoMapper reflectedAutoMapper)
		{
			ReflectedAutoMapper = reflectedAutoMapper;
		}

		public void OnActionExecuting(ActionExecutingContext filterContext)
		{
		}

		public void OnActionExecuted(ActionExecutedContext filterContext)
		{
			ReflectedAutoMapper.TryMapSourceToDestination(filterContext);
		}
	}

ReflectedAutoMapper

Source Type

Getting the source type was straight forward and can also be applied to Jimmy’s original filter.

	var sourceModel = filterContext.Controller.ViewData.Model;
	return sourceModel.GetType();

Destination Type and IViewModelTypeReflector

Here is where the fun begins.  This was complex enough it warranted a separate class, IViewModelTypeReflector.  I will get into the details of that later.  It has a method GetDestinationModelType that takes the ActionExecutedContext and does the voodoo necessary to discover the view model type.

	var destinationType = TypeReflector.GetDestinationModelType(filterContext);

IMapper

Once we  have the source and destination types, we can map from the source to the destination.  To do this, ReflectedAutoMapper has a dependency to IMapper:

	
	public interface IMapper
	{
		bool HasMap(object source, Type sourceType, Type destinationType);
		object Map(object source, Type sourceType, Type destinationType);
	}

 

HasMap tells us if the given mapper can perform the mapping.  Map performs the mapping and returns the result.  The sample code includes two implementations of this for AutoMapper, I will cover them later.

 

Putting it together in ReflectedAutoMapper

If the mapping is successful, it will put the mapped model into the view’s model property.  ReflectedAutoMapper relies on HasMap and also checks to make sure the source type doesn’t already match the destination type before attempting the mapping. 
	public class ReflectedAutoMapper : IReflectedAutoMapper
	{
		public IViewModelTypeReflector TypeReflector { get; set; }

		public IMapper ObjectMapper { get; set; }

		public ReflectedAutoMapper(IMapper objectMapper, IViewModelTypeReflector typeReflector)
		{
			TypeReflector = typeReflector;
			ObjectMapper = objectMapper;
		}

		public void TryMapSourceToDestination(ActionExecutedContext filterContext)
		{
			var sourceModel = filterContext.Controller.ViewData.Model;
			if (sourceModel == null)
			{
				return;
			}

			var destinationType = TypeReflector.GetDestinationModelType(filterContext);
			if (destinationType == null)
			{
				return;
			}

			var mapped = Map(sourceModel, destinationType);
			if (mapped != null)
			{
				filterContext.Controller.ViewData.Model = mapped;
			}
		}

		private object Map(object sourceModel, Type destinationType)
		{
			var sourceType = sourceModel.GetType();
			if (sourceType == destinationType
			    || !ObjectMapper.HasMap(sourceModel, sourceType, destinationType))
			{
				return null;
			}

			var destinationModel = ObjectMapper.Map(sourceModel, sourceType, destinationType);
			return destinationModel;
		}
	}

ReflectedAutoMapFilter in  action

To demonstrate a comparison between this new filter and the original, I created a ReflectedAutoMapAttribute:

	
	public class ReflectedAutoMapAttribute : ActionFilterAttribute
	{
		public ReflectedAutoMapFilter ReflectedAutoMapFilter { get; set; }

		public override void OnActionExecuted(ActionExecutedContext filterContext)
		{
			ReflectedAutoMapFilter.OnActionExecuted(filterContext);
		}
	}

Here are two actions, one with the original AutoMapFilter and one with the new ReflectedAutoMapFilter.

	
	[AutoMap(typeof (Product), typeof (ProductViewModel))]
	public ActionResult ProductAutoMapFilter()
	{
		var product = new Product
		{
			Id = 1,
			Name = "Product auto map filter"
		};
		return View("Product",product);
	}

	[ReflectedAutoMap]
	public ActionResult ProductReflectedAutoMapFilter()
	{
		var product = new Product
		{
			Id = 1,
			Name = "Product reflected auto map filter"
		};
		return View("Product", product);
	}

Both actions link to the same product view which expects a ProductViewModel:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ProductViewModel>" %>

JoinedReflectedAutoMapFilter

The last step is to join the filter instead of attributing it. View results inherit from ViewResultBase in the MVC framework. By joining to these result types, we can apply any necessary mappings! The join point is any action where the output is, or could be, a ViewResultBase type. Note: IJoinedFilter.JoinsTo was discussed in the post on IJoinedFilter

	
	public class JoinedReflectedAutoMapFilter : JoinedActionFilterAdapterBase<ReflectedAutoMapFilter>
	{
		private const bool JoinToNonReflectedActions = true;

		public override bool JoinsTo(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
		{
			if (!(actionDescriptor is ReflectedActionDescriptor))
			{
				return JoinToNonReflectedActions;
			}

			var returnType = (actionDescriptor as ReflectedActionDescriptor).MethodInfo.ReturnType;

			return JoinToViewResultBaseTypes(returnType)
			       || JoinToTypesThatCanHoldViewResultBase(returnType);
		}

		private static bool JoinToTypesThatCanHoldViewResultBase(Type returnType)
		{
			return returnType.IsAssignableFrom(typeof (ViewResultBase));
		}

		private static bool JoinToViewResultBaseTypes(Type returnType)
		{
			return typeof (ViewResultBase).IsAssignableFrom(returnType);
		}
	}

In Action

Now that we are using IJoinedFilter, the action no longer needs the ReflectedAutoMap attribute!

	
	public ActionResult Product()
	{
		var product = new Product
		              {
		              	Id = 1,
		              	Name = "Product from joined, reflected auto map filter"
		              };
		return View(product);
	}

Demystifying ViewModelTypeReflector : IViewModelTypeReflector

Getting the IView

Getting the destination view model type was easier said than done. It starts with getting the view from the ViewResultBase.

	
	private static IView GetView(ActionExecutedContext filterContext)
	{
		if (!(filterContext.Result is ViewResultBase))
		{
			return null;
		}
		var viewResultBase = filterContext.Result as ViewResultBase;
		var viewName = GetViewName(filterContext, viewResultBase);

		ViewEngineResult result = null;
		if (viewResultBase is ViewResult)
		{
			result = viewResultBase.ViewEngineCollection.FindView(filterContext.Controller.ControllerContext, viewName, (viewResultBase as ViewResult).MasterName);
		}
		else if (filterContext.Result is PartialViewResult)
		{
			result = viewResultBase.ViewEngineCollection.FindPartialView(filterContext.Controller.ControllerContext, viewName);
		}

		if (result == null)
		{
			return null;
		}

		return result.View;
	}

	private static string GetViewName(ActionExecutedContext filterContext, ViewResultBase viewResultBase)
	{
		var viewName = viewResultBase.ViewName;
		if (string.IsNullOrEmpty(viewName)) viewName = filterContext.RouteData.GetRequiredString("action");
		return viewName;
	}

Process

  1. Get the view name - It was interesting to discover that ViewResultBase sets the ViewName, if one isn’t specified, when ExecuteResult is called.  Before that, the ViewName is empty, if not explicitly set in the controller. I duplicated the logic in ExecuteResult with GetViewName.
  2. Find the view - different methods are implemented for finding a view if it is partial or not.
  3. Get the view from the ViewEngineResult

Getting the model type - WebFormView

With WebFormViews, the model is not on the view. Instead, it is on the ViewPage which is an extension of the System.Web.UI.Page class. Getting the ViewPage requires a call to the BuildManager for ASP.Net.  Once we have the ViewPage we can use the GetModelType method to extract the type of the model using reflection.

	
	if (view is WebFormView)
	{
		return GetWebFormViewModelType(view as WebFormView);
	}

	private static Type GetWebFormViewModelType(WebFormView view)
	{
		var viewPage = BuildManager.CreateInstanceFromVirtualPath(view.ViewPath, typeof (object));
		if (viewPage == null)
		{
			return null;
		}

		var modelType = GetModelType(viewPage);

		TryDispose(viewPage);

		return modelType;
	}

	private static Type GetModelType(object view)
	{
		const string modelPropertyName = "Model";
		var modelMembers = view.GetType().GetMember(modelPropertyName);
		if (modelMembers == null)
		{
			return null;
		}
		var model = modelMembers.OfType<PropertyInfo>().FirstOrDefault(p => p.PropertyType != typeof (object));
		if (model == null)
		{
			return null;
		}
		return model.PropertyType;
	}

Spark views & wrapping up ViewModelTypeReflector.GetDestinationModelType

If the view is from Spark, the Model is a property on it. To keep a dependency from Spark out of the JoinedFilter project, I simply reused the GetModelType method. I have a feeling other view engines might use the same idea with the model property but have not had time to check.  If not, this class can be extended as necessary to provide support for other view engines.

	
	public Type GetDestinationModelType(ActionExecutedContext filterContext)
	{
		var view = GetView(filterContext);
		if (view == null)
		{
			return null;
		}

		if (view is WebFormView)
		{
			return GetWebFormViewModelType(view as WebFormView);
		}

		var modelType = GetModelType(view);

		TryDispose(view);

		return modelType;
	}

IMapper & AutoMapper

Mapper.HasMap

If it wasn't enough to have to dig through the heap of madness involving getting to the view model type, I added in a bit more fun using reflection with AutoMapper to add a HasMap method to AutoMapper. I sent a sample patch to Jimmy for inclusion in AutoMapper.  In the interim, the reflected version should work fine. Please refer to the implementation of AutoMapperObjectMapper in the sample code for more details.   HasMap avoids exceptions when mapping between types without a mapping registered, as is the default with Mapper.Map.  This way the filter can silently be ignored and regular exceptions about model type mismatches will show if you have not made a call to CreateMap.

 

AutoMapperObjectMapper & CreateMap or DynamicAutoMapperObjectMapper

In my sample I glossed over showing the CreateMaps needed to get auto mapper to work. I added these in the static constructor of the controller, other people put them in Application_Start, probably a better spot for them :)

	
	static AutoMappedController()
	{
		Mapper.CreateMap<Product, ProductViewModel>();
		Mapper.CreateMap<Product, OtherProductViewModel>();
	}

Alternatively, we could use the DynamicMap feature of AutoMapper to always perform the mapping:

	
	public class DynamicAutoMapperObjectMapper : IMapper
	{
		public bool HasMap(object source, Type sourceType, Type destinationType)
		{
			return true;
		}

		public object Map(object source, Type sourceType, Type destinationType)
		{
			return Mapper.DynamicMap(source, sourceType, destinationType);
		}
	}

Either way, using the container of your choice, you can inject either strategy.

Registrations

This entire example relies upon a container to inject quite a few dependencies. I refactored a bit of the original IJoinedFilter registrations into JoinedFilterRegistry to separate the concerns a bit.  All of my registrations are implemented with Windsor.

 

JoinedFilterRegistry

These are the components for IJoinedFilter.
	
	public class JoinedFilterRegistry
	{
		public static void Register(IWindsorContainer container)
		{
			container.Register(
				Component.For<IFilterInjector>().ImplementedBy<WindsorFilterInjector>().LifeStyle.Transient);
			container.Register(
				Component.For<IMasterFilterLocator>().ImplementedBy<MasterFilterLocator>().LifeStyle.Transient);
			container.Register(
				Component.For<IFilterLocator>().ImplementedBy<JoinedFilterLocator>().LifeStyle.Transient);
			container.Register(
				Component.For<IActionInvoker>().ImplementedBy<LocatorActionInvoker>().LifeStyle.Transient);
		}
	}

ReflectedAutoMapRegistry

These are the components for the ReflectedAutoMapFilter.  This is where you can swap in DynamicAutoMapperObjectMapper :)

	
	public static void Register(IWindsorContainer container)
	{
		container.Register(
			Component.For<IReflectedAutoMapper>().ImplementedBy<ReflectedAutoMapper>().LifeStyle.Transient);
		container.Register(
			Component.For<IViewModelTypeReflector>().ImplementedBy<ViewModelTypeReflector>().LifeStyle.Transient);
		container.Register(
			Component.For<IMapper>().ImplementedBy<AutoMapperObjectMapper>().LifeStyle.Transient);
	}

JoinedRegistry

This is the registry I use in my sample application for the components discussed in this post.  It makes a call to JoinedFilterRegistry.Register and then adds its own pieces for the ReflectedAutoMapFilter examples.

	
	private static void RegisterReflectedAutoMapFilter(IWindsorContainer container)
	{
		ReflectedAutoMapRegistry.Register(container);

		container.Register(
			Component.For<IJoinedFilter>().ImplementedBy<JoinedReflectedAutoMapFilter>().LifeStyle.Transient);

		container.Register(
			Component.For<ReflectedAutoMapFilter>().ImplementedBy<ReflectedAutoMapFilter>().LifeStyle.Transient);
	}
Shout it kick it on DotNetKicks.com

Feedback

# re: AutoMapFilter meet IJoinedFilter

Regarding the source type, is it possible to run into problems with ORMs that use proxies when getting the model's type at runtime (.GetType())? Jimmy points this out in his post as the reason for defining the types at compile-time.

If that is an issue, I could see a couple solutions. One would be using dynamic mappings as you demonstrated. Another would be to use reflection to get the source type as well as the destination type. 1/4/2010 10:29 AM | Tyson