Come configurare Automapper in ASP.NET Core


255

Sono relativamente nuovo in .NET e ho deciso di affrontare .NET Core invece di imparare i "vecchi modi". Ho trovato un articolo dettagliato sulla configurazione di AutoMapper per .NET Core qui , ma esiste una procedura più semplice per un principiante?



Per le versioni più recenti di nucleo (> v1) controllare @ risposta di Saineshwar stackoverflow.com/a/53455699/833878
Robbie

1
Una risposta completa con un esempio fare clic su questo link
Iman Bahrampour,

Risposte:


554

L'avevo capito! Ecco i dettagli:

  1. Aggiungi il pacchetto AutoMapper principale alla tua soluzione tramite NuGet .
  2. Aggiungi il pacchetto di iniezione di dipendenza AutoMapper alla tua soluzione tramite NuGet .

  3. Crea una nuova classe per un profilo di mappatura. (Ho creato una classe nella directory principale della soluzione chiamata MappingProfile.cse ho aggiunto il seguente codice.) Userò a Usere UserDtoobject come esempio.

    public class MappingProfile : Profile {
        public MappingProfile() {
            // Add as many of these lines as you need to map your objects
            CreateMap<User, UserDto>();
            CreateMap<UserDto, User>();
        }
    }
  4. Quindi aggiungi AutoMapperConfiguration Startup.cscome mostrato di seguito:

    public void ConfigureServices(IServiceCollection services) {
        // .... Ignore code before this
    
       // Auto Mapper Configurations
        var mappingConfig = new MapperConfiguration(mc =>
        {
            mc.AddProfile(new MappingProfile());
        });
    
        IMapper mapper = mappingConfig.CreateMapper();
        services.AddSingleton(mapper);
    
        services.AddMvc();
    
    }
  5. Per invocare l'oggetto mappato nel codice, eseguire le seguenti operazioni:

    public class UserController : Controller {
    
        // Create a field to store the mapper object
        private readonly IMapper _mapper;
    
        // Assign the object in the constructor for dependency injection
        public UserController(IMapper mapper) {
            _mapper = mapper;
        }
    
        public async Task<IActionResult> Edit(string id) {
    
            // Instantiate source object
            // (Get it from the database or whatever your code calls for)
            var user = await _context.Users
                .SingleOrDefaultAsync(u => u.Id == id);
    
            // Instantiate the mapped data transfer object
            // using the mapper you stored in the private field.
            // The type of the source object is the first type argument
            // and the type of the destination is the second.
            // Pass the source object you just instantiated above
            // as the argument to the _mapper.Map<>() method.
            var model = _mapper.Map<UserDto>(user);
    
            // .... Do whatever you want after that!
        }
    }

Spero che questo aiuti qualcuno a ricominciare da capo con ASP.NET Core! Sono lieto di ricevere feedback o critiche poiché sono ancora nuovo nel mondo .NET!


3
L'articolo dettagliato collegato, lostechies.com/jimmybogard/2016/07/20/… , spiega come Profilesi trovano le lezioni
Kieren Johnstone,

22
@theutz È possibile unire quelle due righe CreateMap con un .ReverseMap () alla fine di entrambi. Forse commentalo, ma lo trovo più intuitivo.
Astravagrant

6
Potrebbe essere utile al passaggio 3 menzionare l'aggiunta di un "utilizzo di AutoMapper;" nella parte superiore in modo che venga importato il metodo di estensione.
Rocklan,

8
Funzionava perfettamente con .net core 1.1, non più una volta aggiornato a .net core 2.0. Penso che sia necessario specificare esplicitamente l'assemblaggio della classe del profilo logico. Sto ancora cercando come farlo. Aggiornamento: Ah la risposta risiede nel tuo commento, devo passare la classe typeof che è il mio profilo. // services.AddAutoMapper (typeof (Startup)); // <- la versione più recente di automapper utilizza questa firma
Esen,

3
In AutoMapper v8 e il componente aggiuntivo Dependency Injection v5, l'unica cosa necessaria è services.AddAutoMapper (); riga nel metodo ConfigureServices della classe Startup. Per me, è stato persino in grado di trovare le classi Profile nei progetti di librerie di classi dipendenti.
stricq,

