Как настроить Automapper в ASP.NET Core

255

Я относительно новичок в .NET, и я решил заняться .NET Core вместо изучения «старых способов». Я нашел подробную статью о настройке AutoMapper для .NET Core здесь , но есть ли более простой способ для новичка?

theutz
источник
Для более новых версий core (> v1) ознакомьтесь с ответом @ Saineshwar stackoverflow.com/a/53455699/833878
Робби
1
Полный ответ с примером нажмите на эту ссылку
Иман Бахрампур

Ответы:

554

Я понял! Вот подробности:

  1. Добавьте основной пакет AutoMapper в ваше решение через NuGet .
  2. Добавьте AutoMapper Dependency Injection Package к своему решению через NuGet .

  3. Создайте новый класс для профиля сопоставления. (Я сделал класс в главном каталоге решения называется MappingProfile.csи добавить следующий код.) Я буду использовать Userи 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. Затем добавьте AutoMapperConfiguration Startup.csкак показано ниже:

    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. Чтобы вызвать сопоставленный объект в коде, сделайте что-то вроде следующего:

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

Я надеюсь, что это поможет кому-то начать с ASP.NET Core! Я приветствую любые отзывы или критику, так как я все еще новичок в мире .NET!

theutz
источник
3
Подробная статья по ссылке , lostechies.com/jimmybogard/2016/07/20/… , объясняет, как Profileрасположены классы
Kieren Johnstone
22
@theutz Вы можете объединить эти две строки CreateMap с .ReverseMap () в конце, ну, в любом случае. Возможно, прокомментируйте это, но я нахожу это более интуитивным.
Astravagrant
6
На шаге 3 может оказаться полезным добавить «использующий AutoMapper»; вверху, чтобы метод расширения был импортирован.
Rocklan
8
Это работало нормально с .net core 1.1, больше не после того, как я обновил до .net core 2.0. Я думаю, мне нужно явно указать класс сборки профиля логики. Все еще исследую, как это сделать. Обновление: А, ответ на ваш комментарий, я должен пройти через класс typeof, который является моим профилем. // services.AddAutoMapper (typeof (Startup)); // <- более новая версия Autopper использует эту сигнатуру
Esen
3
В AutoMapper v8 и дополнении Dependency Injection v5 единственное, что нужно, - это services.AddAutoMapper (); строка в методе ConfigureServices класса Startup. Для меня это было даже возможно найти профили классов в зависимых проектах библиотеки классов.
Stricq
69

Шаг Использовать AutoMapper с ASP.NET Core.

Шаг 1. Установка AutoMapper.Extensions.Microsoft.DependencyInjection из пакета NuGet.

введите описание изображения здесь

Шаг 2. Создайте папку в решении, чтобы сохранить сопоставления с именем «сопоставления».

введите описание изображения здесь

Шаг 3. После добавления папки Mapping мы добавили класс с именем « MappingProfile », это имя может быть уникальным и полезным для понимания.

В этом классе мы собираемся сохранить все отображения.

введите описание изображения здесь

Шаг 4. Инициализация Mapper при запуске «ConfigureServices»

В классе запуска нам нужно инициализировать созданный нами профиль, а также зарегистрировать службу AutoMapper.

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

  services.AddAutoMapper();

Фрагмент кода, чтобы показать метод ConfigureServices, где нам нужно инициализировать и зарегистрировать 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);

    }}

Шаг 5. Получите вывод.

Чтобы получить результат Mapped, нам нужно вызвать AutoMapper.Mapper.Map и передать правильный пункт назначения и источник.

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

CodeSnippet

    [HttpPost]
    public void Post([FromBody] SchemeMasterViewModel schemeMaster)
    {
        if (ModelState.IsValid)
        {
            var mappedresult = AutoMapper.Mapper.Map<SchemeMaster>(schemeMaster);
        }
    }
Saineshwar
источник
13
Я получаю следующее сообщение об ошибке: 'Mapper' does not contain a definition for 'initialize'. Я использую AutoMapper.Extensions.Microsoft.DependencyInjectionверсию 7.0.0
kimbaudi
Супер подробный ответ. Спасибо, сэр.
Род
1
если вы используете ASP.NET CORE 3.0, ознакомьтесь с этим руководством. Как настроить AutoMapper в ASP.NET Core 3.0 tutexchange.com/how-to-set-up-automapper-in-asp-net-core-3-0
Saineshwar
44

Я хочу расширить ответы @ theutz, а именно:

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

