Como configurar o Automapper no ASP.NET Core

255

Sou relativamente novo no .NET e decidi abordar o .NET Core em vez de aprender as "maneiras antigas". Encontrei um artigo detalhado sobre a configuração do AutoMapper para .NET Core aqui , mas há uma explicação mais simples para um novato?

theutz
fonte
5
Veja dotnetcoretutorials.com/2017/09/23/…
Michael Freidgeim
Para versões mais recentes do core (> v1), consulte a resposta de @ Saineshwar stackoverflow.com/a/53455699/833878
Robbie
1
Uma resposta completa com um exemplo clique neste link
Iman Bahrampour

Respostas:

554

Eu descobri! Aqui estão os detalhes:

  1. Adicione o pacote AutoMapper principal à sua solução via NuGet .
  2. Adicione o pacote de injeção de dependência do AutoMapper à sua solução via NuGet .

  3. Crie uma nova classe para um perfil de mapeamento. (Fiz uma classe no diretório principal da solução chamada MappingProfile.cse adicionei o código a seguir.) Usarei um UsereUserDto objeto como exemplo.

    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. Em seguida, adicione o AutoMapperConfiguration no Startup.cscomo mostrado abaixo:

    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. Para invocar o objeto mapeado no código, faça algo como o seguinte:

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

Espero que isso ajude alguém a começar de novo com o ASP.NET Core! Congratulo-me com quaisquer comentários ou críticas, pois ainda sou novo no mundo .NET!

theutz
fonte
3
O artigo detalhado, link : lostechies.com/jimmybogard/2016/07/20/… , explica como as Profileclasses estão localizadas
Kieren Johnstone
22
@theutz Você pode mesclar essas duas linhas do CreateMap com um .ReverseMap () no final de, também. Talvez comente, mas acho mais intuitivo.
Astravagrant
6
Pode ser útil na Etapa 3 mencionar a adição de um "using AutoMapper;" na parte superior para que o método de extensão seja importado.
Rocklan
8
Isso funcionou bem com o .net core 1.1, não mais depois que eu atualizei para o .net core 2.0. Eu acho que preciso especificar explicitamente o conjunto da classe de perfil lógico. Ainda pesquisando como conseguir isso. Atualização: Ah, a resposta está no seu comentário, tenho que passar no tipo de classe que é o meu perfil. // services.AddAutoMapper (typeof (Startup)); // <- versão mais recente AutoMapper utiliza essa assinatura
Esen
3
No AutoMapper v8 e no complemento Dependency Injection v5, a única coisa necessária é o services.AddAutoMapper (); linha no método ConfigureServices da classe Startup. Para mim, foi possível encontrar classes de perfil em projetos de bibliotecas de classes dependentes.
Stricq
69

Etapa para usar o AutoMapper com o ASP.NET Core.

Etapa 1. Instalando o AutoMapper.Extensions.Microsoft.DependencyInjection a partir do NuGet Package.

insira a descrição da imagem aqui

Etapa 2. Crie uma pasta na solução para manter os mapeamentos com o nome "Mapeamentos".

insira a descrição da imagem aqui

Etapa 3. Após adicionar a pasta Mapping, adicionamos uma classe com o nome " MappingProfile ", que esse nome pode ser único e bom de entender.

Nesta classe, vamos manter todos os mapeamentos.

insira a descrição da imagem aqui

Etapa 4. Inicializando o Mapper na Inicialização "ConfigureServices"

Na classe de inicialização, precisamos inicializar o perfil que criamos e também registrar o serviço AutoMapper.

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

  services.AddAutoMapper();

Fragmento de código para mostrar o método ConfigureServices onde precisamos inicializar e registrar o 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);

    }}

Etapa 5. Obter saída.

Para obter o resultado mapeado, precisamos chamar AutoMapper.Mapper.Map e passar o destino e a origem adequados.

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

Fragmento de código

    [HttpPost]
    public void Post([FromBody] SchemeMasterViewModel schemeMaster)
    {
        if (ModelState.IsValid)
        {
            var mappedresult = AutoMapper.Mapper.Map<SchemeMaster>(schemeMaster);
        }
    }
Saineshwar
fonte
13
Eu recebo o seguinte erro: 'Mapper' does not contain a definition for 'initialize'. Estou usando a AutoMapper.Extensions.Microsoft.DependencyInjectionversão 7.0.0
kimbaudi 14/10
Resposta super detalhada. Obrigado senhor.
Rod Hartzell
1
se você estiver usando o ASP.NET CORE 3.0, consulte este tutorial Como configurar o AutoMapper no ASP.NET Core 3.0 tutexchange.com/how-to-set-up-automapper-in-asp-net-core-3-0
Saineshwar
44