69

Passaggio per utilizzare AutoMapper con ASP.NET Core.

Passaggio 1. Installazione di AutoMapper.Extensions.Microsoft.DependencyInjection dal pacchetto NuGet.

inserisci qui la descrizione dell'immagine

Passaggio 2. Creare una cartella in soluzione per mantenere i mapping con il nome "Mapping".

inserisci qui la descrizione dell'immagine

Passaggio 3. Dopo aver aggiunto la cartella Mapping abbiamo aggiunto una classe con Nome " MappingProfile " questo nome può essere qualcosa di unico e buono da capire.

In questa classe, manterremo tutti i mapping.

inserisci qui la descrizione dell'immagine

Passaggio 4. Inizializzazione di Mapper all'avvio "ConfigureServices"

Nella classe di avvio, dobbiamo inizializzare il profilo che abbiamo creato e anche registrare il servizio AutoMapper.

  Mapper.Initialize(cfg => cfg.AddProfile<MappingProfile>());

  services.AddAutoMapper();

Snippet di codice per mostrare il metodo ConfigureServices in cui è necessario inizializzare e registrare AutoMapper.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }


    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });


        // Start Registering and Initializing AutoMapper

        Mapper.Initialize(cfg => cfg.AddProfile<MappingProfile>());
        services.AddAutoMapper();

        // End Registering and Initializing AutoMapper

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    }}

Passaggio 5. Ottieni output.

Per ottenere un risultato mappato, è necessario chiamare AutoMapper.Mapper.Map e passare la destinazione e l'origine corrette.

AutoMapper.Mapper.Map<Destination>(source);

CodeSnippet

    [HttpPost]
    public void Post([FromBody] SchemeMasterViewModel schemeMaster)
    {
        if (ModelState.IsValid)
        {
            var mappedresult = AutoMapper.Mapper.Map<SchemeMaster>(schemeMaster);
        }
    }

13
Ottengo il seguente errore: 'Mapper' does not contain a definition for 'initialize'. Sto usando la AutoMapper.Extensions.Microsoft.DependencyInjectionversione 7.0.0
kimbaudi

Risposta super dettagliata. Grazie Signore.
Rod Hartzell,

1
se stai usando ASP.NET CORE 3.0 controlla questo tutorial Come impostare AutoMapper in ASP.NET Core 3.0 tutexchange.com/how-to-set-up-automapper-in-asp-net-core-3-0
Saineshwar

44

Voglio estendere le risposte di @ theutz, in particolare questa riga:

// services.AddAutoMapper(typeof(Startup));  // <-- newer automapper version uses this signature.

C'è un bug ( probabilmente ) in AutoMapper.Extensions.Microsoft.DependencyInjection versione 3.2.0. (Sto usando .NET Core 2.0)

Questo è affrontato in questo numero di GitHub. Se le classi che ereditano la classe Profile di AutoMapper esistono al di fuori dell'assembly in cui si trova la classe Startup, probabilmente non verranno registrate se l'iniezione di AutoMapper è simile alla seguente:

services.AddAutoMapper();

a meno che non specifichi esplicitamente quali assembly ricercare i profili di AutoMapper.

Può essere fatto in questo modo in Startup.ConfigureServices:

services.AddAutoMapper(<assembies> or <type_in_assemblies>);

dove "assembly" e "type_in_assemblies" puntano all'assembly in cui sono specificate le classi di profilo nell'applicazione. Per esempio:

services.AddAutoMapper(typeof(ProfileInOtherAssembly), typeof(ProfileInYetAnotherAssembly));

