C# Corner

Simplify Your Projections with AutoMapper

Tired of mapping your classes from one format to another? A convention-based, open source library can help alleviate some of those coding headaches.

One of the great things about the .NET community is the wealth of open source projects that are available to you. In previous columns, I've written about my use of Castle Windsor (December 2011) and Rhino.Mocks (August 2011). In this article, I'm going to look at a powerful and extensible open source library found on GitHub called AutoMapper. It's an object-to-object mapping tool that helps you eliminate some of the tedious code needed to map your classes from one format to another.

Writing Your Own Projections
When I talk about "projections," what do I mean? In the Model-View-ViewModel (MVVM) pattern, a view model is bound to a Windows Presentation Foundation (WPF) window or user control (the view). The data in the ViewModel comes from model classes that probably come from a domain layer, an Object Relational Mapper (ORM) or some other mechanism.

If I'm being sloppy when I implement my view model, I'll just take my data transfer objects (DTOs) from my ORM and give them to the view model, like so:

public class MainWindowViewModel
{
  public CustomerDTO Customer { get; set; }
		
  public void LoadCustomer()
  {
    // Load customer DTO from ORM (code elided)
    this.Customer = customerDto;
  }
}

This approach is easier because I don't have to think about which data needs to be bound in the view. But it presents a number of issues, the biggest concern being that I'm tightly coupling my application's data access layer to my presentation layer, which makes testing and refactoring more difficult.

In addition, my view has direct access to the DTO. It could inadvertently access properties that could have unwanted side effects. Suppose I have a Customer object from NHibernate, an open source ORM for .NET that has an Orders property, which is lazy loaded. Any access to that property could cause additional SQL queries to be executed.

Finally, it's more difficult to collaborate with a UI designer if I don't have a clearly defined set of properties that are available for binding or display. This may be less of an issue in larger shops, but if you're like me (with little talent for UI design) being able to interface with a graphic designer on my WPF layout is a huge bonus.

So, to combat these issues, I create a focused ViewModel, as shown in Listing 1.

This approach makes it clear what data is available for binding/display, but it adds quite a bit of work. I need to project my DTO data into my ViewModel data one field at a time -- a common problem in ASP.NET MVC applications. AutoMapper can make this job easier.

Introducing AutoMapper
The AutoMapper homepage is AutoMapper.org. You can install it via NuGet or download releases from the homepage. In this article, I'll cover AutoMapper 2.0, which is free for proprietary use under the MIT License. The latest version of the software has few breaking changes from the 1.x version, so much of this article will apply to earlier versions.

The first step in using AutoMapper is to initialize your mappings. This is only done once per AppDomain. For ASP.NET MVC apps, this usually means Application_Start. For Windows or WPF applications, this is usually done before the first form or window is displayed.

Initializing AutoMapper is easy. It has a number of default conventions for mapping that it recognizes; it also allows you to configure your own mappings if you encounter a situation not handled by AutoMapper.

Here's how I refactored the WPF example using AutoMapper. First, I created a class that represents the data from the DTO that I want to use:

public class CustomerInfo
{
  public string Name { get; set; }
  public string Address1 { get; set; }
  public string Address2 { get; set; }
  public string City { get; set; }
  public string State { get; set; }
  public string Zip { get; set; }
}

Next, I tell AutoMapper how to "map" the data from the CustomerDTO into this CustomerInfo class. Inside my App.xaml.cs, I override the OnStartup method so I can initialize AutoMapper before my WPF app displays the main view:

protected override void OnStartup(StartupEventArgs e)
{
  InitializeAutoMapper();
  base.OnStartup(e);
}

private void InitializeAutoMapper()
{
  Mapper.CreateMap<CustomerDTO, CustomerInfo>();

  Mapper.AssertConfigurationIsValid();
}

As you can see, very little setup is needed. The CreateMap call sets up a mapping between a source class and a destination class. AutoMapper has a number of built-in conventions to make setup of your mappings easier. Properties of the same name and type between the source and destination classes are simply copied as is by default. My CustomerInfo class and CustomerDTO class contain properties of the same name and type, so no additional setup is needed.

The last line of InitializeAutoMapper ensures that all of my mappings are set up properly. This will throw an exception if I forget to define a mapping for one of the fields on the destination class. When you use AutoMapper, always include a call to AssertConfigurationIsValid -- it can save you a lot of headaches. It's akin to catching compile-time errors versus runtime errors.

Finally, I changed my LoadCustomer method to use AutoMapper instead of manual mapping code:

