Jak skonfigurować Automapper w ASP.NET Core

254

Jestem stosunkowo nowy w .NET i postanowiłem zająć się .NET Core zamiast uczyć się „starych sposobów”. Znalazłem tutaj szczegółowy artykuł na temat konfigurowania AutoMapper dla .NET Core , ale czy dla początkującego jest prostszy przewodnik?

theutz
źródło
5
Zobacz dotnetcoretutorials.com/2017/09/23/…
Michael Freidgeim
Dla nowszych wersji rdzenia (> v1) sprawdź odpowiedź @ Saineshwar stackoverflow.com/a/53455699/833878
Robbie
1
Pełna odpowiedź z przykładem kliknij ten link
Iman Bahrampour

Odpowiedzi:

553

Rozgryzłem to! Oto szczegóły:

  1. Dodaj główny pakiet AutoMapper do swojego rozwiązania za pośrednictwem NuGet .
  2. Dodaj pakiet AutoMapper Dependency Injection do swojego rozwiązania za pośrednictwem NuGet .

  3. Utwórz nową klasę dla profilu mapowania. (Utworzyłem klasę w głównym katalogu rozwiązań o nazwie MappingProfile.csi dodałem następujący kod.) Jako przykładu użyję obiektu Useri UserDto.

    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. Następnie dodaj AutoMapperConfiguration w Startup.cssposób pokazany poniżej:

    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. Aby wywołać odwzorowany obiekt w kodzie, wykonaj następujące czynności:

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

Mam nadzieję, że to pomoże komuś zacząć od nowa z ASP.NET Core! Czekam na wszelkie uwagi i uwagi, ponieważ wciąż jestem nowy w świecie .NET!

theutz
źródło
3
Szczegółowy artykuł pod linkiem, lostechies.com/jimmybogard/2016/07/20/… , wyjaśnia, w jaki sposób Profilezlokalizowane są klasy
Kieren Johnstone,
22
@theutz Możesz połączyć te dwie linie CreateMap z .ReverseMap () na końcu, cóż, albo. Może to skomentuję, ale uważam to za bardziej intuicyjne.
Astravagrant,
6
Pomocne w kroku 3 może być dodanie „użycia AutoMapper;” u góry, aby zaimportować metodę rozszerzenia.
Rocklan
8
Działa to dobrze z .net core 1.1, już nie po aktualizacji do .net core 2.0. Myślę, że muszę jawnie określić zespół klasy profilu logicznego. Wciąż szukam, jak to osiągnąć. Aktualizacja: Ach, odpowiedź leży w twoim komentarzu, muszę przekazać klasę typeof, która jest moim profilem. // services.AddAutoMapper (typeof (Startup)); // <- nowsza wersja automapper używa tego podpisu
Esen
3
W AutoMapper v8 i dodatku Dependency Injection v5 jedyne, czego potrzeba, to services.AddAutoMapper (); w metodzie ConfigureServices klasy Startup. Dla mnie był nawet w stanie znaleźć klasy Profile w projektach bibliotek klas zależnych.
stricq
68

Krok Aby użyć AutoMapper z ASP.NET Core.

Krok 1. Instalowanie AutoMapper.Extensions.Microsoft.DependencyInjection z pakietu NuGet.

wprowadź opis zdjęcia tutaj

Krok 2. Utwórz folder w rozwiązaniu, aby zachować mapowania o nazwie „Mapowania”.

wprowadź opis zdjęcia tutaj

Krok 3. Po dodaniu folderu Mapping dodaliśmy klasę o nazwie „ MappingProfile ”. Ta nazwa może być czymś wyjątkowym i dobrym do zrozumienia.

W tej klasie zamierzamy zachować wszystkie mapowania.

wprowadź opis zdjęcia tutaj

Krok 4. Inicjowanie programu Mapper przy uruchamianiu „ConfigureServices”

