Teaching AutoMapper about our conventions


I often need to send data from my entities to the client in JSON format to enable rich AJAX functionality. It isn’t practical to serialize the entities directly, so they are first flattened to a data transfer object (DTO) before serialization. For example, the Case entity has a reference to a Contact entity, which has a FirstName property. On the Case DTO, I might have a ContactFirstName property to hold that value. We use AutoMapper to handle the chore of populating the properties of the DTO using values from the original entity.

Conventions for mapping our DTOs

AutoMapper requires that we declare up front which which entities will be mapped to which DTOs. It has a lot of intelligent defaults, so once we tell it that a Case can be mapped to a CaseDTO, it can figure out how to populate the ContactFirstName property on the DTO from Case.Contact.FirstName. However, there are some properties on the DTO which aren’t so straightforward, so we have to explicitly configure how they are mapped.

Our DTOs generally follow these rules:

  • For every entity reference property on the source entity, the DTO will have a property of the same name, but of type GUID, which holds the entity identifier. Ex: Case.Contact of type Contact maps to CaseDTO.Contact of type GUID.
  • For every entity reference property on the source entity, the DTO will have a property with the name plus the suffix “ViewURL”, which holds the URL for viewing the referenced entity. Ex: CaseDTO.ContactViewUrl holds the URL for the Case.Contact.
  • For every “list value” (think values that show in a drop-down) property on the source entity, the DTO will have a property with the name plus the suffix “Display”, which holds the localized display value. Ex: Case.Contact.Country will map the value “USA” to CaseDTO.ContactCountry, and the value “United States” to CaseDTO.ContactCountryDisplay.

Implementing our mapping rules with AutoMapper

It was very easy to teach AutoMapper about the first rule. It took just a single line of code in our configuration:

cfg.CreateMap<Entity, Guid>().ConvertUsing(entity => entity.Id);

This tells Automapper that any time it tries to map a property of a type that derives from Entity to a property of type GUID, execute the lambda to get the Id.

The other two rules were not so easy to apply. AutoMapper currently has no built-in support for defining conventions to build mappings based on aspects of the types or their properties (although I hear this is something being considered for the future). Instead, you have to explicitly declare the custom relationship between each type, and each custom property resolution. You end up with code that looks like this:

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<Entity, Guid>().ConvertUsing(entity => entity.Id);
    cfg.CreateMap<Case, CaseDTO>()
        .ForMember(d => d.ContactViewUrl, map => map.ResolveUsing<UrlValueResolver>().FromMember(s => s.Contact))
        .ForMember(d => d.SiteViewUrl, map => map.ResolveUsing<UrlValueResolver>().FromMember(s => s.Site))
        .ForMember(d => d.InstalledPartViewUrl, map => map.ResolveUsing<UrlValueResolver>().FromMember(s => s.InstalledPart))
        // ... and the rest of the ViewUrl properties
        .ForMember(d => d.CaseTypeDisplay, map => map.ResolveUsing<ListValueResolver>().FromMember(s => s.CaseType))
        .ForMember(d => d.StatusDisplay, map => map.ResolveUsing<ListValueResolver>().FromMember(s => s.Status))
        // ... and the rest of the Display properties
        ;
    cfg.CreateMap<Site, SiteDTO>()
        .ForMember(d => d.SupportSiteViewUrl, map => map.ResolveUsing<UrlValueResolver>().FromMember(s => s.SupportSite))
        // ... and the rest of the ViewUrl properties
        .ForMember(d => d.PrimaryAddressCountryDisplay, map => map.ResolveUsing<ListValueResolver>().FromMember(s => s.PrimaryAddress.Country))
        // ... and the rest of the Display properties
        ;
    cfg.CreateMap<Part, PartDTO>()
        // ... all of the ViewUrl properties
        // ... all of the Display properties
        ;
    // ... and the rest of the Entities
});

The sample is abbreviated because showing all of the entities, and all of the ViewUrl and Display property mappings for each of those entities is just too repetitive to be interesting. Aside from the tedium of having to type it up the first time, it makes maintenance a lot more painful. Any time we add an entity or list value property to an entity, we would have to remember to go add the corresponding mapping to the AutoMapper configuration, or risk introducing bugs.

Declaring mapping rule conventions

The benefit of establishing conventions in your code is that different parts of the system can rely on those conventions, so you get a lot of functionality that just works the way you would expect. Since we consistently name our DTOs the same way, and we consistently name properties on the DTO the same way, we should be able to rely on the fact that these will always be populated the same way.

With a couple patches to AutoMapper (now available in the latest source), I was able to build some extension methods that allowed me to express our conventions. The previous (which had to be edited for length) code can now be rewritten as:

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<Entity, Guid>().ConvertUsing(entity => entity.Id);
    cfg.CreateMapsForSourceTypes(x => x.CanBeCastTo<Entity>(),
        type => Type.GetType(typeof (ListItemMethodDTO).Namespace + "." + type.Name + "DTO"),
        (map, source, destination) =>
        {
            map.MapViewUrls(source, destination);
            map.MapDisplayValues(source, destination);
        });
});

Now the code expresses our convention: for every type that derives from Entity, map it to a type (in a given namespace) with the same name with a DTO suffix, and map all the ViewUrl and Display members appropriately.

I’ve posted the full source for our extension methods, but the CreateMapsForSourceTypes method is probably the only one that is generally applicable:

public static void CreateMapsForSourceTypes(this IConfiguration configuration, Func<Type, bool> filter, Func<Type, Type> destinationType, Action<IMappingExpression, Type, Type> mappingConfiguration)
{
    var typesInThisAssembly = typeof (AutoMapperExtensions).Assembly.GetExportedTypes();
    CreateMapsForSourceTypes(configuration, typesInThisAssembly.Where(filter), destinationType, mappingConfiguration);
}

public static void CreateMapsForSourceTypes(this IConfiguration configuration, IEnumerable<Type> typeSource, Func<Type, Type> destinationType, Action<IMappingExpression, Type, Type> mappingConfiguration)
{
    foreach (var type in typeSource)
    {
        var destType = destinationType(type);
        if (destType == null) continue;
        var mappingExpression = configuration.CreateMap(type, destType);
        mappingConfiguration(mappingExpression, type, destType);
    }
}

It simply takes a criteria for identifying your source types, a function for determining the destination type based on the source type, and a set of configuration steps that should be applied to each mapping. Goodbye repetitive code!

Adding variable output behavior to your FubuMVC actions