I suppongo (e ho messo l'accento su questa parola) che a causa a seguito dell'attuazione di sovraccarico senza parametri (codice sorgente da GitHub ):

public static IServiceCollection AddAutoMapper(this IServiceCollection services)
{
     return services.AddAutoMapper(null, AppDomain.CurrentDomain.GetAssemblies());
}

facciamo affidamento sul fatto che CLR abbia già un assieme JITed contenente profili AutoMapper che potrebbero essere o non essere veri in quanto vengono solo modificati quando necessario (maggiori dettagli in questa domanda StackOverflow).


5
Questa è la risposta corretta per l'ultima versione di AutoMapper e AspNetCore
Joshit

1
questa era la risposta che stavo cercando AutoMapper 8.1 (ultima versione)
Tinaira,

30

La risposta di theutz qui è molto buona, voglio solo aggiungere questo:

Se lasci che il tuo profilo di mappatura erediti MapperConfigurationExpressioninvece di Profile, puoi semplicemente aggiungere un test per verificare la tua configurazione di mappatura, che è sempre utile:

[Fact]
public void MappingProfile_VerifyMappings()
{
    var mappingProfile = new MappingProfile();

    var config = new MapperConfiguration(mappingProfile);
    var mapper = new Mapper(config);

    (mapper as IMapper).ConfigurationProvider.AssertConfigurationIsValid();
}

Ricevo un errore: "L'iniezione di dipendenza estensione AutoMapper non è compatibile con asp.net core 1.1". Per favore aiuto!
Rohit Arora,

Sembra che la definizione di "verifica" sia in discussione. Questo esplode quando alcune proprietà sono state progettate per impedire la mappatura.
Jeremy Holovacs,

2
Se non si desidera mappare una proprietà, configurarla con .Ignore (). In questo modo, ti costringe a pensare attivamente alla gestione di ciascun caso, assicurandoti di non perdere nulla quando vengono apportate modifiche. Super pratico, in realtà. Quindi sì, il test di verifica è una rete di sicurezza più ampia di quanto molti credano. Non è infallibile, ma si occupa del primo 90%.
Arve Systad,

18

L'ho risolto in questo modo (simile al precedente ma mi sembra che sia una soluzione più pulita) per .NET Core 2.2 / Automapper 8.1.1 con Extensions.DI 6.1.1.

Crea classe MappingProfile.cs e popola il costruttore con Maps (intendo utilizzare una singola classe per contenere tutti i miei mapping)

    public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<Source, Dest>().ReverseMap();
        }
    }

In Startup.cs, aggiungi di seguito per aggiungere a DI (l'assembly arg è per la classe che contiene le tue configurazioni di mappatura, nel mio caso, è la classe MappingProfile).

//add automapper DI
services.AddAutoMapper(typeof(MappingProfile));

In Controller, usalo come faresti con qualsiasi altro oggetto DI

    [Route("api/[controller]")]
    [ApiController]
    public class AnyController : ControllerBase
    {
        private readonly IMapper _mapper;

        public AnyController(IMapper mapper)
        {
            _mapper = mapper;
        }

        public IActionResult Get(int id)
        {
            var entity = repository.Get(id);
            var dto = _mapper.Map<Dest>(entity);

            return Ok(dto);
        }
    }


2
Mi piace la tua risposta. Penso avvolgimento MappingProfilescon new Type[]{}, come mostrato in questa risposta non è necessaria.
Too Fat Man No Neck,

10

Nel mio Startup.cs (Core 2.2, Automapper 8.1.1)

services.AddAutoMapper(new Type[] { typeof(DAL.MapperProfile) });            

Nel mio progetto di accesso ai dati

namespace DAL
{
    public class MapperProfile : Profile
    {
        // place holder for AddAutoMapper (to bring in the DAL assembly)
    }
}

Nella mia definizione del modello

namespace DAL.Models
{
    public class PositionProfile : Profile
    {
        public PositionProfile()
        {
            CreateMap<Position, PositionDto_v1>();
        }
    }

    public class Position
    {
        ...
    }

Perché non usi semplicemente services.AddAutoMapper( typeof(DAL.MapperProfile) ); invece di services.AddAutoMapper(new Type[] { typeof(DAL.MapperProfile) });?
Too Fat Man No Neck,

8

Mi piacciono molte risposte, in particolare quella di @saineshwar. Sto usando .net Core 3.0 con AutoMapper 9.0, quindi sento che è ora di aggiornare la sua risposta.

Ciò che ha funzionato per me è stato in Startup.ConfigureServices (...) registrare il servizio in questo modo:

    services.AddAutoMapper(cfg => cfg.AddProfile<MappingProfile>(), 
                               AppDomain.CurrentDomain.GetAssemblies());

Penso che il resto della risposta di @saineshwar sia perfetto. Ma se qualcuno è interessato il mio codice controller è:

[HttpGet("{id}")]
public async Task<ActionResult> GetIic(int id)
{
    // _context is a DB provider
    var Iic = await _context.Find(id).ConfigureAwait(false);

    if (Iic == null)
    {
        return NotFound();
    }

    var map = _mapper.Map<IicVM>(Iic);

    return Ok(map);
}

E la mia classe di mappatura:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Iic, IicVM>()
            .ForMember(dest => dest.DepartmentName, o => o.MapFrom(src => src.Department.Name))
            .ForMember(dest => dest.PortfolioTypeName, o => o.MapFrom(src => src.PortfolioType.Name));
            //.ReverseMap();
    }
}