Quero estender as respostas de @ theutz - ou seja, esta linha:

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

Há um erro ( provavelmente ) no AutoMapper.Extensions.Microsoft.DependencyInjection versão 3.2.0. (Estou usando o .NET Core 2.0)

Isso é abordado nesta edição do GitHub. Se suas classes que herdam a classe Profile do AutoMapper existirem fora da montagem onde você está na classe Startup, elas provavelmente não serão registradas se a injeção do AutoMapper estiver assim:

services.AddAutoMapper();

a menos que você especifique explicitamente quais assemblies procurar pelos perfis do AutoMapper.

Isso pode ser feito assim em sua Startup.ConfigureServices:

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

onde "assemblies" e "type_in_assemblies" apontam para o assembly em que as classes Profile em seu aplicativo são especificadas. Por exemplo:

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

Eu suponho que (e eu coloquei ênfase nessa palavra) que, devido a seguinte implementação de sobrecarga sem parâmetros (código-fonte do GitHub ):

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

contamos com o CLR que já montou o JIT, contendo perfis do AutoMapper, que podem ou não ser verdadeiros, pois são acionados apenas quando necessário (mais detalhes nesta pergunta do StackOverflow).

GrayCat
fonte
5
Isso é a resposta correta para a versão mais recente do AutoMapper e AspNetCore
Joshit
1
esta foi a resposta que eu estava procurando para AutoMapper 8.1 (última versão)
Tinaira
30

A resposta do theutz aqui é muito boa, só quero acrescentar:

Se você deixar o seu perfil de mapeamento herdar em MapperConfigurationExpressionvez de Profile, poderá simplesmente adicionar um teste para verificar sua configuração de mapeamento, o que é sempre útil:

[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
fonte
Estou recebendo um erro: "A injeção de dependência do AutoMapper Extension é incompatível com o asp.net core 1.1". Por favor ajude!
Rohit Arora
Parece que a definição de "verificar" está em debate. Isso ocorre quando certas propriedades são omitidas pelo design para impedir o mapeamento.
Jeremy Holovacs 21/03
2
Se você não deseja que uma propriedade seja mapeada, configure-a com .Ignore (). Dessa forma, obriga a pensar ativamente sobre como lidar com cada caso - garantindo que você não perca nada quando as alterações estiverem sendo feitas. Super prático, na verdade. Então, sim, o teste de verificação é uma rede de segurança maior do que muitas pessoas imaginam. Não é infalível, mas cuida dos primeiros 90%.
Arve Systad
18

Resolvi-o desta maneira (semelhante ao acima, mas sinto que é uma solução mais limpa) para o .NET Core 2.2 / Automapper 8.1.1 com Extensions.DI 6.1.1.

Crie a classe MappingProfile.cs e preencha o construtor com o Maps (pretendo usar uma única classe para armazenar todos os meus mapeamentos)

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

Em Startup.cs, adicione abaixo para adicionar ao DI (o argumento arg é para a classe que contém suas configurações de mapeamento, no meu caso, é a classe MappingProfile).

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

No Controller, use-o como faria com qualquer outro objeto 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
fonte
2
Eu gosto da sua resposta. Eu acho que não é necessário envolver MappingProfiles-se new Type[]{}como mostrado nesta resposta .
Homem gordo demais, sem pescoço
10

No meu Startup.cs (Core 2.2, Automapper 8.1.1)

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

No meu projeto de acesso a dados

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

Na minha definição de modelo

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

    public class Position
    {
        ...
    }
Brian Rice
fonte
Por que você simplesmente services.AddAutoMapper( typeof(DAL.MapperProfile) ); não usa services.AddAutoMapper(new Type[] { typeof(DAL.MapperProfile) });?
Homem gordo demais, sem pescoço
8

Eu gosto de muitas respostas, principalmente a de @saineshwar. Estou usando o .net Core 3.0 com o AutoMapper 9.0, então acho que é hora de atualizar sua resposta.

O que funcionou para mim foi no Startup.ConfigureServices (...) registrar o serviço da seguinte maneira:

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

Eu acho que o resto da resposta @saineshwar continua perfeito. Mas se alguém estiver interessado, meu código de controlador é:

[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 minha classe de mapeamento:

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

----- EDIT -----

Depois de ler os documentos vinculados nos comentários de Lucian Bargaoanu, acho melhor mudar um pouco essa resposta.

O parâmetro services.AddAutoMapper() (que teve a resposta @saineshwar) não funciona mais (pelo menos para mim). Mas se você usar o assembly NuGet AutoMapper.Extensions.Microsoft.DependencyInjection, a estrutura poderá inspecionar todas as classes que estendem o AutoMapper.Profile (como o meu, MappingProfile).

Portanto, no meu caso, onde a classe pertence ao mesmo assembly em execução, o registro do serviço pode ser reduzido para services.AddAutoMapper(System.Reflection.Assembly.GetExecutingAssembly());
(Uma abordagem mais elegante pode ser uma extensão sem parâmetros com essa codificação).

Obrigado, Lucian!

Vic
fonte
1
docs.automapper.org/en/latest/…
Lucian Bargaoanu 17/10/1919
6

Estou usando o AutoMapper 6.1.1 e o asp.net Core 1.1.2.

Antes de tudo, defina as classes Profile herdadas pela Profile Class of Automapper. Criei a interface IProfile vazia, o objetivo é apenas encontrar as classes desse tipo.

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

Agora crie uma classe separada, por exemplo, Mapeamentos

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

    }

Agora no MVC Core web Project no arquivo Startup.cs, no construtor, chame a classe Mapping que inicializará todos os mapeamentos no momento do carregamento do aplicativo.

Mappings.RegisterMappings();
Aamir
fonte
Você pode apenas criar uma subclasse a partir da classe de perfil e, quando o programa estiver executando services.AddAutoMapper (); linha de códigos O automapper os conhece automaticamente.
Is17id em 01/10
Eu não acho que isso seja necessário se você usar o AutoMapper.Extensions.Microsoft.DependancyInjection que está disponível no nuget.
Greg Gum
5

Para o ASP.NET Core (testado usando 2.0+ e 3.0), se você preferir ler a documentação de origem: https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection/blob/master/README.md

Caso contrário, siga estes 4 passos:

  1. Instale o AutoMapper.Extensions.Microsoft.DependancyInjection a partir do nuget.

  2. Basta adicionar algumas classes de perfil.

  3. Em seguida, adicione abaixo à sua classe startup.cs. services.AddAutoMapper(OneOfYourProfileClassNamesHere)

  4. Em seguida, basta injetar o IMapper em seus controladores ou onde você precisar:

public class EmployeesController {

    private readonly IMapper _mapper;

    public EmployeesController(IMapper mapper){

        _mapper = mapper;
    }

E se você quiser usar o ProjectTo agora, basta:

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

Para o 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)
  }
}

