What is AutoMapper?

AutoMapper is a simple little library built to solve a deceptively complex problem – getting rid of code that mapped one object to another.

This can be very useful when you want to map your Model to ViewModel and vice versa, without writing and maintaining property mapping between those objects.

How to Install it

AutoMapper is available through Nuget, so you can simply install it from the package manager console:

Install-Package AutoMapper

Then in order to use it you need to create a map for the types you want to map like for example:

Mapper.CreateMap<Product, ProductViewModel>();

The type on the left is the source type, and the type on the right is the destination type. To perform a mapping, you can use the Map method:

ProductViewModel product = Mapper.Map<ProductViewModel>(product); // single object
IEnumerable<ProductViewModel> product = Mapper.Map<ProductViewModel[]>(products); // collection

This approach looks easy but what if you have hundreds of models and a lot more ViewModels? You should keep a file with all the mappings or you can have some help through interfaces.

You can create three interfaces:

using AutoMapper;

namespace AutoMapperExample
{
    public interface IMapFrom<T> {} // Class that implements it declares FROM which object will be mapped

    public interface IMapTo<T> { } // Class that implements it declares TO which object will be mapped

    // In case complex mapping is required through this option you
    // can create custom mapping rules
    public interface IHaveCustomMappings 
    {
        void CreateMappings(IConfiguration configuration);
    }
}

and through them register all you mapping at application startup.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AutoMapper;
using AutoMapperExample;
using AutoMapperExample.Infrastructure.Mappings;
using WebActivatorEx;

// Execute this method on ApplicationStart
[assembly: PreApplicationStartMethod(typeof(AutoMapperConfig), "Execute")]

namespace AutoMapperExample
{
    public class AutoMapperConfig
    {
        /// <summary>
        /// Initialize Mapping process by finding all types that needs 
        /// to be mapped
        /// </summary>
        public static void Execute()
        {
            var types = Assembly.GetExecutingAssembly().GetExportedTypes();

            Mapper.Initialize(cfg =>
            {
                cfg.AllowNullDestinationValues = false;
            });

            RegisterStandardMappings(types);
            RegisterReverseMappings(types);
            ReverseCustomMappings(types);
        }

        /// <summary>
        /// Load all types that implement interface <see cref="IMapFrom{T}"/>
        /// and create a map between {T} and them
        /// </summary>
        /// <param name="types"></param>
        private static void RegisterStandardMappings(IEnumerable<Type> types)
        {
            var maps = (from t in types
                        from i in t.GetInterfaces()
                        where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)
                              && !t.IsAbstract
                              && !t.IsInterface
                        select new
                        {
                            Source = i.GetGenericArguments()[0],
                            Destination = t
                        }).ToArray();

            foreach (var map in maps)
            {
                Mapper.CreateMap(map.Source, map.Destination);
            }
        }

        /// <summary>
        /// Load all types that implement interface <see cref="IMapFrom{T}"/>
        /// and create a map between them and {T}
        /// </summary>
        /// <param name="types"></param>
        private static void RegisterReverseMappings(IEnumerable<Type> types)
        {
            var maps = (from t in types
                        from i in t.GetInterfaces()
                        where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapTo<>)
                              && !t.IsAbstract
                              && !t.IsInterface
                        select new
                        {
                            Source = t,
                            Destination = i.GetGenericArguments()[0]
                        }).ToArray();

            foreach (var map in maps)
            {
                Mapper.CreateMap(map.Source, map.Destination);
            }
        }

        /// <summary>
        /// Load all types that implement interface <see cref="IHaveCustomMappings"/>
        /// and register their mapping
        /// </summary>
        /// <param name="types"></param>
        private static void ReverseCustomMappings(IEnumerable<Type> types)
        {
            var maps = (from t in types
                        from i in t.GetInterfaces()
                        where typeof(IHaveCustomMappings).IsAssignableFrom(t)
                              && !t.IsAbstract
                              && !t.IsInterface
                        select (IHaveCustomMappings)Activator.CreateInstance(t)).ToArray();

            foreach (var map in maps)
            {
                map.CreateMappings(Mapper.Configuration);
            }
        }
    }
}

Now, we can add the interface that defines the mapping our class needs and the appropriate mapping will be registered at the application startup.

public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public ApplicationUser CreatedBy { get; set; }
    }

    public class ProductViewModel : IMapFrom<Product>
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string CreatedByUserName { get; set; } // Automapper automatically flattens it to simpler model
    }

    public class ProductCreateViewModel : IMapTo<Product>
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class ProductEditViewModel : IHaveCustomMappings
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public new void CreateMappings(IConfiguration configuration)
        {
            configuration.CreateMap<ProductEditViewModel, Product>()
                .ForAllMembers(opt => opt.Condition(src => !src.IsSourceValueNull)); // ignore if a value is empty (for strings) or null
        }
    }

 

Categorized in:

Tagged in:

,