public void LoadCustomer()
{
  // Load customer DTO from ORM (code elided)
  this.CustomerInfo = Mapper.Map<CustomerDTO, CustomerInfo>(customerDto);
}

Much cleaner! I don't even need to create an instance of CustomerInfo. I just tell AutoMapper to map the existing CustomerDTO to a CustomerInfo class and it automatically instantiates a new version of the CustomerInfo class, performs the mappings and returns the mapped destination object.

Additional Conventions
In addition to property name matching, AutoMapper allows destination properties to be obtained from a matching method. Imagine I have a complex EngineConfiguration class:

public class EngineConfiguration
{
  public string ModelNumber { get; set; }
  public int ModelYear { get; set; }

  public int CylinderCount()
  {
    return 8;
  }

  // Dozens of other properties removed for brevity
}

I want to display some simple engine information in my WPF application so I define a class for the data I want to display:

public class EngineInfo
{
  public string ModelNumber { get; set; }
  public int ModelYear { get; set; }
  public int CylinderCount { get; set; }
}

Initializing the mapping for these two classes inside InitializeAutoMapper is easy:

Mapper.CreateMap<EngineConfiguration, EngineInfo>();

While the EngineConfiguration class doesn't contain a property called CylinderCount, it contains a matching method name with the same return type. Therefore, AutoMapper will use this method as the source for EngineInfo.CylinderCount.

If AutoMapper can't find either a matching property name or method name for a particular destination property, it will look for a method on the source class called "GetXXX" where "XXX" is the name of the destination property. In the example code, the EngineInfo.CylinderCount property would look for "GetCylinderCount" if no GetCylinder property or method exists.

Finally (if this wasn't enough!), if AutoMapper still can't find the source of the data for a destination property, it will split the property name into parts based on PascalCase notation and look for properties in the format [object].[propertyname]. For example, I have a CarSetup class that includes a reference to an EngineConfiguration:

public class CarSetup
{
  public string VIN { get; set; }
  public string Maker { get; set; }
  public Guid ExportCode { get; set; }
  public EngineConfiguration Configuration { get; set; }
  public int DefectRate { get; set; }
}

I need to display a car's VIN and the ModelNumber from the EngineConfiguration. I simply define my data class as:

public class CarInfo
{
  public string VIN { get; set; }
  public string ConfigurationModelNumber { get; set; }
}

And the mapping is:

Mapper.CreateMap<CarSetup, CarInfo>();

When AutoMapper goes to populate the ConfigurationModelNumber, it won't find a property called "ConfigurationModelNumber," a method called "ConfigurationModelNumber" or a method called "GetConfigurationModelNumber." Therefore, it will break the destination property name into parts "Configuration.ModelNumber" and look in the CarInfo's Configuration property for the ModelNumber property.

As you can see, the default conventions built in to AutoMapper make set up almost effortless. However, at times you may need more fine-grained control of your mappings. Luckily, AutoMapper supports this level of control.

Customized Mappings
The first thing most users run into with AutoMapper is a situation where they have one or two properties on the destination class that they want to set themselves -- the data can't come from the source class.

Suppose I have a simple Employee database and expose some employee information:

class Employee
{
  public string Name { get; set; }
  public DateTime DateOfBirth { get; set; }
  public DateTime HireDate { get; set; }
  public Employee Supervisor { get; set; }
}

I've got an application that needs to display an employee's name along with how many sick days that person has used:

class EmployeeStats
{
  public string Name { get; set; }
  public int SickDaysUsed { get; set; }
}

The number of sick days used comes from a different system -- I won't find it in the Employee class.

When setting up the mapping, I can start with the basic CreateMap:

Mapper.CreateMap<Employee, EmployeeStats>();

But if I ask AutoMapper to validate my configuration with Mapper.AssertConfigurationIsValid it throws an exception because it doesn't know (and I haven't told it) how to map SickDaysUsed. I'll get this value from another system, so I'll just tell AutoMapper to ignore that field when performing a Map:

Mapper.CreateMap<Employee, EmployeeStats>()
  .ForMember(d => d.SickDaysUsed, o => o.Ignore());

The ForMember method takes two lambdas: The first one identifies the destination field I'm setting up a mapping for and the second one identifies various options for the mapping. In this case, I use the AutoMapper Ignore option. My configuration is now valid. Using this mapping is as simple as:

var employee = GetEmployee();
var stats = Mapper.Map<Employee, EmployeeStats>(employee);
stats.SickDaysUsed = GetSickDays(employee);

