220

I'm using AutoMapper in an ASP.NET MVC application. I was told that I should move the AutoMapper.CreateMap elsewhere as they have a lot of overhead. I'm not too sure how to design my application to put these calls in just 1 place.

I have a web layer, service layer and a data layer. Each a project of its own. I use Ninject to DI everything. I'll utilize AutoMapper in both web and service layers.

So what are your setup for AutoMapper's CreateMap? Where do you put it? How do you call it?

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
Shawn Mclean
  • 56,733
  • 95
  • 279
  • 406

10 Answers10

223

Doesn't matter, as long as it's a static class. It's all about convention.

Our convention is that each "layer" (web, services, data) has a single file called AutoMapperXConfiguration.cs, with a single method called Configure(), where X is the layer.

The Configure() method then calls private methods for each area.

Here's an example of our web tier config:

public static class AutoMapperWebConfiguration
{
   public static void Configure()
   {
      ConfigureUserMapping();
      ConfigurePostMapping();
   }

   private static void ConfigureUserMapping()
   {
      Mapper.CreateMap<User,UserViewModel>();
   } 

   // ... etc
}

We create a method for each "aggregate" (User, Post), so things are separated nicely.

Then your Global.asax:

AutoMapperWebConfiguration.Configure();
AutoMapperServicesConfiguration.Configure();
AutoMapperDomainConfiguration.Configure();
// etc

It's kind of like an "interface of words" - can't enforce it, but you expect it, so you can code (and refactor) if necessary.

EDIT:

Just thought I'd mention that I now use AutoMapper profiles, so the above example becomes:

public static class AutoMapperWebConfiguration
{
   public static void Configure()
   {
      Mapper.Initialize(cfg =>
      {
        cfg.AddProfile(new UserProfile());
        cfg.AddProfile(new PostProfile());
      });
   }
}

public class UserProfile : Profile
{
    protected override void Configure()
    {
         Mapper.CreateMap<User,UserViewModel>();
    }
}

Much cleaner/more robust.

wonea
  • 4,783
  • 17
  • 86
  • 139
RPM1984
  • 72,246
  • 58
  • 225
  • 350
  • I have two project. MyApp.UI (mvc) and MyApp.Core (CLR). ViewModels are in MyApp.UI and EntityModels are in MyApp.Core . Question: `Where to place AutoMapperXConfiguration.cs`. (mvc or clr)? To use it in clr, I should refrence mvc project to clr. So I think, in mvc is logical? – AliRıza Adıyahşi Nov 01 '12 at 07:14
  • 2
    @AliRızaAdıyahşi Both projects should have a mapping file. Core should have AutoMapperCoreConfiguration, and UI should have AutoMapperWebConfiguration. The web configuration should add the profiles from the Core configuration. – RPM1984 Nov 01 '12 at 22:57
  • 7
    Does calling `Mapper.Initialize` in each Configuration class overwrite the previous profiles added? If so, what should be used instead of Initialize? – Cody Jun 18 '14 at 15:43
  • 4
    Doesn't this make your web API project have a reference to Your service and domain layers? – Chazt3n Jan 05 '15 at 18:51
  • If we are going to use Profile, and give a inheritance to the class - what if we already have inherited classes? we cant use multiple class inheritance and for using automapper, we can't create interfaces – Moiz Feb 27 '15 at 04:50
  • 3
    If I have Web -> Service -> BLL -> DAL. My entities are in my DAL. I don't want to give a reference to my DAL from either web or Service. How do I initialize it? – Vyache Dec 11 '15 at 20:02
  • I have a another layer called common, where I keep all my view modal dto. – Vyache Dec 11 '15 at 22:17
  • 20
    As of AutoMapper 4.2 `Mapper.CreateMap()` is now obselete. `'Mapper.Map(TSource, TDestination)' is obsolete: 'The static API will be removed in version 5.0. Use a MapperConfiguration instance and store statically as needed. Use CreateMapper to create a mapper instance.'`. How would you update your example to conform to the new requirements? – ᴍᴀᴛᴛ ʙᴀᴋᴇʀ Feb 15 '16 at 10:16
  • 2
    The problem with this is that the IMapper instance is created in your website/api project and in architecture where my domain or service project is not aware of my website/api project I cannot access IMapper instance to perform any mappings in my domain project. Seems to me I have to create separate static instances of IMapper... Am I missing anything? – Marko Apr 06 '16 at 15:28
  • 2
    @MatthewT.Baker You can only use CreateMap(); without "Mapper.". https://github.com/AutoMapper/AutoMapper/wiki/Configuration – Jenan May 16 '16 at 11:34
  • 1
    Also as of AutoMapper 5.0 [overriding OnConfigure() in Profile instances is obsolete; use a constructor instead](https://github.com/AutoMapper/AutoMapper/wiki/Configuration) – Ahmed Apr 09 '17 at 01:34
  • 1
    There is no more Configure method to override. Mappings should be placed in constructor now. – IRONicMAN Jul 16 '19 at 15:41
34

You can really put it anywhere as long as your web project references the assembly that it is in. In your situation I would put it in the service layer as that will be accessible by the web layer and the service layer and later if you decide to do a console app or you are doing a unit test project the mapping configuration will be available from those projects as well.

In your Global.asax you will then call the method that sets all of your maps. See below:

File AutoMapperBootStrapper.cs

public static class AutoMapperBootStrapper
{
     public static void BootStrap()
     {  
         AutoMapper.CreateMap<Object1, Object2>();
         // So on...


     }
}

Global.asax on application start

just call

AutoMapperBootStrapper.BootStrap();

Now some people will argue against this method violates some SOLID principles, which they have valid arguments. Here they are for the reading.

Configuring Automapper in Bootstrapper violates Open-Closed Principle?

Community
  • 1
  • 1
Brett Allred
  • 3,459
  • 26
  • 30
  • 13
    This. Every step towards proper "hardcore" architecture seems to involve exponentially more code. This is easy; it will suffice for 99.9% of the coders out there; and your co-workers will appreciate the simplicity. Yes, everyone should read the issue regarding the Open-Closed principle, but everyone should also think about the tradeoff. – anon Jul 27 '11 at 03:25
  • where have you created AutoMapperBootStrapper class? – user6395764 Apr 15 '18 at 14:31
16

Update: The approach posted here is no more valid as SelfProfiler has been removed as of AutoMapper v2.

I would take a similar approach as Thoai. But I would use the built-in SelfProfiler<> class to handle the maps, then use the Mapper.SelfConfigure function to initialize.

Using this object as the source:

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public string GetFullName()
    {
        return string.Format("{0} {1}", FirstName, LastName);
    }
}