----- MODIFICARE -----

Dopo aver letto i documenti collegati nei commenti di Lucian Bargaoanu, penso che sia meglio cambiare un po 'questa risposta.

Il parametro senza parametri services.AddAutoMapper()(con la risposta @saineshwar) non funziona più (almeno per me). Ma se si utilizza l'assemblaggio NuGet AutoMapper.Extensions.Microsoft.DependencyInjection, il framework è in grado di ispezionare tutte le classi che estendono AutoMapper.Profile (come il mio, MappingProfile).

Quindi, nel mio caso, in cui la classe appartiene allo stesso assembly in esecuzione, la registrazione del servizio può essere abbreviata in services.AddAutoMapper(System.Reflection.Assembly.GetExecutingAssembly());
(Un approccio più elegante potrebbe essere un'estensione senza parametri con questa codifica).

Grazie, Lucian!



6

Sto usando AutoMapper 6.1.1 e asp.net Core 1.1.2.

Prima di tutto, definisci le classi di profilo ereditate dalla classe di profilo di Automapper. Ho creato un'interfaccia IProfile che è vuota, lo scopo è solo quello di trovare le classi di questo tipo.

 public class UserProfile : Profile, IProfile
    {
        public UserProfile()
        {
            CreateMap<User, UserModel>();
            CreateMap<UserModel, User>();
        }
    }

Ora crea una classe separata, ad esempio Mappature

 public class Mappings
    {
     public static void RegisterMappings()
     {            
       var all =
       Assembly
          .GetEntryAssembly()
          .GetReferencedAssemblies()
          .Select(Assembly.Load)
          .SelectMany(x => x.DefinedTypes)
          .Where(type => typeof(IProfile).GetTypeInfo().IsAssignableFrom(type.AsType()));

            foreach (var ti in all)
            {
                var t = ti.AsType();
                if (t.Equals(typeof(IProfile)))
                {
                    Mapper.Initialize(cfg =>
                    {
                        cfg.AddProfiles(t); // Initialise each Profile classe
                    });
                }
            }         
        }

    }

Ora nel progetto Web Core MVC nel file Startup.cs, nel costruttore, chiama la classe Mapping che inizializzerà tutti i mapping al momento del caricamento dell'applicazione.

Mappings.RegisterMappings();

Puoi semplicemente creare una sottoclasse dalla classe del profilo e quando il programma esegue services.AddAutoMapper (); linea di codici L'automapper li conosce automaticamente.
Isaeid,

Non penso che questo sia necessario se usi AutoMapper.Extensions.Microsoft.DependancyInjection che è disponibile in nuget.
Greg Gum,

5

Per ASP.NET Core (testato con 2.0+ e 3.0), se si preferisce leggere la documentazione di origine: https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection/blob/master/README.md

Altrimenti seguire questi 4 passaggi funziona:

  1. Installa AutoMapper.Extensions.Microsoft.DependancyInjection dal nuget.

  2. Aggiungi semplicemente alcune classi di profili.

  3. Quindi aggiungi di seguito alla tua classe startup.cs. services.AddAutoMapper(OneOfYourProfileClassNamesHere)

  4. Quindi semplicemente Inietti IMapper nei tuoi controller o ovunque tu ne abbia bisogno:

public class EmployeesController {

    private readonly IMapper _mapper;

    public EmployeesController(IMapper mapper){

        _mapper = mapper;
    }

E se vuoi usare ProjectTo è ora semplicemente:

var customers = await dbContext.Customers.ProjectTo<CustomerDto>(_mapper.ConfigurationProvider).ToListAsync()

4

Per AutoMapper 9.0.0:

public static IEnumerable<Type> GetAutoMapperProfilesFromAllAssemblies()
    {
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            foreach (var aType in assembly.GetTypes())
            {
                if (aType.IsClass && !aType.IsAbstract && aType.IsSubclassOf(typeof(Profile)))
                    yield return aType;
            }
        }
    }

MapperProfile:

public class OrganizationProfile : Profile
{
  public OrganizationProfile()
  {
    CreateMap<Foo, FooDto>();
    // Use CreateMap... Etc.. here (Profile methods are the same as configuration methods)
  }
}

Nel tuo avvio:

services.AddAutoMapper(GetAutoMapperProfilesFromAllAssemblies()
            .ToArray());

In Controller o servizio: Inject mapper:

private readonly IMapper _mapper;

Uso:

var obj = _mapper.Map<TDest>(sourceObject);

4

Nelle ultime versioni di asp.net core è necessario utilizzare la seguente inizializzazione:

services.AddAutoMapper(typeof(YourMappingProfileClass));

2

Asp.Net Core 2.2 con AutoMapper.Extensions.Microsoft.DependencyInjection.

public class MappingProfile : Profile
{
  public MappingProfile()
  {
      CreateMap<Domain, DomainDto>();
  }
}

In Startup.cs

services.AddAutoMapper(typeof(List.Handler));

1

Per aggiungere ciò che Arve Systad ha menzionato per i test. Se per qualsiasi motivo sei come me e desideri mantenere la struttura di ereditarietà fornita nella soluzione theutz, puoi impostare MapperConfiguration in questo modo:

var mappingProfile = new MappingProfile();
var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile(mappingProfile);
});
var mapper = new Mapper(config);