In other situations, you might want to define your own logic for mapping. Suppose I needed a simple form that displays an employee's name and years of service. I don't store how long the employee has been working -- only their hire date. Of course, it's trivial to calculate such a date -- but it's not something built in to AutoMapper. For this, I'll have to write some custom code for AutoMapper to use.

I'm using the same Employee class defined for the last example. Now I need a class to store the view data:

class EmployeeService
{
  public string Name { get; set; }
  public int Years { get; set; }		
}

Next, I define my mapping along with a special setting for the "Years" property:

Mapper.CreateMap<Employee, EmployeeService>()
  .ForMember(d => d.Years,
  o => o.MapFrom(s => (DateTime.Now - s.HireDate).TotalDays/365));

This time, instead of using the Ignore option, I used the MapFrom option. This option takes a lambda that references the source object that's being mapped. I do a quick calculation of the employee's years of service based on the current date and hire date. This data is mapped to EmployeeService.Years. The MapFrom option provides an easy way to do simple calculations to determine the destination property value.

Custom Value Resolvers
Sometimes, the logic used in the MapFrom option gets a little complex -- even to the point that you'll want to unit test it. In that case, AutoMapper lets you create a standalone class to convert a value from one type to another.

Let's say that for whatever reason (company merger, accounting laws), calculating the years of service got a bit more complex:

"If an employee is 50 years old or older, years of service is determined based on hire date. If an employee is younger than 50 years old, years of service is 3 less than the number of years since the hire date."

Normally, something like this would probably be domain logic placed somewhere in the domain model. But for the sake of this example, I'm going to create a custom value resolver:

class YearsOfServiceResolver : ValueResolver<Employee, int>
{
  protected override int ResolveCore(Employee source)
  {
    var age = DateTime.Now - source.DateOfBirth;
    if (age.TotalDays / 365 >= 50)
  {
    return
    (int) ((DateTime.Now - source.HireDate).TotalDays/365);
  }

  return (int) ((DateTime.Now - source.HireDate).TotalDays/365) - 3;
  }
}

The ValueResolver<TSource, TDest> type comes from AutoMapper. Simply inherit from that class and override the ResolveCore method to implement the logic. This customized logic is now in a class by itself and can be unit tested with the rest of my code.

Now I need to tell AutoMapper to use this custom value resolver when mapping the EmployeeService.Years property:

Mapper.CreateMap<Employee, EmployeeService>()
  .ForMember(d => d.Years,
  o => o.ResolveUsing<YearsOfServiceResolver>());

In addition to specifying the custom resolver via ResolveĀ¬Using<Type> syntax, I could use the Microsoft .NET Framework 2.0 style of ResolveUsing(typeof(YearsOfServiceResolver)) or even provide a specific instance of the custom value resolver.

Custom Type Converters
Sometimes, the only thing you can do when converting one type to another is to take complete control of the conversion. This is where custom type converters come in.

I once had a legacy system that stored the date in the Unix timestamp format. If you're not familiar with it, the Unix timestamp is the number of seconds that have passed since the Unix epoch of Jan. 1, 1970. So the value of 86,400 would represent Jan. 2, 1970 (1 second * 60 seconds per minute * 60 minutes per hour * 24 hours == 1 day or 86,400 seconds). I didn't want my ViewModels (or my controllers in ASP.NET MVC) to have to worry about doing the conversion. The solution was to create a custom type converter that would convert from the Unix time to a .NET DateTime.

AutoMapper defines a generic interface for custom type converting. All I had to do was implement the logic to convert a double (the Unix date from the legacy system) to a .NET DateTime:

class UnixDateConverter : ITypeConverter<double, DateTime>
{
  public DateTime Convert(ResolutionContext context)
  {
    var source = (double) context.SourceValue;
    var unixEpoch = new DateTime(1970, 1, 1);
    var localDate = unixEpoch.AddSeconds(source).ToLocalTime();
    return localDate;
  }
}

I tell AutoMapper to use this converter whenever it needs to convert a double to a DateTime by using the CreateMap call, but I add an option:

Mapper.CreateMap<double, DateTime>().ConvertUsing<UnixDateConverter>();

This approach makes the converter global to AutoMapper -- there's no need to define this using ForMember calls on individual properties. I define it once at the beginning of my mappings and all other classes that have properties that need to be converted from double to DateTime will use this converter.