Существует ошибка ( вероятно ) в AutoMapper.Extensions.Microsoft.DependencyInjection версии 3.2.0. (Я использую .NET Core 2.0)

Это решается в этой проблеме GitHub. Если ваши классы, наследующие класс Profile AutoMapper, существуют вне сборки, где вы используете класс Startup, они, вероятно, не будут зарегистрированы, если ваша инъекция AutoMapper выглядит следующим образом:

services.AddAutoMapper();

если вы явно не укажете, какие сборки искать в профилях AutoMapper.

Это можно сделать так в вашем Startup.ConfigureServices:

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

где «сборки» и «type_in_assemblies» указывают на сборку, где указаны классы профиля в вашем приложении. Например:

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

Я полагаю (и я подчеркиваю это слово), что из-за последующей реализации перегрузки без параметров (исходный код из GitHub ):

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

мы полагаемся на то, что в CLR уже имеется сборка JIT, содержащая профили AutoMapper, которые могут быть или не быть истинными, поскольку они объединяются только при необходимости (более подробно в этом вопросе StackOverflow).

GrayCat
источник
5
Вот правильный ответ для последней версии AutoMapper и AspNetCore
Джошит
1
это был ответ, который я искал для AutoMapper 8.1 (последняя версия)
Tinaira
30

ответ theutz здесь очень хороший, я просто хочу добавить это:

Если вы позволяете вашему отображение профиль наследует от MapperConfigurationExpressionвместо Profile, вы можете очень просто добавить тест , чтобы проверить правильность настройки отображения, которое всегда под рукой:

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

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

    (mapper as IMapper).ConfigurationProvider.AssertConfigurationIsValid();
}
Арве Систад
источник
Я получаю одну ошибку: «Внедрение зависимостей расширения AutoMapper несовместимо с asp.net core 1.1». Пожалуйста помоги!
Рохит Арора
Похоже, определение «проверить» подлежит обсуждению. Это взрывается, когда определенные свойства опущены, чтобы предотвратить отображение.
Джереми Холовач
2
Если вы не хотите, чтобы свойство отображалось, установите его с помощью .Ignore (). Таким образом, это заставляет вас активно думать о том, как справляться с каждым делом, и быть уверенным, что вы не пропустите что-то при внесении изменений. Супер практично, на самом деле. Так что да, проверка-проверка - это большая сеть безопасности, чем многие думают. Это не надежно, но это заботится о первых 90%.
Арве Систад
18

Я решил это таким образом (похоже на выше, но я чувствую, что это более чистое решение) для .NET Core 2.2 / Automapper 8.1.1 с Extensions.DI 6.1.1.

Создать класс MappingProfile.cs и заполнить конструктор Maps (я планирую использовать один класс для хранения всех моих отображений)

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

В Startup.cs добавьте ниже, чтобы добавить в DI (arg сборки предназначен для класса, который содержит ваши конфиги отображения, в моем случае это класс MappingProfile).

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

В контроллере используйте его так же, как любой другой объект 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
Мне нравится твой ответ. Я думаю , что упаковка MappingProfilesс , new Type[]{}как показано в этом ответе не требуется.
Слишком толстый мужчина без шеи
10

В моем Startup.cs (Core 2.2, Automapper 8.1.1)

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

В моем проекте доступа к данным

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

В моем определении модели

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

    public class Position
    {
        ...
    }
Брайан Райс
источник
Почему бы тебе просто не использовать services.AddAutoMapper( typeof(DAL.MapperProfile) ); вместо services.AddAutoMapper(new Type[] { typeof(DAL.MapperProfile) });?
Слишком толстый мужчина без шеи
8

Мне нравится много ответов, в частности, @saineshwar. Я использую .net Core 3.0 с AutoMapper 9.0, поэтому я чувствую, что пришло время обновить его ответ.

То, что работало для меня, было в Startup.ConfigureServices (...) зарегистрировать сервис следующим образом:

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

Я думаю, что остальная часть ответа @saineshwar остается идеальной. Но если кому-то интересно, мой код контроллера:

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

И мой класс картографии:

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

----- РЕДАКТИРОВАТЬ -----

После прочтения документов, связанных в комментариях Лучана Баргаоану, я думаю, что лучше немного изменить этот ответ.

services.AddAutoMapper()Без параметров (у которых был ответ @saineshwar) больше не работает (по крайней мере, для меня). Но если вы используете сборку NuGet AutoMapper.Extensions.Microsoft.DependencyInjection, платформа сможет проверять все классы, расширяющие AutoMapper.Profile (например, мой, MappingProfile).