L'ho fatto in NUnit.


1

services.AddAutoMapper (); non ha funzionato per me. (Sto usando Asp.Net Core 2.0)

Dopo aver configurato come di seguito

   var config = new AutoMapper.MapperConfiguration(cfg =>
   {                 
       cfg.CreateMap<ClientCustomer, Models.Customer>();
   });

inizializza il mapper IMapper mapper = config.CreateMapper ();

e aggiungere l'oggetto mapper ai servizi come singleton services.AddSingleton (mapper);

in questo modo sono in grado di aggiungere un DI al controller

  private IMapper autoMapper = null;

  public VerifyController(IMapper mapper)
  {              
   autoMapper = mapper;  
  }

e ho usato come di seguito nei miei metodi di azione

  ClientCustomer customerObj = autoMapper.Map<ClientCustomer>(customer);

Ciao @venkat probabilmente hai solo bisogno di aggiungere il pacchetto AutoMapper.Extensions.Microsoft.DependancyInjection al tuo progetto
dalcam

-1

per quanto riguarda la risposta theutz, non è necessario specificare il parrameter del mapper IMapper nel costruttore dei controller.

puoi usare il Mapper in quanto è un membro statico in qualsiasi posizione del codice.

public class UserController : Controller {
   public someMethod()
   {
      Mapper.Map<User, UserDto>(user);
   }
}

11
Ma la statica è un po 'anti-testabile, no?
Scott Fraley,

3
Sì. Ciò funzionerà in molti casi, ma se non si dispone di una mappatura configurata quando si richiama questo metodo in un test, verrà generata un'eccezione (e quindi fallendo il test per la ragione sbagliata). Con un iniettato IMapperpuoi prenderlo in giro e, ad esempio, renderlo nullo se è irrilevante per il test dato.
Arve Systad,
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.