W Startup Class musimy zainicjować utworzony przez nas profil, a także zarejestrować usługę AutoMapper.

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

  services.AddAutoMapper();

Fragment kodu pokazujący metodę ConfigureServices, w której musimy zainicjować i zarejestrować 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);

    }}

Krok 5. Uzyskaj wynik.

Aby uzyskać wynik mapowania, musimy zadzwonić do AutoMapper.Mapper.Map i przekazać poprawne miejsce docelowe i źródło.

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

CodeSnippet

    [HttpPost]
    public void Post([FromBody] SchemeMasterViewModel schemeMaster)
    {
        if (ModelState.IsValid)
        {
            var mappedresult = AutoMapper.Mapper.Map<SchemeMaster>(schemeMaster);
        }
    }
Saineshwar
źródło
13
Pojawia się następujący błąd: 'Mapper' does not contain a definition for 'initialize'. Używam AutoMapper.Extensions.Microsoft.DependencyInjectionwersji 7.0.0
kimbaudi
Super szczegółowa odpowiedź. Dziękuję Panu.
Rod Hartzell,
1
jeśli korzystasz z ASP.NET CORE 3.0 sprawdź ten samouczek Jak skonfigurować AutoMapper w ASP.NET Core 3.0 tutexchange.com/how-to-set-up-automapper-in-asp-net-core-3-0
Saineshwar
44

Chcę rozszerzyć odpowiedzi @ theutz - mianowicie ten wiersz:

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

Jest to błąd ( prawdopodobnie ) w wersji 3.2.0 AutoMapper.Extensions.Microsoft.DependencyInjection. (Używam .NET Core 2.0)

Rozwiązano to w tym problemie GitHub. Jeśli twoje klasy dziedziczące klasy Profile AutoMapper istnieją poza asemblerem, w którym znajduje się klasa startowa, prawdopodobnie nie zostaną zarejestrowane, jeśli twój zastrzyk AutoMapper wygląda następująco:

services.AddAutoMapper();

chyba że wyraźnie określisz, które zespoły mają wyszukiwać profile AutoMapper.

Można to zrobić w następujący sposób w Startup.ConfigureServices:

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

gdzie asemblies i „type_in_assemblies” wskazują na zespół, w którym określone są klasy Profile w aplikacji. Na przykład:

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

Ja przypuszczam (i kładę nacisk na to słowo), które ze względu na realizację następujących przeciążenia bez parametrów (kod źródłowy z GitHub ):

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

polegamy na tym, że CLR ma już zestaw JITed zawierający profile AutoMapper, które mogą być lub nie być prawdziwe, ponieważ są one blokowane tylko w razie potrzeby (więcej szczegółów w tym pytaniu StackOverflow).

GrayCat
źródło
5
To poprawna odpowiedź dla najnowszej wersji AutoMapper i AspNetCore
Joshit
1
to była odpowiedź, której szukałem AutoMapper 8.1 (najnowsza wersja)
Tinaira
30

odpowiedź theutza tutaj jest bardzo dobra, chcę tylko dodać:

Jeśli MapperConfigurationExpressionzamiast tego dziedziczysz profil mapowania Profile, możesz po prostu dodać test, aby zweryfikować konfigurację mapowania, co zawsze jest przydatne:

