web-dev-qa-db-fra.com

Où placer AutoMapper.CreateMaps?

J'utilise AutoMapper dans une application _ASP.NET MVC_. On m'a dit que je devrais déplacer le _AutoMapper.CreateMap_ car ils ont beaucoup de frais généraux. Je ne sais pas trop comment concevoir mon application pour placer ces appels dans une seule position.

J'ai une couche Web, une couche de service et une couche de données. Chacun un projet qui lui est propre. J'utilise Ninject pour tout DI. Je vais utiliser AutoMapper dans les couches Web et de service.

Alors, quelle est votre configuration pour CreateMap de AutoMapper? Où le mettez-vous? Comment appelez-vous cela?

211
Shawn Mclean

Peu importe, tant que c'est une classe statique. Tout tourne autour de la convention .

Notre convention veut que chaque "couche" (Web, services, données) ait un seul fichier appelé AutoMapperXConfiguration.cs, avec une seule méthode appelée Configure(), où X est le calque.

La méthode Configure() appelle ensuite les méthodes private 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.

Puis votre Global.asax:

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

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

EDIT:

Je pensais juste que je mentionnerais que j'utilise maintenant AutoMapper profiles , ainsi 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.

218
RPM1984

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

Dans votre Global.asax, vous appellerez alors 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 se disputeront contre cette méthode viole certains principes SOLID dont ils disposent d'arguments valides. Les voici pour la lecture.

Configurer Automapper dans Bootstrapper viole le principe Open-Closed?

33
Brett Allred

Mise à jour: L'approche publiée ici n'est plus valide car SelfProfiler a été supprimé à partir d'AutoMapper v2.

Je prendrais une approche similaire à celle de Thoai. Mais je voudrais utiliser la classe SelfProfiler<> intégrée pour gérer les cartes, puis utiliser la fonction Mapper.SelfConfigure pour initialiser.

En utilisant 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 un sens, sans vous soucier d’une classe de mappage monolithique.

16
codeprogression

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

  1. en utilisant un conteneur ioc
  2. n'aime pas briser ouvert fermé pour cette
  3. n'aime pas un fichier de configuration monolithique

J'ai fait une combinaison 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.

15
Marius

Toutes les solutions ci-dessus fournissent une méthode statique permettant d’appeler (depuis app_start ou n’importe où) l’appel à d’autres méthodes pour configurer des parties de mappage-configuration. Toutefois, si vous avez une application modulaire, que les modules peuvent se brancher et ne plus être utilisés à tout moment, ces solutions ne fonctionnent pas. Je suggère d'utiliser la bibliothèque WebActivator qui peut enregistrer certaines méthodes à exécuter sur app_pre_start et app_post_start n'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 WebActivator via NuGet.

14
javad amiry

En plus de la meilleure réponse, un bon moyen consiste à utiliser Autofac Librairie IoC pour ajouter un peu d'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 méthode Application_Start:

MapperConfig.Configure();

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

10
Mahmoud Moravej

Mettre toute la logique de mappage dans un emplacement 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 naviguer vers la définition de mappage que vous voulez 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 se présentera comme suit:

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

Depuis la nouvelle version d'AutoMapper à l'aide de 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

5
Andrey Burykin

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)
3
roland

Pour ceux qui utilisent (perdus):

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

Voici comment j'ai réussi à intégrer AutoMapper dans le " nouvelle façon ". En outre, un énorme grâce à cela 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 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);
3
jpgrassi