Dove posizionare AutoMapper.CreateMaps?


216

Sto usando AutoMapperin ASP.NET MVCun'applicazione. Mi è stato detto che avrei dovuto spostare AutoMapper.CreateMapaltrove perché hanno un sacco di spese generali. Non sono sicuro di come progettare la mia applicazione per mettere queste chiamate in un solo posto.

Ho un livello Web, un livello di servizio e un livello dati. Ognuno è un progetto a sé stante. Uso Ninjectper DI tutto. Userò AutoMapperin entrambi i livelli web e di servizio.

Quindi quali sono le tue impostazioni per AutoMapperCreateMap? Dove lo metti? Come lo chiami?

Risposte:


219

Non importa, purché sia ​​una classe statica. Si tratta di convenzione .

La nostra convenzione è che ogni "livello" (web, servizi, dati) ha un singolo file chiamato AutoMapperXConfiguration.cs, con un singolo metodo chiamato Configure(), dove si Xtrova il livello.

Il Configure()metodo chiama quindi i privatemetodi per ciascuna area.

Ecco un esempio della nostra configurazione del livello Web:

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

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

   // ... etc
}

Creiamo un metodo per ogni "aggregato" (Utente, Posta), quindi le cose sono ben separate.

Quindi il tuo Global.asax:

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

È un po 'come una "interfaccia di parole" - non puoi farla rispettare, ma te lo aspetti, quindi puoi codificare (e refactoring) se necessario.

MODIFICARE:

Ho pensato di menzionare che ora utilizzo i profili AutoMapper , quindi l'esempio sopra diventa:

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

Molto più pulito / più robusto.


2
@ AliRızaAdıyahşi Entrambi i progetti dovrebbero avere un file di mappatura. Il core dovrebbe avere AutoMapperCoreConfiguration e l'interfaccia utente dovrebbe avere AutoMapperWebConfiguration. La configurazione Web dovrebbe aggiungere i profili dalla configurazione Core.
RPM1984,

7
La chiamata Mapper.Initializein ciascuna classe di configurazione sovrascrive i profili precedenti aggiunti? In tal caso, cosa dovrebbe essere usato invece di Inizializza?
Cody,

4
Questo non fa sì che il tuo progetto API web abbia un riferimento ai tuoi livelli di servizio e dominio?
Chazt3n,

3
Se ho Web -> Servizio -> BLL -> DAL. Le mie entità sono nel mio DAL. Non voglio dare un riferimento al mio DAL né dal web né dal servizio. Come lo inizializzo?
Vyache,

19
A partire da AutoMapper 4.2 Mapper.CreateMap()è ora obsoleto. '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.'. Come aggiorneresti il ​​tuo esempio per renderlo conforme ai nuovi requisiti?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ

34

Puoi davvero metterlo ovunque fintanto che il tuo progetto web fa riferimento all'assembly in cui si trova. Nella tua situazione lo metterei nel livello di servizio in quanto sarà accessibile dal livello Web e dal livello di servizio e in seguito se decidi di esegui un'app console o stai facendo un progetto di unit test, la configurazione della mappatura sarà disponibile anche da quei progetti.

Nel tuo Global.asax chiamerai quindi il metodo che imposta tutte le tue mappe. Vedi sotto:

File AutoMapperBootStrapper.cs

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


     }
}

Global.asax all'avvio dell'applicazione

chiama soltanto

AutoMapperBootStrapper.BootStrap();

Ora alcune persone discuteranno contro questo metodo viola alcuni principi SOLIDI, che hanno validi argomenti. Eccoli per la lettura.

La configurazione di Automapper in Bootstrapper viola il principio Open-Closed?


13
Questo. Ogni passo verso la corretta architettura "hardcore" sembra coinvolgere esponenzialmente più codice. Questo è facile; sarà sufficiente per il 99,9% dei programmatori là fuori; e i tuoi collaboratori apprezzeranno la semplicità. Sì, tutti dovrebbero leggere il problema relativo al principio Open-Closed, ma tutti dovrebbero anche pensare al compromesso.
anon

dove hai creato la classe AutoMapperBootStrapper?
user6395764,

16

Aggiornamento: l'approccio pubblicato qui non è più valido poiché SelfProfilerè stato rimosso da AutoMapper v2.

Avrei adottato un approccio simile a quello di Thoai. Ma vorrei utilizzare la SelfProfiler<>classe integrata per gestire le mappe, quindi utilizzare la Mapper.SelfConfigurefunzione per inizializzare.

Usando questo oggetto come sorgente:

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

E questi come destinazione:

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

Puoi creare questi profili:

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

Per inizializzare nella tua applicazione, crea questa classe

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

Aggiungi questa riga al tuo file global.asax.cs: AutoMapperConfiguration.Initialize()

Ora puoi posizionare le tue classi di mappatura dove hanno senso per te e non preoccuparti di una classe di mappatura monolitica.


3
Solo FYI, la classe SelfProfiler è sparita da Automapper v2.
Matt Honeycutt,

15