List and Array Support
Rarely do developers work on a single item. All of the examples so far have dealt with a single entity (an Employee, a Customer). In the real world, you're dealing with a collection of employees or customers.

AutoMapper doesn't require special support for converting a list or an array of items. As long as I define the element type mapping, AutoMapper can handle enumerating the list, performing the mapping and returning a new, mapped list.

All of the mappings I've showed so far don't need any changes (or additional mappings defined) to support a collection of employees:

var partTimers = GetParttimeEmployees();

var mapped = Mapper.Map<IEnumerable<Employee>,
  IEnumerable<EmployeeStats>>(partTimers);

The full range of supported collection types is documented on the AutoMapper Web site.

Pre and Post Mapping Actions
AutoMapper will also let you define code to run before and after the mapping process. The pre-condition is useful if you have a property on your source object that you want to use in the mapping, but it has to be loaded or initialized before it can be used (perhaps loaded from NHibernate). For this type of situation, use the BeforeMap method:

Mapper.CreateMap<SelectedJob, Job>()
  .BeforeMap((s, d) => ...);

The lambda gets access to both the mapped source object (the first parameter) as well as the destination object (the second parameter).

Likewise, you can define an AfterMap lambda that will be called after AutoMapper has done its job:

Mapper.CreateMap<SelectedJob, Job>()
  .AfterMap((s, d) => ...);

The sample code download for this article demonstrates the BeforeMap and the AfterMap settings (access the sample code from VisualStudioMagazine.com/Steele0212).

<>>Mapping to an Existing Class
In the default mapping that I've shown, AutoMapper creates the destination class and populates it. But that's not my only option. Suppose I have an existing, instantiated destination object that I want to map to:

class UserDTO
{
  public string Name { get; set; }
  public int Age { get; set; }
}

class UserInfo
{
  public string Name { get; set; }
  public int Age { get; set; }
  public Uri HelpUri { get; set; }		
}

I need to map the UserDTO information into the UserInfo class, but the UserInfo class is initialized with the HelpUri somewhere else in the application. In this situation, I can use the Map overload that accepts the source and the destination object:

var info = GetUserInfo();
var dto = new UserDTO
  {
    Age = 33,
    Name = "Bob Smith",
  };
Mapper.Map(dto, info);

You'll note in the sample code that I still had to define an Ignore for the HelpUri property on the destination object because it's pre-populated in the existing destination and I don't want AutoMapper doing anything with it.

Controlling Destination Class Creation
When AutoMapper creates your destination class for you, it uses the class' default, parameterless constructor. What if your destination class doesn't have a parameterless constructor? Or your destination class needs to come from a factory or an IoC container? AutoMapper has that covered.

When defining a map with CreateMap, you can supply a lambda that will tell AutoMapper how to create the destination object. Here's an example where I need to map a Ninja class to a Fighter class. My destination class, Fighter, comes from an IFighterFactory:

interface IFighterFactory
{
  Fighter GetFighter();
}

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

public class Fighter
{
  public Guid Id { get; set; }
  public string Type { get; set; }
  public string Name { get; set; }
}

When I define my mapping at application startup, I'll tell AutoMapper to use my factory to construct the Fighter class ("factory" refers to an existing IFighterFactory):

Mapper.CreateMap<Ninja, Fighter>()
  .ConstructUsing(s => factory.GetFighter())
  .ForMember(d => d.Id, o => o.Ignore())
  .ForMember(d => d.Type, o => o.MapFrom(s => "NINJA"));

Note that I also had to add two special mappings:

The Id property is pre-populated from my factory as part of its creation and I don't want AutoMapper to change it.

The Type property is hardcoded to "NINJA" during the mapping.

Defining Your Own Profiles
As you use AutoMapper more and more, you'll want to centralize the configuration of your mapping initialization. This makes the re-use of your mapping configuration throughout your application easier to share and maintain. AutoMapper calls these configuration classes "Profiles."

You define your own profile by creating a class that inherits from AutoMapper.Profile. Listing 2, is an example Profile that I could've used in the sample code -- it only includes a few of the CreateMap calls.

Configuring AutoMapper using a Profile is simple:

Mapper.Initialize(c => c.AddProfile<SampleProfile>());
Mapper.AssertConfigurationIsValid();

With your configuration centralized, utilizing AutoMapper across multiple applications that share your domain libraries becomes much, much easier.

AutoMapper is free software that can make mapping code easier to write and maintain while still giving you the flexibility to work in most any environment

comments powered by Disqus

Featured

Subscribe on YouTube