Na sua inicialização:

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

No controlador ou serviço: injetar mapeador:

private readonly IMapper _mapper;

Uso:

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

Nas versões mais recentes do núcleo do asp.net, você deve usar a seguinte inicialização:

services.AddAutoMapper(typeof(YourMappingProfileClass));
martcs
fonte
2

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

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

Em Startup.cs

services.AddAutoMapper(typeof(List.Handler));
Sras
fonte
1

Para adicionar o que Arve Systad mencionou para teste. Se por algum motivo você for como eu e quiser manter a estrutura de herança fornecida na solução theutz, poderá configurar a Configuração do Mapper da seguinte maneira:

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

Eu fiz isso no NUnit.

LandSharks
fonte
1

services.AddAutoMapper (); não funcionou para mim. (Estou usando o Asp.Net Core 2.0)

Depois de configurar como abaixo

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

inicialize o mapeador IMapper mapper = config.CreateMapper ();

e adicione o objeto mapeador aos serviços como serviços singleton.AddSingleton (mapper);

Desta forma, eu sou capaz de adicionar um DI ao controlador

  private IMapper autoMapper = null;

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

e eu usei como abaixo nos meus métodos de ação

  ClientCustomer customerObj = autoMapper.Map<ClientCustomer>(customer);
Venkat pv
fonte
Oi @venkat você provavelmente só precisava para adicionar o pacote AutoMapper.Extensions.Microsoft.DependancyInjection ao seu projeto
dalcam
-1

sobre a resposta theutz, não há necessidade de especificar o parâmetro do mapeador IMapper no construtor de controladores.

você pode usar o mapeador, pois é um membro estático em qualquer lugar do código.

public class UserController : Controller {
   public someMethod()
   {
      Mapper.Map<User, UserDto>(user);
   }
}
yaronmil
fonte
11
Mas a estática é um pouco anti-testável, não?
Scott Fraley
3
Sim. Isso funcionará em muitos casos, mas se você não tiver mapeamento configurado ao invocar esse método em um teste, lançará uma exceção (e, portanto, falhará no teste pelo motivo errado). Com um injetado, IMappervocê pode zombar disso e, por exemplo, fazer com que ele retorne nulo se for irrelevante para o teste fornecido.
Arve Systad