[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
źródło
Pojawia się jeden błąd: „Wstrzykiwanie zależności AutoMapper Extension jest niezgodne z rdzeniem asp.net core 1.1”. Proszę pomóż!
Rohit Arora
Wydaje się, że definicja „weryfikacji” jest przedmiotem dyskusji. Pojawia się, gdy niektóre właściwości są projektowo odrzucane, aby zapobiec odwzorowaniu.
Jeremy Holovacs
2
Jeśli nie chcesz mapować właściwości, skonfiguruj ją za pomocą .Ignore (). W ten sposób zmusza Cię do aktywnego zastanowienia się nad każdą sprawą - upewnienia się, że nie przegapisz żadnych rzeczy podczas wprowadzania zmian. Właściwie bardzo praktyczne. Tak więc, test weryfikacyjny jest większą siatką bezpieczeństwa, niż wiele osób zdaje sobie sprawę. Nie jest niezawodny, ale dba o pierwsze 90%.
Arve Systad
18

Rozwiązałem to w ten sposób (podobnie jak powyżej, ale wydaje mi się, że jest to czystsze rozwiązanie) dla .NET Core 2.2 / Automapper 8.1.1 z rozszerzeniami.DI 6.1.1.

Utwórz klasę MappingProfile.cs i wypełnij konstruktora Mapami (planuję użyć jednej klasy do przechowywania wszystkich moich mapowań)

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

W Startup.cs dodaj poniżej, aby dodać do DI (argument zestawu jest dla klasy, która przechowuje twoje konfiguracje mapowania, w moim przypadku jest to klasa MappingProfile).

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

W kontrolerze używaj go jak każdego innego obiektu 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
źródło
2
Podoba mi się twoja odpowiedź. Myślę, że owijanie MappingProfilesw new Type[]{}sposób pokazany w tej odpowiedzi nie jest konieczne.
Programista zorientowany na pieniądze
10

W moim Startup.cs (Core 2.2, Automapper 8.1.1)

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

W moim projekcie dostępu do danych

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

W mojej definicji modelu

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

    public class Position
    {
        ...
    }
Brian Rice
źródło
Dlaczego nie można po prostu użyć services.AddAutoMapper( typeof(DAL.MapperProfile) ); zamiast services.AddAutoMapper(new Type[] { typeof(DAL.MapperProfile) });?
Programista zorientowany na pieniądze
8

Lubię wiele odpowiedzi, szczególnie jedną z @saineshwar. Używam .net Core 3.0 z AutoMapper 9.0, więc czuję, że nadszedł czas, aby zaktualizować swoją odpowiedź.

Dla mnie zadziałało Startup.ConfigureServices (...) zarejestruj usługę w ten sposób:

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

Myślę, że reszta odpowiedzi @saineshwar pozostaje idealna. Ale jeśli ktoś jest zainteresowany, mój kod kontrolera to:

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

A moja klasa mapowania:

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

----- EDYTOWAĆ -----

Po przeczytaniu dokumentów dołączonych w komentarzach Luciana Bargaoanu, myślę, że lepiej nieco zmienić tę odpowiedź.

Bez parametrów services.AddAutoMapper()(który miał odpowiedź @saineshwar) już nie działa (przynajmniej dla mnie). Ale jeśli użyjesz zestawu NuGet AutoMapper.Extensions.Microsoft.DependencyInjection, środowisko może sprawdzać wszystkie klasy, które rozszerzają AutoMapper.Profile (takie jak moje, MappingProfile).

Tak więc w moim przypadku, gdy klasa należy do tego samego wykonującego zestawu, rejestracja usługi może zostać skrócona do services.AddAutoMapper(System.Reflection.Assembly.GetExecutingAssembly());
(Bardziej eleganckim podejściem może być bezparametrowe rozszerzenie z tym kodowaniem).

Dzięki, Lucian!

Vic
źródło
6

Korzystam z AutoMapper 6.1.1 i asp.net Core 1.1.2.

Przede wszystkim zdefiniuj klasy Profile dziedziczone przez Profile Class of Automapper. Stworzyłem interfejs IProfile, który jest pusty, jego celem jest jedynie znalezienie klas tego typu.

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

Teraz utwórz oddzielną klasę, np. Mapowania

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

    }

Teraz w MVC Core web Project w pliku Startup.cs, w konstruktorze, wywołaj klasę Mapping, która zainicjuje wszystkie mapowania podczas ładowania aplikacji.