And these as the destination:

public class UserViewModel
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class UserWithAgeViewModel
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public int Age { get; set; }
}

You can create these profiles:

public class UserViewModelProfile : SelfProfiler<User,UserViewModel>
{
    protected override void DescribeConfiguration(IMappingExpression<User, UserViewModel> map)
    {
    //This maps by convention, so no configuration needed
    }
}

public class UserWithAgeViewModelProfile : SelfProfiler<User, UserWithAgeViewModel>
{
    protected override void DescribeConfiguration(IMappingExpression<User, UserWithAgeViewModel> map)
    {
    //This map needs a little configuration
        map.ForMember(d => d.Age, o => o.MapFrom(s => DateTime.Now.Year - s.BirthDate.Year));
    }
}

To initialize in your application, create this class

 public class AutoMapperConfiguration
 {
      public static void Initialize()
      {
          Mapper.Initialize(x=>
          {
              x.SelfConfigure(typeof (UserViewModel).Assembly);
              // add assemblies as necessary
          });
      }
 }

Add this line to your global.asax.cs file: AutoMapperConfiguration.Initialize()

Now you can place your mapping classes where they make sense to you and not worry about one monolithic mapping class.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
codeprogression
  • 3,371
  • 1
  • 22
  • 16
15

For those of you who adhere to the following:

  1. using an ioc container
  2. don't like to break open closed for this
  3. don't like a monolithic config file

I did a combo between profiles and leveraging my ioc container:

IoC configuration:

public class Automapper : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly().BasedOn<Profile>().WithServiceBase());

        container.Register(Component.For<IMappingEngine>().UsingFactoryMethod(k =>
        {
            Profile[] profiles = k.ResolveAll<Profile>();

            Mapper.Initialize(cfg =>
            {
                foreach (var profile in profiles)
                {
                    cfg.AddProfile(profile);
                }
            });

            profiles.ForEach(k.ReleaseComponent);

            return Mapper.Engine;
        }));
    }
}

Configuration example:

public class TagStatusViewModelMappings : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<Service.Contracts.TagStatusViewModel, TagStatusViewModel>();
    }
}

Usage example:

public class TagStatusController : ApiController
{
    private readonly IFooService _service;
    private readonly IMappingEngine _mapper;

    public TagStatusController(IFooService service, IMappingEngine mapper)
    {
        _service = service;
        _mapper = mapper;
    }

    [Route("")]
    public HttpResponseMessage Get()
    {
        var response = _service.GetTagStatus();

        return Request.CreateResponse(HttpStatusCode.Accepted, _mapper.Map<List<ViewModels.TagStatusViewModel>>(response)); 
    }
}

