Où placer AutoMapper.CreateMaps?

216

J'utilise AutoMapperdans une ASP.NET MVCapplication. On m'a dit que je devais déplacer AutoMapper.CreateMapailleurs car ils ont beaucoup de frais généraux. Je ne sais pas trop comment concevoir mon application pour mettre ces appels en un seul endroit.

J'ai une couche web, une couche service et une couche données. Chacun un projet qui lui est propre. J'utilise Ninjectpour tout DI. J'utiliserai AutoMapperdans les couches Web et de service.

Alors, quelle est votre configuration pour AutoMapperCreateMap? Où le mets-tu? Comment l'appelez-vous?

Shawn Mclean
la source

Réponses:

219

Peu importe, tant qu'il s'agit d'une classe statique. Tout est question de convention .

Notre convention est que chaque "couche" (web, services, données) a un seul fichier appelé AutoMapperXConfiguration.cs, avec une seule méthode appelée Configure(), où Xest la couche.

La Configure()méthode appelle ensuite des privateméthodes pour chaque zone.

Voici un exemple de notre configuration de niveau Web:

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

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

   // ... etc
}

Nous créons une méthode pour chaque "agrégat" (utilisateur, publication), afin que les choses soient bien séparées.

Alors votre Global.asax:

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

C'est un peu comme une "interface de mots" - ne peut pas l'imposer, mais vous vous y attendez, vous pouvez donc coder (et refactoriser) si nécessaire.

ÉDITER:

Je pensais juste mentionner que j'utilise maintenant des profils AutoMapper , donc l'exemple ci-dessus devient:

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

Beaucoup plus propre / plus robuste.

RPM1984
la source
2
@ AliRızaAdıyahşi Les deux projets doivent avoir un fichier de mappage. Le noyau doit avoir AutoMapperCoreConfiguration et l'interface utilisateur doit avoir AutoMapperWebConfiguration. La configuration Web doit ajouter les profils de la configuration Core.
RPM1984
7
L'appel Mapper.Initializedans chaque classe de configuration écrase-t-il les profils précédents ajoutés? Dans l'affirmative, que faut-il utiliser au lieu d'initialiser?
Cody
4
Cela ne fait-il pas que votre projet d'API Web fasse référence à vos couches de service et de domaine?
Chazt3n
3
Si j'ai Web -> Service -> BLL -> DAL. Mes entités sont dans mon DAL. Je ne veux pas faire référence à mon DAL à partir du Web ou du Service. Comment l'initialiser?
Vyache
19
Depuis AutoMapper 4.2, il Mapper.CreateMap()est désormais obsolète. 'Mapper.Map<TSource, TDestination>(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.'. Comment mettriez-vous à jour votre exemple pour vous conformer aux nouvelles exigences?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
34

Vous pouvez vraiment le placer n'importe où tant que votre projet Web fait référence à l'assembly dans lequel il se trouve. Dans votre situation, je le mettrais dans la couche service car il sera accessible par la couche web et la couche service et plus tard si vous décidez de faites une application console ou vous faites un projet de test unitaire, la configuration de mappage sera également disponible à partir de ces projets.

Dans votre Global.asax, vous appellerez ensuite la méthode qui définit toutes vos cartes. Voir ci-dessous:

Fichier AutoMapperBootStrapper.cs

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


     }
}

Global.asax au démarrage de l'application

il suffit d'appeler

AutoMapperBootStrapper.BootStrap();

Maintenant, certaines personnes vont argumenter contre cette méthode qui viole certains principes SOLIDES, dont ils ont des arguments valides. Les voici pour la lecture.

Configurer Automapper dans Bootstrapper viole le principe ouvert-fermé?

Brett Allred
la source
13
Ce. Chaque étape vers une architecture "hardcore" appropriée semble impliquer de façon exponentielle plus de code. C'est facile; cela suffira pour 99,9% des codeurs là-bas; et vos collègues apprécieront la simplicité. Oui, tout le monde devrait lire la question concernant le principe ouvert-fermé, mais tout le monde devrait également penser au compromis.
Anon
où avez-vous créé la classe AutoMapperBootStrapper?
user6395764
16

Mettre à jour: l'approche publiée ici n'est plus valide car elle SelfProfilera été supprimée à partir d'AutoMapper v2.

Je prendrais une approche similaire à Thoai. Mais j'utiliserais le intégréSelfProfiler<> classe pour gérer les cartes, puis utiliserais la Mapper.SelfConfigurefonction pour initialiser.

Utilisation de cet objet comme 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);
    }
}

Et ceux-ci comme 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; }
}

Vous pouvez créer ces profils:

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

Pour initialiser dans votre application, créez cette classe

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

Ajoutez cette ligne à votre fichier global.asax.cs: AutoMapperConfiguration.Initialize()

Vous pouvez maintenant placer vos classes de mappage là où elles ont du sens pour vous et ne pas vous soucier d'une seule classe de mappage monolithique.

progression du code
la source
3
Pour info, la classe SelfProfiler a disparu depuis Automapper v2.
Matt Honeycutt
15

Pour ceux d'entre vous qui adhèrent à ce qui suit:

  1. à l'aide d'un conteneur ioc
  2. n'aime pas se casser ouvert pour cette
  3. n'aime pas un fichier de configuration monolithique

