Using AutoMapper: Creating Mappings

Posts in this Series

Intro

In the previous post, we looked at how to centralize our AutoMapper mapping definitions in a config class that we can run at application start. In this post, we'll look at how to create these mappings and how to handle some more complex mapping scenarios.

The CreateMap Method

All mapping definitions for AutoMapper are created using the CreateMap method. This is actually and generic method that takes two class types in the form of:

AutoMapper.Mapper.CreateMap<SourceClass, DestinationClass>();

It's important to note that this is a one-way mapping. For example, if I defined a mapping such as:

AutoMapper.Mapper.CreateMap<Book, BookViewModel>();

I could map a Book instance to a BookViewModel instance with:

var bookViewModel = AutoMapper.Mapper.Map<BookViewModel>(book);

But, if I tried to map a BookViewModel instance back to a Book instance:

var book = AutoMapper.Mapper.Map<Book>(bookViewModel);

An exception would be raised, as AutoMapper doesn't know how to perform this mapping. We could of course just call CreateMap again with the class types reversed to define this mapping:

AutoMapper.Mapper.CreateMap<Book, BookViewModel>();
AutoMapper.Mapper.CreateMap<BookViewModel, Book>();

But, there's actually an even easier way. If you don't need to define any custom mapping logic for the reverse mapping, then you can just call ReverseMap() off of CreateMap():

AutoMapper.Mapper.CreateMap<Book, BookViewModel>().ReverseMap();

With just that, you can then map back and for between Book and BookViewModel.

It's Automagic!

Since AutoMapper is designed to work generically with any set of classes, it relies on conventions to determine how to map from one object type to another. One of the most basic conventions is that your type members should have the same names. Take for example the following classes:

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

public class NiceBookViewModel
{
    public string Title { get; set; }
}

public class BadBookViewModel
{
    public string BookTitle { get; set; }
}

If we map from an instance of Book to an instance of NiceBookViewModel, the Title property will be set as we expect. However, if you map from Book to BadBookViewModel, BookTitle will remain null. AutoMapper has no way of knowing that BookTitle should get the value of Title, because the property names are not the same. However, often you won't have total control of property names for one or both classes, so AutoMapper would quickly become useless if it couldn't handle these types of situations: you simply have to tell it that you want the value of one property "projected" onto the other property:

AutoMapper.Mapper.CreateMap<Book, BadBookViewModel>()
    .ForMember(dest => dest.BookTitle,
               opts => opts.MapFrom(src => src.Title));

Now, when you map Book to BadBookViewModel, AutoMapper knows that BookTitle should be set with the value of Title.

Another convention AutoMapper employs relates to embedded objects. In the first post in this series, we used the following classes:

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; }
}

Even though Book and BookViewModel have properties named Author, the types of those properties do not match, so AutoMapper cannot map Book.Author to BookViewModel.Author because the former is a string while the later is an instance of Author. For embedded objects, AutoMapper's convention is to look for a property on the destination type named in the form of the object property's name plus the property on that object's name, in camel-case. In other words, if we altered our BookViewModel to:

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

AutoMapper needs no special configuration and will readily map Book.Author.Name to BookViewModel.AuthorName. That's nice, but again, we can't always control this, so you can always just spell it out for AutoMapper instead:

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

Projections

The nice thing about MapFrom is that you can specify more complex logic than simple 1-to-1 mappings. What if we change our Author to the following?

public class Author
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

We now have two properties that we'll still need to map to just one property on our view model. Turns out, this is not complicated at all:

AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
    .ForMember(dest => dest.Author,
               opts => opts.MapFrom(
                   src => string.Format("{0} {1}",
                       src.Author.FirstName,
                       src.Author.LastName)));

I'm using pretty liberal spacing to keep the code sample from wrapping inelegantly here, so the key part to pay attention to is that instead of mapping from src.Author.Name as we were previously, we're now mapping from a string composed of the FirstName and LastName properties together on Author. Without going too far off the rails here, the parameter to MapFrom is a lambda expression. Lambdas are a fairly advanced topic that's not appropriate to go into here, but the extremely simplified explanation is that they are essentially a function defined and called all at once. The part to the left of the => is the function's parameter(s). In this case, that's an object we're calling src which AutoMapper will fill in with the the source class instance. The part to the right of the => is the return value of the function, which in this case is our string composition. In other words, you can do pretty much whatever logic you want in MapFrom as long as it can be completed in a single expression and returns the type needed by the receiving property (a string, in this case).

More Complex Projections

What if we need to map multiple properties on our source class into a embedded object on our destination class? For this example, we'll use the following classes:

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Address Address { get; set; }
}

public class PersonDTO
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

Here, we have a DTO where all the properties are inline, but our model uses a composition Address class to contain the address-specific properties. Again, MapFrom to the rescue:

AutoMapper.Mapper.CreateMap<PersonDTO, Person>()
    .ForMember(dest => dest.Address,
               opts => opts.MapFrom(
                   src => new Address
                   {
                       Street = src.Street,
                       City = src.City,
                       State = src.State,
                       ZipCode = src.ZipCode
                   }));

So, again, the right side of the lambda expression can be any expression that returns a valid type for the destination property. Here, the destination property is an Address instance, so we create a new instance of Address and initialize it with the properties on the source class, PersonDTO.

Nested Mappings

Now, what if in our previous example, instead of all the address-specific properties being inline on our PersonDTO class, they were contained in an embedded object like our Person class? If the types of both properties were Address and the names of both properties were Address, AutoMapper's convention-based mapping would kick in and no specific configuration would be needed at all, but what if we had an AddressDTO class as well?

public class AddressDTO
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

public class PersonDTO
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public AddressDTO Address { get; set; }
}    

If we try to map PersonDTO to Person, we'll get an exception. We created a mapping from PersonDTO to Person, but we haven't told AutoMapper how to map between AddressDTO and Address.

When AutoMapper comes across the same property name on the source and destination classes with different types, it will helpfully automatically attempt to map from one type to the other. This means that you can map entire object hierarchies in a single Map call, which is extremely powerful. The only caveat, is that each type in the hierarchy must have a mapping defined. So, to fix our exception, we just have to add:

AutoMapper.Mapper.CreateMap<PersonDTO, Person>();
AutoMapper.Mapper.CreateMap<AddressDTO, Address>();

Wrap-up

As you should see by now, AutoMapper is both simple and powerful. Thanks to its convention-based mapping, many times, even in complex object graphs you don't have to create any custom configuration at all other than the a simple call to CreateMap() for each distinct type-mapping in play. And, in more complex scenarios, where things don't quite line up with the conventions, MapFrom makes quick work of defining custom mapping logic.

In the next post, we'll look at other mapping scenarios and get a little more acquainted with using the Map call to actually do the mapping.

comments powered by Disqus