Per quelli di voi che aderiscono a quanto segue:

  1. usando un contenitore di ioc
  2. non mi piace aprire per questo
  3. non mi piace un file di configurazione monolitico

Ho fatto una combinazione tra i profili e sfruttando il mio contenitore di ioc:

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

Esempio di configurazione:

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

Esempio di utilizzo:

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

Il compromesso è che devi fare riferimento al Mapper dall'interfaccia IMappingEngine anziché dal Mapper statico, ma questa è una convenzione con cui posso convivere.


14

Tutte le soluzioni di cui sopra forniscono un metodo statico per chiamare (da app_start o da qualsiasi dove) che dovrebbe chiamare altri metodi per configurare parti della configurazione della mappatura. Tuttavia, se si dispone di un'applicazione modulare, i moduli possono essere collegati e disattivati ​​in qualsiasi momento, queste soluzioni non funzionano. Suggerisco di utilizzare una WebActivatorlibreria in grado di registrare alcuni metodi su cui eseguire app_pre_starte app_post_startdove:

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

È possibile installare WebActivatortramite NuGet.


2
Di recente sono giunto alla stessa conclusione. Mantiene il codice di creazione della mappa vicino al codice che lo consuma. Questo metodo rende un controller MVC molto più gestibile.
mfras3r

Come posso avviarlo ovunque, puoi fornire un esempio? I link del tuo blog non funzionano ...
Vyache,

1
@Vyache è abbastanza chiaro! nel MyModule1progetto (o qualunque sia il nome del tuo progetto) basta creare una classe chiamata InitMapInModule1e inserire il codice all'interno del file; per altri moduli, fare lo stesso.
Ravy amiry

Gotcha, in realtà l'ho appena provato. Ho aggiunto WebActivator da Nuget alla mia libreria di classi (DAL) e ho creato una classe AutoMapperDalConfiguration statica lì dentro Ho creato l'implementazione di @ RPM1984 per configurare e inizializzare le mappe. Non sto usando il profilo attraverso. Grazie.
Vyache,

10

Oltre alla migliore risposta, un buon modo è usare Autofac IoC liberary per aggiungere un po 'di automazione. Con questo devi solo definire i tuoi profili indipendentemente dalle iniziazioni.

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

            }

        }
    }

e chiamando questa linea nel Application_Startmetodo:

MapperConfig.Configure();

Il codice sopra trova tutte le sottoclassi di Profile e le avvia automaticamente.


7

Mettere tutta la logica di mappatura in 1 posizione non è una buona pratica per me. Perché la classe di mappatura sarà estremamente ampia e molto difficile da mantenere.

Consiglio di mettere le cose di mappatura insieme alla classe ViewModel nello stesso file cs. È possibile passare facilmente alla definizione di mappatura desiderata seguendo questa convenzione. Inoltre, durante la creazione della classe di mapping, è possibile fare riferimento alle proprietà ViewModel più rapidamente poiché si trovano nello stesso file.

Quindi la classe del tuo modello di vista sarà simile a:

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

9
Come lo chiami?
Shawn Mclean,

1
Vorrei seguire una classe per regola del file: stackoverflow.com/q/2434990/1158845
Umair


5

Dalla nuova versione di AutoMapper l'utilizzo del metodo statico Mapper.Map () è obsoleto. Quindi puoi aggiungere MapperConfiguration come proprietà statica a MvcApplication (Global.asax.cs) e usarlo per creare un'istanza di 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


3

Per coloro che stanno (perdendo) utilizzando:

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1 (con profili)

Ecco come sono riuscito a integrare AutoMapper nel " nuovo modo ". Inoltre, un enorme grazie a questa risposta (e domanda)

1 - Creata una cartella nel progetto WebAPI denominata "ProfileMappers". In questa cartella inserisco tutte le classi dei miei profili che creano i miei mapping:

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

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

2 - Nel mio App_Start, ho un SimpleInjectorApiInitializer che configura il mio contenitore 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 - Quindi, nel controller basta iniettare come al solito un'interfaccia IMapper:

private readonly IMapper mapper;

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

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);

Con alcune modifiche ad alcune specifiche, questo approccio funziona egregiamente anche con MVC - grazie ragazzo!
Nick Coad,

si prega di aggiungere un esempio demo in github
Mohammad Daliri,

3

Per i programmatori vb.net che utilizzano la nuova versione (5.x) di 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

Profili:

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

Mappatura:

Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)

Ho provato la tua risposta ma mostra errore su questa riga: Dim config = New MapperConfiguration (// Risoluzione del sovraccarico non riuscita perché non è possibile chiamare 'Nuovo' accessibile con questi argomenti: 'Sovraccarichi pubblici Sub New (configurationExpression As MapperConfigurationExpression) Can mi aiuti per favore su questo?
Barsan

@barsan: hai configurato correttamente tutte le classi di profilo (UserProfile e PostProfile)? Per me funziona con Automapper versione 5.2.0.
roland

La nuova versione 6.0 è stata rilasciata. Quindi Protected Overrides Sub Configure()è deprecato. Tutto rimane uguale ma questa linea dovrebbe essere:Public Sub New()
roland
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.