J'ai fait un combo entre les profils et en exploitant mon conteneur ioc:

Configuration IoC:

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

Exemple de configuration:

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

Exemple d'utilisation:

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

Le compromis est que vous devez référencer le mappeur par l'interface IMappingEngine au lieu du mappeur statique, mais c'est une convention avec laquelle je peux vivre.

Marius
la source
14

Toutes les solutions ci-dessus fournissent une méthode statique pour appeler (depuis app_start ou n'importe où) qu'elle doit appeler d'autres méthodes pour configurer des parties de la configuration de mappage. Mais, si vous avez une application modulaire, que les modules peuvent se connecter et se déconnecter à tout moment, ces solutions ne fonctionnent pas. Je suggère d'utiliser une WebActivatorbibliothèque qui peut enregistrer certaines méthodes pour s'exécuter app_pre_startet app_post_startn'importe où:

// 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...

Vous pouvez installer WebActivatorvia NuGet.

ravy amiry
la source
2
Je suis récemment arrivé à la même conclusion. Il garde votre code de création de carte proche du code qui le consomme. Cette méthode rend un contrôleur MVC beaucoup plus facile à gérer.
mfras3r
Comment puis-je le démarrer n'importe où, pouvez-vous fournir un exemple? Les liens de votre blog ne fonctionnent pas ...
Vyache
1
@Vyache c'est assez clair! dans le MyModule1projet (ou quel que soit le nom de votre projet), créez simplement une classe nommée InitMapInModule1et mettez le code dans le fichier; pour les autres modules, faites de même.
ravy amiry
Gotcha, je viens de l'essayer. J'ai ajouté WebActivator de Nuget à ma bibliothèque de classes (DAL) et créé une classe statique AutoMapperDalConfiguration là-dedans J'ai créé l'implémentation @ RPM1984 pour configurer et initialiser les cartes. Je n'utilise pas le profil via. Je vous remercie.
Vyache
10

En plus de la meilleure réponse, un bon moyen consiste à utiliser Autofac IoC liberary pour ajouter une certaine automatisation. Avec cela, vous définissez simplement vos profils indépendamment des 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);
                    });                    
                }

            }

        }
    }

et en appelant cette ligne dans la Application_Startméthode:

MapperConfig.Configure();

Le code ci-dessus trouve toutes les sous-classes de profil et les lance automatiquement.

Mahmoud Moravej
la source
7

Mettre toute la logique de mappage en un seul endroit n'est pas une bonne pratique pour moi. Parce que la classe de mappage sera extrêmement grande et très difficile à maintenir.

Je recommande de regrouper les éléments de mappage avec la classe ViewModel dans le même fichier cs. Vous pouvez facilement accéder à la définition de mappage que vous souhaitez en suivant cette convention. De plus, lors de la création de la classe de mappage, vous pouvez faire référence aux propriétés ViewModel plus rapidement car elles se trouvent dans le même fichier.

Ainsi, votre classe de modèle de vue ressemblera à:

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
la source
9
Comment l'appelez-vous?
Shawn Mclean
1
Je
suivrais une
Une soultion similaire est décrite dans le blog Velir Organizing AutoMapper Map Configurations in MVC
xmedeko
5

À partir de la nouvelle version d'AutoMapper utilisant la méthode statique Mapper.Map () est obsolète. Vous pouvez donc ajouter MapperConfiguration en tant que propriété statique à MvcApplication (Global.asax.cs) et l'utiliser pour créer une instance de 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
la source
3

Pour ceux qui sont (perdus) en utilisant:

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1 (avec profils)

Voici comment j'ai réussi à intégrer AutoMapper de la " nouvelle façon ". Aussi, un grand merci à cette réponse (et question)

1 - Création d'un dossier dans le projet WebAPI appelé "ProfileMappers". Dans ce dossier je place toutes mes classes de profils qui créent mes mappages:

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

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

2 - Dans mon App_Start, j'ai un SimpleInjectorApiInitializer qui configure mon conteneur SimpleInjector:

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 - Ensuite, dans votre contrôleur, injectez simplement comme d'habitude une interface IMapper:

private readonly IMapper mapper;

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

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
jpgrassi
la source
Avec quelques ajustements à certains détails, cette approche fonctionne également très bien avec MVC - merci mec!
Nick Coad
veuillez ajouter un exemple de démonstration dans github
Mohammad Daliri
3

Pour les programmeurs vb.net utilisant la nouvelle version (5.x) d'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

Profils:

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

Cartographie:

Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)
Roland
la source
J'ai essayé votre réponse mais elle affiche une erreur sur cette ligne: Dim config = New MapperConfiguration (// Échec de la résolution de surcharge car aucun 'Nouveau' accessible ne peut être appelé avec ces arguments: 'Public Overloads Sub New (configurationExpression As MapperConfigurationExpression) Can vous s'il vous plaît aidez-moi à ce sujet?
barsan
@barsan: Avez-vous configuré correctement toutes les classes de profil (UserProfile et PostProfile)? Pour moi, cela fonctionne avec Automapper version 5.2.0.
roland
La nouvelle version 6.0 est sortie. Donc, Protected Overrides Sub Configure()c'est obsolète. Tout reste le même mais cette ligne devrait être:Public Sub New()
roland