Таким образом, в моем случае, когда класс принадлежит одной и той же исполняющей сборке, регистрация службы может быть сокращена до services.AddAutoMapper(System.Reflection.Assembly.GetExecutingAssembly());
(более элегантный подход мог бы быть расширением без параметров с этим кодированием).

Спасибо, Люциан!

Vic
источник
1
docs.automapper.org/en/latest/…
Лучиан Баргаоану
6

Я использую AutoMapper 6.1.1 и asp.net Core 1.1.2.

Прежде всего, определите классы профиля, унаследованные классом профиля Automapper. Я создал интерфейс IProfile, который пуст, цель состоит только в том, чтобы найти классы этого типа.

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

Теперь создайте отдельный класс, например, Mappings

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

    }

Теперь в веб-проекте MVC Core в файле Startup.cs в конструкторе вызовите класс Mapping, который будет инициализировать все сопоставления во время загрузки приложения.

Mappings.RegisterMappings();
Аамир
источник
Вы можете просто создать подкласс из класса профиля, и когда программа запускает сервисы. AddAutoMapper (); строка кодов Autopper автоматически знает их.
isaeid
Я не думаю, что это необходимо, если вы используете AutoMapper.Extensions.Microsoft.DependancyInjection, который доступен в nuget.
Грег Гам
5

Для ASP.NET Core (протестировано с использованием 2.0+ и 3.0), если вы предпочитаете читать исходную документацию: https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection/blob/master/README.md

В противном случае следующие 4 шага работают:

  1. Установите AutoMapper.Extensions.Microsoft.DependancyInjection из nuget.

  2. Просто добавьте несколько профильных классов.

  3. Затем добавьте ниже в свой класс startup.cs. services.AddAutoMapper(OneOfYourProfileClassNamesHere)

  4. Затем просто вставьте IMapper в свои контроллеры или куда вам нужно:

public class EmployeesController {

    private readonly IMapper _mapper;

    public EmployeesController(IMapper mapper){

        _mapper = mapper;
    }

И если вы хотите использовать ProjectTo сейчас, просто:

var customers = await dbContext.Customers.ProjectTo<CustomerDto>(_mapper.ConfigurationProvider).ToListAsync()
dalcam
источник
4

Для 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)
  }
}

В вашем стартапе:

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

В контроллере или сервисе: Inject Mapper:

private readonly IMapper _mapper;

Использование:

var obj = _mapper.Map<TDest>(sourceObject);
Николае Лупей
источник
4

В последних версиях ядра asp.net вы должны использовать следующую инициализацию:

services.AddAutoMapper(typeof(YourMappingProfileClass));
martcs
источник
2

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

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

В Startup.cs

services.AddAutoMapper(typeof(List.Handler));
SRAS
источник
1

Чтобы добавить то, что Арве Систад упомянул для тестирования. Если по какой-либо причине вы похожи на меня и хотите сохранить структуру наследования, представленную в решении theutz, вы можете настроить MapperConfiguration следующим образом:

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

Я сделал это в NUnit.

LandSharks
источник
1

services.AddAutoMapper (); не работал для меня (Я использую Asp.Net Core 2.0)

После настройки, как показано ниже

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

инициализировать преобразователь IMapper mapper = config.CreateMapper ();

и добавьте объект mapper к сервисам как singleton services.AddSingleton (mapper);

таким образом, я могу добавить DI в контроллер

  private IMapper autoMapper = null;

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

и я использовал, как показано ниже, в моих методах действий

  ClientCustomer customerObj = autoMapper.Map<ClientCustomer>(customer);
Венкат пв
источник
Здравствуйте, @venkat. Возможно, вам просто нужно добавить пакет AutoMapper.Extensions.Microsoft.DependancyInjection в ваш проект
dalcam,
-1

Что касается ответа theutz, то нет необходимости указывать параметр IMapper mapper в конструкторе контроллеров.

Вы можете использовать Mapper, поскольку он является статическим элементом в любом месте кода.

public class UserController : Controller {
   public someMethod()
   {
      Mapper.Map<User, UserDto>(user);
   }
}
yaronmil
источник
11
Но статика немного анти-тестируемая, нет?
Скотт Фрейли
3
Ага. Это будет работать во многих случаях, но если у вас нет настроенного отображения при вызове этого метода в тесте, он выдаст исключение (и, следовательно, провалит тест по неправильной причине). С помощью инъекции IMapperвы можете смоделировать это и, например, просто заставить его вернуть ноль, если это не имеет отношения к данному тесту.
Арве Систад