The trade-off is that you have to reference the Mapper by the IMappingEngine interface instead of the static Mapper, but that's a convention I can live with.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Marius
  • 9,208
  • 8
  • 50
  • 73
14

All of above solutions provide a static method to call (from app_start or any where) that it should call other methods to configure parts of mapping-configuration. But, if you have a modular application, that modules may plug in and out of application at any time, these solutions does not work. I suggest using WebActivator library that can register some methods to run on app_pre_start and app_post_start any where:

// in MyModule1.dll
public class InitMapInModule1 {
    static void Init() {
        Mapper.CreateMap<User, UserViewModel>();
        // other stuffs
    }
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule1), "Init")]

// in MyModule2.dll
public class InitMapInModule2 {
    static void Init() {
        Mapper.CreateMap<Blog, BlogViewModel>();
        // other stuffs
    }
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")]

// in MyModule3.dll
public class InitMapInModule3 {
    static void Init() {
        Mapper.CreateMap<Comment, CommentViewModel>();
        // other stuffs
    }
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")]

// and in other libraries...

You can install WebActivator via NuGet.

amiry jd
  • 27,021
  • 30
  • 116
  • 215
  • 2
    I've recently come to the same conclusion. It keeps your map creation code close to the code consuming it. This method makes a MVC controller far more maintainable. – mfras3r Jul 24 '12 at 17:18
  • How do I start it anywhere, can you provide an example? Your blog links don't work... – Vyache Dec 14 '15 at 15:47
  • 1
    @Vyache it's pretty clear! in `MyModule1` project (or whatever your project's name is) just create a class named `InitMapInModule1` and put the code inside the file; for other modules, do the same. – amiry jd Dec 14 '15 at 16:50
  • Gotcha, I actually just tried it. I added WebActivator from Nuget to my Class library (DAL) and created a static AutoMapperDalConfiguration class in there I created @RPM1984 implementation to configure and Initialize the maps. I'm not using profile through. Thank you. – Vyache Dec 14 '15 at 17:30
10

In addition to the best answer, a good way is using Autofac IoC liberary to add some automation. With this you just define your profiles regardless of initiations.

   public static class MapperConfig
    {
        internal static void Configure()
        {

            var myAssembly = Assembly.GetExecutingAssembly();

            var builder = new ContainerBuilder();

            builder.RegisterAssemblyTypes(myAssembly)
                .Where(t => t.IsSubclassOf(typeof(Profile))).As<Profile>();

            var container = builder.Build();

            using (var scope = container.BeginLifetimeScope())
            {
                var profiles = container.Resolve<IEnumerable<Profile>>();

                foreach (var profile in profiles)
                {
                    Mapper.Initialize(cfg =>
                    {
                        cfg.AddProfile(profile);
                    });                    
                }

            }

        }
    }

and calling this line in Application_Start method:

MapperConfig.Configure();

The above code finds all Profile sub classes and initiate them automatically.

Mahmoud Moravej
  • 8,705
  • 6
  • 46
  • 65
7

Putting all the mapping logic in 1 location is not a good practice for me. Because the mapping class will be extremely large and very hard to maintain.

I recommend put the mapping stuff together with the ViewModel class in the same cs file. You can easily navigate to the mapping definition you want following this convention. Moreover, while creating the mapping class, you can reference to the ViewModel properties faster since they are in the same file.

So your view model class will look like:

public class UserViewModel
{
    public ObjectId Id { get; set; }

    public string Firstname { get; set; }

    public string Lastname { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }
}

public class UserViewModelMapping : IBootStrapper // Whatever
{
    public void Start()
    {
        Mapper.CreateMap<User, UserViewModel>();
    }
}
Van Thoai Nguyen
  • 986
  • 9
  • 22
  • 1
    I would follow one class per file rule: http://stackoverflow.com/q/2434990/1158845 – Umair Mar 13 '16 at 02:13
  • Similar soultion is described in Velir's blog [Organizing AutoMapper’s Map Configurations in MVC](http://www.velir.com/blog/index.php/2012/08/27/organizing-automappers-map-configurations-in-mvc/) – xmedeko Mar 24 '16 at 15:26
5

From new version of AutoMapper using static method Mapper.Map() is deprecated. So you can add MapperConfiguration as static property to MvcApplication (Global.asax.cs) and use it to create instance of Mapper.

App_Start

public class MapperConfig
{
    public static MapperConfiguration MapperConfiguration()
    {
        return new MapperConfiguration(_ =>
        {
            _.AddProfile(new FileProfile());
            _.AddProfile(new ChartProfile());
        });
    }
}

Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    internal static MapperConfiguration MapperConfiguration { get; private set; }

