Comment configurer Automapper dans ASP.NET Core

255

Je suis relativement nouveau chez .NET et j'ai décidé de m'attaquer à .NET Core au lieu d'apprendre les "anciennes méthodes". J'ai trouvé un article détaillé sur la configuration d'AutoMapper pour .NET Core ici , mais existe-t-il une procédure plus simple pour un débutant?

theutz
la source
5
Voir dotnetcoretutorials.com/2017/09/23/…
Michael Freidgeim
Pour les nouvelles versions de core (> v1), consultez la réponse de @ Saineshwar stackoverflow.com/a/53455699/833878
Robbie
1
Une réponse complète avec un exemple cliquez sur ce lien
Iman Bahrampour

Réponses:

554

Je l'ai compris! Voici les détails:

  1. Ajoutez le package AutoMapper principal à votre solution via NuGet .
  2. Ajoutez le package d'injection de dépendances AutoMapper à votre solution via NuGet .

  3. Créez une nouvelle classe pour un profil de mappage. (J'ai créé une classe dans le répertoire principal de la solution appelée MappingProfile.cset ajoutez le code suivant.) Je vais utiliser un objet Useret UserDtocomme exemple.

    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. Ajoutez ensuite la configuration AutoMapperConfiguration Startup.cscomme indiqué ci-dessous:

    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. Pour appeler l'objet mappé dans du code, procédez comme suit:

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

J'espère que cela aide quelqu'un à recommencer avec ASP.NET Core! Je me réjouis de tout commentaire ou critique car je suis encore nouveau dans le monde .NET!

theutz
la source
3
L'article détaillé lié, lostechies.com/jimmybogard/2016/07/20/… , explique comment les Profileclasses sont situées
Kieren Johnstone
22
@theutz Vous pouvez fusionner ces deux lignes CreateMap avec un .ReverseMap () à la fin de, eh bien, soit. Peut-être le commenter, mais je le trouve plus intuitif.
Astravagrant
6
Il peut être utile à l'étape 3 de mentionner l'ajout d'un "utilisant AutoMapper;" en haut pour que la méthode d'extension soit importée.
Rocklan
8
Cela a bien fonctionné avec .net core 1.1, plus une fois que j'ai mis à niveau vers .net core 2.0. Je pense que je dois spécifier explicitement l'assembly de classe de profil logique. Toujours en train de chercher comment y parvenir. Mise à jour: Ah la réponse réside dans votre commentaire, je dois passer la classe typeof qui est mon profil. // services.AddAutoMapper (typeof (Démarrage)); // <- la nouvelle version de l'automappeur utilise cette signature
Esen
3
Dans AutoMapper v8 et le module Dependency Injection v5, la seule chose nécessaire est les services.AddAutoMapper (); dans la méthode ConfigureServices de la classe Startup. Pour moi, il a même pu trouver des classes de profil dans des projets de bibliothèque de classes dépendants.
stricq
69

Étape pour utiliser AutoMapper avec ASP.NET Core.

Étape 1. Installation d'AutoMapper.Extensions.Microsoft.DependencyInjection à partir du package NuGet.

entrez la description de l'image ici

Étape 2. Créez un dossier dans la solution pour conserver les mappages avec le nom "Mappings".

entrez la description de l'image ici

Étape 3. Après avoir ajouté le dossier Mapping, nous avons ajouté une classe avec le nom " MappingProfile ", ce nom peut être unique et bon à comprendre.

Dans cette classe, nous allons maintenir tous les mappages.

entrez la description de l'image ici

Étape 4. Initialisation du mappeur au démarrage "ConfigureServices"

Dans la classe de démarrage, nous devons initialiser le profil que nous avons créé et également enregistrer le service AutoMapper.

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

  services.AddAutoMapper();

Extrait de code pour afficher la méthode ConfigureServices où nous devons initialiser et enregistrer 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);

    }}

Étape 5. Obtenez la sortie.

Pour obtenir le résultat mappé, nous devons appeler AutoMapper.Mapper.Map et transmettre la destination et la source appropriées.

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

Extrait de code

    [HttpPost]
    public void Post([FromBody] SchemeMasterViewModel schemeMaster)
    {
        if (ModelState.IsValid)
        {
            var mappedresult = AutoMapper.Mapper.Map<SchemeMaster>(schemeMaster);
        }
    }
Saineshwar
la source
13
Je reçois l'erreur suivante: 'Mapper' does not contain a definition for 'initialize'. J'utilise la AutoMapper.Extensions.Microsoft.DependencyInjectionversion 7.0.0
kimbaudi
Réponse super détaillée. Merci Monsieur.
Rod Hartzell
1
si vous utilisez ASP.NET CORE 3.0, consultez ce didacticiel Comment configurer AutoMapper dans ASP.NET Core 3.0 tutexchange.com/how-to-set-up-automapper-in-asp-net-core-3-0
Saineshwar
44