Mappings.RegisterMappings();
Aamir
źródło
Możesz po prostu utworzyć podklasę z klasy profilu, a gdy program uruchomi usługi. AddAutoMapper (); linia kodów Automapper automatycznie je zna.
isaeid
Nie sądzę, aby było to konieczne, jeśli korzystasz z AutoMapper.Extensions.Microsoft.DependancyInjection, który jest dostępny w nugecie.
Greg Gum
5

W przypadku programu ASP.NET Core (testowany przy użyciu wersji 2.0+ i 3.0), jeśli wolisz czytać dokumentację źródłową: https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection/blob/master/README.md

W przeciwnym razie następujące 4 kroki działają:

  1. Zainstaluj AutoMapper.Extensions.Microsoft.DependancyInjection z nuget.

  2. Po prostu dodaj kilka klas profili.

  3. Następnie dodaj poniżej swoją klasę startup.cs. services.AddAutoMapper(OneOfYourProfileClassNamesHere)

  4. Następnie wystarczy wstrzyknąć IMapper do kontrolerów lub tam, gdzie jest to potrzebne:

public class EmployeesController {

    private readonly IMapper _mapper;

    public EmployeesController(IMapper mapper){

        _mapper = mapper;
    }

A jeśli chcesz użyć ProjectTo teraz, po prostu:

var customers = await dbContext.Customers.ProjectTo<CustomerDto>(_mapper.ConfigurationProvider).ToListAsync()
dalcam
źródło
4

W przypadku 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)
  }
}

W twoim startupie:

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

W kontrolerze lub usłudze: Inject mapper:

private readonly IMapper _mapper;

Stosowanie:

var obj = _mapper.Map<TDest>(sourceObject);
Nicolae Lupei
źródło
4

W najnowszych wersjach asp.net core należy użyć następującej inicjalizacji:

services.AddAutoMapper(typeof(YourMappingProfileClass));
martcs
źródło
2

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

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

W Startup.cs

services.AddAutoMapper(typeof(List.Handler));
Sras
źródło
1

Aby dodać do tego, co Arve Systad wspomniał o testowaniu. Jeśli z jakiegokolwiek powodu jesteś podobny do mnie i chcesz zachować strukturę dziedziczenia podaną w rozwiązaniu theutz, możesz skonfigurować MapperConfiguration w następujący sposób:

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

Zrobiłem to w NUnit.

LandSharks
źródło
1

services.AddAutoMapper (); nie działało dla mnie. (Używam Asp.Net Core 2.0)

Po skonfigurowaniu jak poniżej

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

zainicjuj program mapujący IMapper mapper = config.CreateMapper ();

i dodaj obiekt mapujący do usług jako usługi singleton.AddSingleton (mapper);

w ten sposób mogę dodać DI do kontrolera

  private IMapper autoMapper = null;

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

i użyłem jak poniżej w moich metodach działania

  ClientCustomer customerObj = autoMapper.Map<ClientCustomer>(customer);
Venkat pv
źródło
Cześć @venkat, prawdopodobnie po prostu potrzebowałeś dodać pakiet AutoMapper.Extensions.Microsoft.DependancyInjection do swojego projektu
dalcam 25.09.19
-1

Jeśli chodzi o odpowiedź theutz, nie ma potrzeby określania parametru konwertera IMapper w konstruktorze kontrolerów.

możesz użyć Mapera, ponieważ jest on statycznym elementem w dowolnym miejscu kodu.

public class UserController : Controller {
   public someMethod()
   {
      Mapper.Map<User, UserDto>(user);
   }
}
Yaronmil
źródło
11
Ale statyka jest trochę anty-testowalna, nie?
Scott Fraley
3
Tak. Będzie to działać w wielu przypadkach, ale jeśli nie masz skonfigurowanego mapowania podczas wywoływania tej metody w teście, wygeneruje wyjątek (a zatem nie powiedzie się test z niewłaściwego powodu). Po wstrzyknięciu IMappermożesz wyśmiewać to i, na przykład, po prostu ustawić wartość zero, jeśli nie ma to znaczenia dla danego testu.
Arve Systad