    protected void Application_Start()
    {
        MapperConfiguration = MapperConfig.MapperConfiguration();
        ...
    }
}

BaseController.cs

    public class BaseController : Controller
    {
        //
        // GET: /Base/
        private IMapper _mapper = null;
        protected IMapper Mapper
        {
            get
            {
                if (_mapper == null) _mapper = MvcApplication.MapperConfiguration.CreateMapper();
                return _mapper;
            }
        }
    }

https://github.com/AutoMapper/AutoMapper/wiki/Migrating-from-static-API

Andrey Burykin
  • 700
  • 11
  • 23
3

For vb.net programmers using the new Version (5.x) of AutoMapper.

Global.asax.vb:

Public Class MvcApplication
    Inherits System.Web.HttpApplication

    Protected Sub Application_Start()
        AutoMapperConfiguration.Configure()
    End Sub
End Class

AutoMapperConfiguration:

Imports AutoMapper

Module AutoMapperConfiguration
    Public MapperConfiguration As IMapper
    Public Sub Configure()
        Dim config = New MapperConfiguration(
            Sub(cfg)
                cfg.AddProfile(New UserProfile())
                cfg.AddProfile(New PostProfile())
            End Sub)
        MapperConfiguration = config.CreateMapper()
    End Sub
End Module

Profiles:

Public Class UserProfile
    Inherits AutoMapper.Profile
    Protected Overrides Sub Configure()
        Me.CreateMap(Of User, UserViewModel)()
    End Sub
End Class

Mapping:

Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)
roland
  • 900
  • 12
  • 25
  • I've tried your answer but it is showing error on this line: Dim config = New MapperConfiguration( // Overload resolution failed because no accessible 'New' can be called with these arguments: 'Public Overloads Sub New(configurationExpression As MapperConfigurationExpression) Can you please help me on that? – barsan Dec 23 '16 at 22:08
  • @barsan: Have you configured all profile classes correctly (UserProfile and PostProfile)? For me it works with Automapper version 5.2.0. – roland Dec 25 '16 at 13:46
  • The new version 6.0 is released. So the `Protected Overrides Sub Configure()` is deprecated. Everything stays the same but this line should be: `Public Sub New()` – roland Mar 21 '17 at 11:31
3

For those who are (lost) using:

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1 (With Profiles)

Here's how I managed integrating AutoMapper in the "new way". Also, a Huge thanks to this answer(and question)

1 - Created a folder in the WebAPI project called "ProfileMappers". In this folder I place all my profiles classes which creates my mappings:

public class EntityToViewModelProfile : Profile
{
    protected override void Configure()
    {
        CreateMap<User, UserViewModel>();
    }

    public override string ProfileName
    {
        get
        {
            return this.GetType().Name;
        }
    }
}

2 - In my App_Start, I have a SimpleInjectorApiInitializer which configures my SimpleInjector container:

public static Container Initialize(HttpConfiguration httpConfig)
{
    var container = new Container();

    container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();

    //Register Installers
    Register(container);

    container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

    //Verify container
    container.Verify();

    //Set SimpleInjector as the Dependency Resolver for the API
    GlobalConfiguration.Configuration.DependencyResolver =
       new SimpleInjectorWebApiDependencyResolver(container);

    httpConfig.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);

    return container;
}

private static void Register(Container container)
{
     container.Register<ISingleton, Singleton>(Lifestyle.Singleton);

    //Get all my Profiles from the assembly (in my case was the webapi)
    var profiles =  from t in typeof(SimpleInjectorApiInitializer).Assembly.GetTypes()
                    where typeof(Profile).IsAssignableFrom(t)
                    select (Profile)Activator.CreateInstance(t);

    //add all profiles found to the MapperConfiguration
    var config = new MapperConfiguration(cfg =>
    {
        foreach (var profile in profiles)
        {
            cfg.AddProfile(profile);
        }
    });

    //Register IMapper instance in the container.
    container.Register<IMapper>(() => config.CreateMapper(container.GetInstance));

    //If you need the config for LinqProjections, inject also the config
    //container.RegisterSingleton<MapperConfiguration>(config);
}

3 - Startup.cs

//Just call the Initialize method on the SimpleInjector class above
var container = SimpleInjectorApiInitializer.Initialize(configuration);

4 - Then, in your controller just inject as usually a IMapper interface:

private readonly IMapper mapper;

public AccountController( IMapper mapper)
{
    this.mapper = mapper;
}

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
Community
  • 1
  • 1
jpgrassi
  • 5,482
  • 2
  • 36
  • 55