Je veux étendre les réponses de @ theutz - à savoir cette ligne:

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

Il existe un bogue ( probablement ) dans AutoMapper.Extensions.Microsoft.DependencyInjection version 3.2.0. (J'utilise .NET Core 2.0)

Ceci est abordé dans ce numéro GitHub. Si vos classes héritant de la classe Profile d'AutoMapper existent en dehors de l'assembly où votre classe de démarrage est, elles ne seront probablement pas enregistrées si votre injection AutoMapper ressemble à ceci:

services.AddAutoMapper();

sauf si vous spécifiez explicitement les assemblys pour lesquels rechercher les profils AutoMapper.

Cela peut être fait comme ceci dans votre Startup.ConfigureServices:

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

"assemblies" et "type_in_assemblies" pointent vers l'assembly où les classes de profil dans votre application sont spécifiées. Par exemple:

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

Je suppose (et j'insiste sur ce mot) qu'en raison de l'implémentation suivante de la surcharge sans paramètre (code source de GitHub ):

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

nous comptons sur CLR ayant déjà un assemblage JITed contenant des profils AutoMapper qui peuvent être ou ne pas être vrais car ils ne sont jitted que lorsque cela est nécessaire (plus de détails dans cette question StackOverflow).

GrayCat
la source
5
C'est la bonne réponse pour la dernière version d'AutoMapper et d'AspNetCore
Joshit
1
c'était la réponse que je cherchais pour AutoMapper 8.1 (dernière version)
Tinaira
30

la réponse de theutz ici est très bonne, je veux juste ajouter ceci:

Si vous laissez votre profil de mappage hériter de MapperConfigurationExpressionau lieu de Profile, vous pouvez très simplement ajouter un test pour vérifier votre configuration de mappage, ce qui est toujours pratique:

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

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

    (mapper as IMapper).ConfigurationProvider.AssertConfigurationIsValid();
}
Arve Systad
la source
Je reçois une erreur: "L'injection de dépendance d'extension AutoMapper est incompatible avec asp.net core 1.1". Aidez-moi!
Rohit Arora
Il semble que la définition de «vérifier» soit sujette à débat. Cela explose lorsque certaines propriétés sont omises par conception pour empêcher le mappage.
Jeremy Holovacs
2
Si vous ne voulez pas qu'une propriété soit mappée, configurez-la avec .Ignore (). De cette façon, cela vous oblige à réfléchir activement à la gestion de chaque cas - en vous assurant de ne rien manquer lorsque des modifications sont apportées. Super pratique, en fait. Alors oui, le test de vérification est un filet de sécurité plus grand que beaucoup de gens le pensent. Ce n'est pas infaillible, mais il prend soin des 90% premiers.
Arve Systad
18

Je l'ai résolu de cette façon (similaire à ci-dessus mais j'ai l'impression que c'est une solution plus propre) pour .NET Core 2.2 / Automapper 8.1.1 w / Extensions.DI 6.1.1.

Créer la classe MappingProfile.cs et remplir le constructeur avec Maps (je prévois d'utiliser une seule classe pour contenir tous mes mappages)

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

Dans Startup.cs, ajoutez ci-dessous pour ajouter à DI (l'argument d'assembly est pour la classe qui contient vos configurations de mappage, dans mon cas, c'est la classe MappingProfile).

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

Dans Controller, utilisez-le comme n'importe quel autre objet 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);
        }
    }

Coy Meeks
la source
2
J'aime ta réponse. Je pense que l'emballage MappingProfilesavec new Type[]{}comme indiqué dans cette réponse n'est pas nécessaire.
Too Fat Man No Neck
10

Dans mon Startup.cs (Core 2.2, Automapper 8.1.1)

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

Dans mon projet d'accès aux données

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

Dans ma définition de modèle

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

    public class Position
    {
        ...
    }
Brian Rice
la source
Pourquoi n'utilisez-vous pas simplement à la services.AddAutoMapper( typeof(DAL.MapperProfile) ); place de services.AddAutoMapper(new Type[] { typeof(DAL.MapperProfile) });?
Too Fat Man No Neck
8

J'aime beaucoup de réponses, en particulier celle de @saineshwar. J'utilise .net Core 3.0 avec AutoMapper 9.0, donc je pense qu'il est temps de mettre à jour sa réponse.

Ce qui a fonctionné pour moi était dans Startup.ConfigureServices (...) enregistrez le service de cette manière:

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

Je pense que le reste de la réponse @saineshwar reste parfait. Mais si quelqu'un est intéressé, mon code de contrôleur est:

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

Et ma classe de cartographie:

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

----- ÉDITER -----

Après avoir lu les documents liés dans les commentaires de Lucian Bargaoanu, je pense qu'il vaut mieux changer un peu cette réponse.

