Using AutoMapper: Getting Started

Posts in this Series

Intro

Eventually, in any application, a developer will need to translate data from one object type to another. Common examples include DTOs (Data Transfer Objects), View Models, or even just some request or response object from a service or Web API call. The general idea is that you have some class instance with property values and you want to fill some other class instance with those property values. There may or may not be overlap in the exact properties, their types, their names, etc., or you may only be dealing with a subset or superset of those properties.

Manual Mapping

Here's some simple classes we'll use for this example:

public class Author
{
    public string Name { get; set; }
}

public class Book
{
    public string Title { get; set; }
    public Author Author { get; set; }
}

public class BookViewModel
{
    public string Title { get; set; }
    public string Author { get; set; }
}

In order to create an instance of BookViewModel from an instance of Book, we would do something like:

BookViewModel model = new BookViewModel
{
    Title = book.Title,
    Author = book.Author.Name
}

That example is pretty straight-forward, but already we've got some tedious code repetition: if I add another property on Book that I want on my BookViewModel, I've got to track down every instantiation and manually add the mapping for that property. Also, we've got logic hidden here that creates a bit of a maintenance headache: BookViewModel.Author is a string, while Book.Author is an instance of Author. In order to map that property, I have to explain how to get a string value from Author, which I solved in this case by using the Author.Name property. However, if I later decide that I want to divide Name into FirstName and LastName properties, I'm going to end up breaking every instantiation of BookViewModel across my entire codebase: that's bad.

The solution to these problems is to create some mapping mechanism with minimal configuration that we can use in a fairly generic way in our code, but thankfully, we don't have to actually do that: third-party libraries exist that we can lean upon, specifically, one called AutoMapper.

I've been using AutoMapper for a long time, and I've found it to be both simple and robust, but what it is not is well documented. It's my goal to demystify its use a bit by looking at some of the most common difficulties in using AutoMapper - starting with getting it going for the first time.

Getting Started with AutoMapper

Now, in truth, AutoMapper is extremely easy to start working with -- almost stupidly easy. You simply add the "AutoMapper" package to your project from the Nuget Package Manager. Once installed, you create a "mapping" somewhere in your project, and then go ahead and map your objects. For the example above, it's really as easy as:

AutoMapper.Mapper.CreateMap<Book, BookViewModel>();
var model = AutoMapper.Mapper.Map<BookViewModel>(book);

You can already see some of the benefits here, I'm sure. First, there's no domain logic here -- like none. It doesn't matter whether I add another property to Book and BookViewModel or another twenty properties; the mapping code remains the same. I don't have to make a bunch of subsequent changes across my codebase: for the most part, everything "just works" and keeps rolling along as usual.

However, we've still got one slight problem. I have to define that actual mapping with CreateMap, but this is just boilerplate code. If I use it like this, I'll have to repeat this mapping definition every place I want to perform this mapping. Also, I technically lied to you earlier: the actual necessary code is slightly more complicated:

AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
    .ForMember(dest => dest.Author,
               opts => opts.MapFrom(src => src.Author.Name));

What this extra code does exactly, I'll cover in a future post, but suffice to say, this is not something we want to repeat over and over in our codebase. This is one area where the AutoMapper documentation is completely lacking. See, these mappings only need to be declared once for your entire project. If at any point in run-time you declare the mapping, it will be available to any further code that runs afterwards. The question then becomes: where can we put these mappings so that we can be assured that they have already run by the time the code that needs them runs?

So, where should I create the mappings?

Admittedly, the answer may be obvious to some of you veteran ASP.NET devs out there, but at least for me, when I started using AutoMapper for the first time, I was also green to the whole .NET world, as well. For others like me, there's a file called "Global.asax" which contains startup logic for your application. Since we want our mappings run just once and before our other code runs, this is a great place to put them. However, before you jet off to start dumping all your mappings into that file, let's take a slight jog over to a folder in your project called "App_Start". If you look in there, you'll see files such as "FilterConfig.cs", "BundleConfig.cs", "RouteConfig.cs". Now, if you look inside Global.asax, you'll see that the classes defined in these files are utilized here. Microsoft has provided a good pattern to follow here. Global.asax is really the beating heart and soul of your application. That means two things: 1) if you break something here, you bring your application crashing down around your ears, and 2) you don't want a lot of logic running in this file, because the more logic, the greater chance you'll break something (see 1).

AutoMapperConfig.cs

So, my suggestion, and what I personally do in all my applications, is add an "AutoMapperConfig.cs" file to the "App_Start" folder. Inside that file, add a class such as:

public static class AutoMapperConfig
{
    public static void RegisterMappings()
    {
        AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
            .ForMember(dest => dest.Author,
                       opts => opts.MapFrom(src => src.Author.Name));
    }
}

Then, in Global.asax, in the Application_Start() method, add a call to AutoMapperConfig.RegisterMappings().

Wrap-up

With that, all our mappings will be created at application start, and we'll be able to freely use AutoMapper to map from one object to another throughout our application. As the need arises to map different object types, just continue to add your additional mappings to "AutoMapperConfig.cs". Then, if you need to modify the mapping definitions, you only need to make one change in one file and the rest of your application chugs along, none the wiser.

In the next post, we'll look at how to create mappings.

comments powered by Disqus