AutoMapper is a great tool that I use on every project I’m involved in. Primarily I use it to map my Model to my ViewModel when doing MVC development. If you haven’t used it before, I highly recommend checking it out.
If you’ve used AutoMapper before on a larger sized project, then you’ve probably seen how long and unruly your mapping definitions can become. This file can grow and grow and just be a pain to deal with. Luckily, AutoMapper provides a way to separate the definitions up into more logical units. When doing MVC development, it might be natural to group all the Model/ViewModel mappings for a single controller, but you can break it up however makes logical sense to you. You would do this by creating a custom Profile for each where you define your mappings for those entities.
To make this a little easier, let’s define a base class for all of our custom Profiles to inherit from. This also allows us to define any formatters that we want to use across all of our mappings. In this example, we have a date formatter that will take a DateTime or DateTime? and convert it to the standard string display we want for this site.
public abstract class BaseProfile : Profile { private readonly string _profileName; protected BaseProfile(string profileName) { _profileName = profileName; } public override string ProfileName { get { return _profileName; } } protected override void Configure() { ForSourceType<DateTime>().AddFormatter<StandardDateTimeFormatter>(); ForSourceType<DateTime?>().AddFormatter<StandardDateTimeFormatter>(); CreateMaps(); } protected abstract void CreateMaps(); }
Now when we want to create a new profile for AutoMapper we can create it like so:
public class EventProfile : BaseProfile { public EventProfile() : base("EventProfile") { } protected override void CreateMaps() { CreateMap<Event, IndexViewModel.Event>(); CreateMap<Event, CreateViewModel>(); CreateMap<Event, DetailsViewModel>(); CreateMap<Event, EditViewModel>(); CreateMap<Event, DeleteViewModel>(); } }
This is a trivial example where the properties match perfectly from our Event Model to our ViewModels, but since that isn’t the point of this article I’m not going to worry about it.
Now you just have to tell AutoMapper to load these profiles. If you have some bootstrapper code you would add it there, if not then Global.asax is a common place (I didn’t say the best place, but it is common and it works). Here is how you would do that. You need to call AddProfile<>() for each custom profile we create in order to register it.
Mapper.Initialize(x => { x.ConstructServicesUsing(ObjectFactory.GetInstance); x.AddProfile<EventProfile>(); x.AddProfile<NewsProfile>(); x.AddProfile<UserProfile>(); x.AddProfile<OrderProfile>(); // etc., etc. }); Mapper.AssertConfigurationIsValid();
This definitely helped break up a very large AutoMapper mappings file into smaller more easily managed pieces so I like where it is going. But after using it for a bit I noticed that I kept forgetting to add my new Profile into this initialization part and that wasted time and was just kind of annoying. So I decided to change it so I could be lazy and not have to worry about it.
Using reflection we can grab all of the classes that inherit from BaseProfile and then call AddProfile with each of them. There may or may not be a way to grab all the types and call AddProfile<T>() with it like is done in the code above but I couldn’t figure out how to and quickly found another way so stopped trying. There is also an AddProflile(Profile profile) method that accepts an instantiated class of type Profile (which BaseProfile inherits from). So the only thing left to do is code it up:
Mapper.Initialize(x => { x.ConstructServicesUsing(ObjectFactory.GetInstance); // get all the AutoMapper Profile classes using reflection var profileTypes = typeof(BaseProfile).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(BaseProfile))); foreach (var type in profileTypes) { x.AddProfile((BaseProfile)Activator.CreateInstance(type)); } }); Mapper.AssertConfigurationIsValid();
If you haven’t seen the Activator.CreateInstance() call before, it creates a new instance of that object type using the default constructor, and that works perfectly for you custom profiles since they don’t need any parameters. There are also versions of Activator.CreateInstance where you can pass in your parameters in case you run into that scenario.
The result of this is a better organized structure for AutoMapper, especially on larger projects, while making as little work for us each time we need to add a new Profile.