Le sans paramètre services.AddAutoMapper() (qui avait la réponse @saineshwar) ne fonctionne plus (du moins pour moi). Mais si vous utilisez l'assembly NuGet AutoMapper.Extensions.Microsoft.DependencyInjection, le framework est capable d'inspecter toutes les classes qui étendent AutoMapper.Profile (comme la mienne, MappingProfile).

Donc, dans mon cas, lorsque la classe appartient au même assembly d'exécution, l'enregistrement du service peut être raccourci services.AddAutoMapper(System.Reflection.Assembly.GetExecutingAssembly());
(une approche plus élégante pourrait être une extension sans paramètre avec ce codage).

Merci, Lucian!

Vic
la source
6

J'utilise AutoMapper 6.1.1 et asp.net Core 1.1.2.

Tout d'abord, définissez les classes de profil héritées par Profile Class of Automapper. J'ai créé l'interface IProfile qui est vide, le but est seulement de trouver les classes de ce type.

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

Créez maintenant une classe séparée, par exemple Mappages

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

    }

Maintenant, dans MVC Core web Project dans le fichier Startup.cs, dans le constructeur, appelez la classe Mapping qui initialisera tous les mappages au moment du chargement de l'application.

Mappings.RegisterMappings();
Aamir
la source
Vous pouvez simplement créer une sous-classe à partir de la classe de profil et lorsque le programme exécute services.AddAutoMapper (); ligne de codes L'automappeur les connaît automatiquement.
isaeid
Je ne pense pas que cela soit nécessaire si vous utilisez AutoMapper.Extensions.Microsoft.DependancyInjection qui est disponible dans nuget.
Greg Gum
5

Pour ASP.NET Core (testé avec 2.0+ et 3.0), si vous préférez lire la documentation source: https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection/blob/master/README.md

Sinon, suivre ces 4 étapes fonctionne:

  1. Installez AutoMapper.Extensions.Microsoft.DependancyInjection à partir de nuget.

  2. Ajoutez simplement quelques classes de profil.

  3. Ajoutez ensuite ci-dessous à votre classe startup.cs. services.AddAutoMapper(OneOfYourProfileClassNamesHere)

  4. Ensuite, injectez simplement IMapper dans vos contrôleurs ou là où vous en avez besoin:

public class EmployeesController {

    private readonly IMapper _mapper;

    public EmployeesController(IMapper mapper){

        _mapper = mapper;
    }

Et si vous voulez utiliser ProjectTo c'est maintenant simplement:

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

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

Dans votre startup:

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

Dans le contrôleur ou le service: Injectez le mappeur:

private readonly IMapper _mapper;

Usage:

var obj = _mapper.Map<TDest>(sourceObject);
Nicolae Lupei
la source
4

Dans les dernières versions du noyau asp.net, vous devez utiliser l'initialisation suivante:

services.AddAutoMapper(typeof(YourMappingProfileClass));
martcs
la source
2

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

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

Dans Startup.cs

services.AddAutoMapper(typeof(List.Handler));
Sras
la source
1

Pour ajouter à ce qu'Arve Systad a mentionné pour les tests. Si, pour une raison quelconque, vous êtes comme moi et que vous souhaitez conserver la structure d'héritage fournie dans la solution theutz, vous pouvez configurer la configuration de Mapper comme suit:

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

Je l'ai fait dans NUnit.

LandSharks
la source
1

services.AddAutoMapper (); n'a pas fonctionné pour moi. (J'utilise Asp.Net Core 2.0)

Après avoir configuré comme ci-dessous

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

initialiser le mappeur IMapper mapper = config.CreateMapper ();

et ajouter l'objet mappeur aux services en tant que services singleton. AddSingleton (mappeur);

de cette façon, je suis en mesure d'ajouter une DI au contrôleur

  private IMapper autoMapper = null;

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

et j'ai utilisé comme ci-dessous dans mes méthodes d'action

  ClientCustomer customerObj = autoMapper.Map<ClientCustomer>(customer);
Venkat pv
la source
Salut @venkat, vous aviez probablement juste besoin d'ajouter le package AutoMapper.Extensions.Microsoft.DependancyInjection à votre projet
dalcam
-1

à propos de la réponse theutz, il n’est pas nécessaire de spécifier parramètre du mappeur IMapper au niveau du constructeur des contrôleurs.

vous pouvez utiliser le mappeur car il est un membre statique à n'importe quel endroit du code.

public class UserController : Controller {
   public someMethod()
   {
      Mapper.Map<User, UserDto>(user);
   }
}
yaronmil
la source
11
Mais la statique est un peu anti-testable, non?
Scott Fraley
3
Oui. Cela fonctionnera dans de nombreux cas, mais si vous n'avez pas de mappage configuré lors de l'appel de cette méthode dans un test, cela lèvera une exception (et donc échouera au test pour la mauvaise raison). Avec un injecté, IMappervous pouvez vous moquer de cela et, par exemple, le rendre simplement nul s'il n'est pas pertinent pour le test donné.
Arve Systad