Где разместить AutoMapper.CreateMaps?

216

Я использую AutoMapperв ASP.NET MVCприложении. Мне сказали, что я должен переехать в AutoMapper.CreateMapдругое место, поскольку у них много накладных расходов. Я не слишком уверен, как спроектировать мое приложение, чтобы эти вызовы были размещены всего в одном месте.

У меня есть веб-слой, сервисный слой и слой данных. У каждого свой проект. Я использую Ninjectдля DI все. Я буду использовать AutoMapperкак на веб-уровне, так и на уровне сервиса.

Итак, каковы ваши настройки для AutoMapperCreateMap? Куда ты это положил? Как вы это называете?

Шон Маклин
источник

Ответы:

219

Неважно, если это статический класс. Это все о конвенции .

Наше соглашение состоит в том, что каждый «слой» (веб, сервисы, данные) имеет один файл, называемый AutoMapperXConfiguration.cs, с единственным методом Configure(), который Xназывается слоем.

Затем Configure()метод вызывает privateметоды для каждой области.

Вот пример нашей конфигурации веб-уровня:

public static class AutoMapperWebConfiguration
{
   public static void Configure()
   {
      ConfigureUserMapping();
      ConfigurePostMapping();
   }

   private static void ConfigureUserMapping()
   {
      Mapper.CreateMap<User,UserViewModel>();
   } 

   // ... etc
}

Мы создаем метод для каждого «агрегата» (User, Post), чтобы все было хорошо разделено.

Тогда ваш Global.asax:

AutoMapperWebConfiguration.Configure();
AutoMapperServicesConfiguration.Configure();
AutoMapperDomainConfiguration.Configure();
// etc

Это что-то вроде «интерфейса слов» - не может обеспечить его выполнение, но вы ожидаете этого, поэтому вы можете кодировать (и реорганизовывать) при необходимости.

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

Просто подумал, что упомяну, что теперь я использую профили AutoMapper , поэтому приведенный выше пример выглядит так:

public static class AutoMapperWebConfiguration
{
   public static void Configure()
   {
      Mapper.Initialize(cfg =>
      {
        cfg.AddProfile(new UserProfile());
        cfg.AddProfile(new PostProfile());
      });
   }
}

public class UserProfile : Profile
{
    protected override void Configure()
    {
         Mapper.CreateMap<User,UserViewModel>();
    }
}

Гораздо чище / надежнее.

RPM1984
источник
2
@ AliRızaAdıyahşi Оба проекта должны иметь файл сопоставления. Ядро должно иметь AutoMapperCoreConfiguration, а пользовательский интерфейс должен иметь AutoMapperWebConfiguration. Веб-конфигурация должна добавить профили из базовой конфигурации.
RPM1984
7
Перезаписывают ли вызовы Mapper.Initializeв каждом классе конфигурации предыдущие добавленные профили? Если да, что следует использовать вместо Initialize?
Коди
4
Разве это не делает ваш проект веб-API ссылкой на уровни вашей службы и домена?
Chazt3n
3
Если у меня есть Web -> Сервис -> BLL -> DAL. Мои сущности в моем DAL. Я не хочу давать ссылку на мой DAL из Интернета или Сервиса. Как мне его инициализировать?
Вяче
19
Начиная с версии AutoMapper 4.2 Mapper.CreateMap()устарела. 'Mapper.Map<TSource, TDestination>(TSource, TDestination)' is obsolete: 'The static API will be removed in version 5.0. Use a MapperConfiguration instance and store statically as needed. Use CreateMapper to create a mapper instance.', Как бы вы обновили свой пример, чтобы он соответствовал новым требованиям?
ʙᴀᴋᴇʀ ʙᴀᴋᴇʀ
34

Вы можете поместить его в любое место, если ваш веб-проект ссылается на сборку, в которой он находится. В вашей ситуации я бы поместил его на сервисный уровень, так как он будет доступен для веб-слоя и сервисного уровня, а позже, если вы решите Создайте консольное приложение или проект модульного тестирования, и конфигурация сопоставления будет доступна и для этих проектов.

В вашем Global.asax вы затем вызовете метод, который устанавливает все ваши карты. Увидеть ниже:

Файл AutoMapperBootStrapper.cs

public static class AutoMapperBootStrapper
{
     public static void BootStrap()
     {  
         AutoMapper.CreateMap<Object1, Object2>();
         // So on...


     }
}

Global.asax при запуске приложения

просто позвони

AutoMapperBootStrapper.BootStrap();

Теперь некоторые люди будут утверждать, что этот метод нарушает некоторые принципы SOLID, которые они имеют веские аргументы. Вот они для чтения.

Конфигурирование Automapper в Bootstrapper нарушает принцип Open-Closed?

Бретт Оллред
источник
13
Это. Кажется, что каждый шаг к правильной «хардкорной» архитектуре включает экспоненциально больше кода. Это просто; этого будет достаточно для 99,9% кодеров; и ваши коллеги оценят простоту. Да, каждый должен прочитать вопрос о принципе Open-Closed, но каждый также должен подумать о компромиссе.
Anon
где вы создали класс AutoMapperBootStrapper?
user6395764
16

Обновление: опубликованный здесь подход больше не действителен, так как SelfProfilerбыл удален с AutoMapper v2.

Я бы выбрал такой же подход, как и Тоаи. Но я бы использовал встроенный SelfProfiler<>класс для обработки карт, а затем использовал Mapper.SelfConfigureфункцию для инициализации.

Используя этот объект в качестве источника:

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public string GetFullName()
    {
        return string.Format("{0} {1}", FirstName, LastName);
    }
}

И это как пункт назначения:

public class UserViewModel
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class UserWithAgeViewModel
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public int Age { get; set; }
}

Вы можете создать эти профили:

public class UserViewModelProfile : SelfProfiler<User,UserViewModel>
{
    protected override void DescribeConfiguration(IMappingExpression<User, UserViewModel> map)
    {
    //This maps by convention, so no configuration needed
    }
}

public class UserWithAgeViewModelProfile : SelfProfiler<User, UserWithAgeViewModel>
{
    protected override void DescribeConfiguration(IMappingExpression<User, UserWithAgeViewModel> map)
    {
    //This map needs a little configuration
        map.ForMember(d => d.Age, o => o.MapFrom(s => DateTime.Now.Year - s.BirthDate.Year));
    }
}

Для инициализации в вашем приложении создайте этот класс

 public class AutoMapperConfiguration
 {
      public static void Initialize()
      {
          Mapper.Initialize(x=>
          {
              x.SelfConfigure(typeof (UserViewModel).Assembly);
              // add assemblies as necessary
          });
      }
 }

Добавьте эту строку в ваш файл global.asax.cs: AutoMapperConfiguration.Initialize()

Теперь вы можете размещать свои классы отображения там, где они имеют смысл, и не беспокоиться об одном монолитном классе отображения.

codeprogression
источник
3
Просто FYI, класс SelfProfiler ушел с Automapper v2.
Мэтт Ханикатт
15

Для тех из вас, кто придерживается следующего:

  1. используя контейнер ioc
  2. не люблю открываться для этого
  3. не нравится монолитный конфигурационный файл

Я сделал комбо между профилями и используя мой контейнер ioc:

Конфигурация IoC:

public class Automapper : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly().BasedOn<Profile>().WithServiceBase());

        container.Register(Component.For<IMappingEngine>().UsingFactoryMethod(k =>
        {
            Profile[] profiles = k.ResolveAll<Profile>();

            Mapper.Initialize(cfg =>
            {
                foreach (var profile in profiles)
                {
                    cfg.AddProfile(profile);
                }
            });

            profiles.ForEach(k.ReleaseComponent);

            return Mapper.Engine;
        }));
    }
}

Пример конфигурации:

public class TagStatusViewModelMappings : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<Service.Contracts.TagStatusViewModel, TagStatusViewModel>();
    }
}

Пример использования:

public class TagStatusController : ApiController
{
    private readonly IFooService _service;
    private readonly IMappingEngine _mapper;

    public TagStatusController(IFooService service, IMappingEngine mapper)
    {
        _service = service;
        _mapper = mapper;
    }

    [Route("")]
    public HttpResponseMessage Get()
    {
        var response = _service.GetTagStatus();

        return Request.CreateResponse(HttpStatusCode.Accepted, _mapper.Map<List<ViewModels.TagStatusViewModel>>(response)); 
    }
}

Компромисс заключается в том, что вы должны ссылаться на Mapper через интерфейс IMappingEngine вместо статического Mapper, но это соглашение, с которым я могу жить.

Marius
источник
14

Все вышеперечисленные решения предоставляют статический метод для вызова (из app_start или любого другого места), который он должен вызывать другими методами для настройки частей mapping-конфигурации. Но если у вас есть модульное приложение, эти модули могут подключаться и отключаться от приложения в любое время, эти решения не работают. Я предлагаю использовать WebActivatorбиблиотеку, которая может зарегистрировать некоторые методы для запуска app_pre_startи в app_post_startлюбом месте:

// in MyModule1.dll
public class InitMapInModule1 {
    static void Init() {
        Mapper.CreateMap<User, UserViewModel>();
        // other stuffs
    }
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule1), "Init")]

// in MyModule2.dll
public class InitMapInModule2 {
    static void Init() {
        Mapper.CreateMap<Blog, BlogViewModel>();
        // other stuffs
    }
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")]

// in MyModule3.dll
public class InitMapInModule3 {
    static void Init() {
        Mapper.CreateMap<Comment, CommentViewModel>();
        // other stuffs
    }
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")]

// and in other libraries...

Вы можете установить WebActivatorчерез NuGet.

Рави Амиры
источник
2
Я недавно пришел к такому же выводу. Он сохраняет код создания вашей карты близко к коду, который его потребляет. Этот метод делает контроллер MVC гораздо более удобным в обслуживании.
mfras3r
Как мне начать это где угодно, можете ли вы привести пример? Ссылки на ваш блог не работают ...
Vyache
1
@ Вяче, это довольно ясно! в MyModule1проекте (или как там у вашего проекта) просто создайте класс с именем InitMapInModule1и поместите код в файл; для других модулей сделайте то же самое.
ravy amiry
Понятно, я на самом деле только что попробовал. Я добавил WebActivator из Nuget в свою библиотеку классов (DAL) и создал статический класс AutoMapperDalConfiguration, где я создал реализацию @ RPM1984 для настройки и инициализации карт. Я не использую профиль через. Спасибо.
Вяче
10

В дополнение к лучшему ответу, хорошим способом является использование библиотеки Autofac IoC для добавления некоторой автоматизации. При этом вы просто определяете свои профили независимо от инициаций.

   public static class MapperConfig
    {
        internal static void Configure()
        {

            var myAssembly = Assembly.GetExecutingAssembly();

            var builder = new ContainerBuilder();

            builder.RegisterAssemblyTypes(myAssembly)
                .Where(t => t.IsSubclassOf(typeof(Profile))).As<Profile>();

            var container = builder.Build();

            using (var scope = container.BeginLifetimeScope())
            {
                var profiles = container.Resolve<IEnumerable<Profile>>();

                foreach (var profile in profiles)
                {
                    Mapper.Initialize(cfg =>
                    {
                        cfg.AddProfile(profile);
                    });                    
                }

            }

        }
    }

и вызывая эту строку в Application_Startметоде:

MapperConfig.Configure();

Приведенный выше код находит все подклассы профиля и запускает их автоматически.

Махмуд Моравей
источник
7

Размещение всей логики сопоставления в одном месте не является хорошей практикой для меня. Потому что класс отображения будет очень большим и очень сложным в обслуживании.

Я рекомендую поместить сопоставление вместе с классом ViewModel в тот же файл cs. Вы можете легко перейти к определению отображения, которое вы хотите, следуя этому соглашению. Более того, при создании класса отображения вы можете ссылаться на свойства ViewModel быстрее, поскольку они находятся в одном файле.

Таким образом, ваш класс модели представления будет выглядеть так:

public class UserViewModel
{
    public ObjectId Id { get; set; }

    public string Firstname { get; set; }

    public string Lastname { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }
}

public class UserViewModelMapping : IBootStrapper // Whatever
{
    public void Start()
    {
        Mapper.CreateMap<User, UserViewModel>();
    }
}
Ван Тоай Нгуен
источник
9
Как вы это называете?
Шон Маклин
1
Я следовал бы одному классу за правило файла: stackoverflow.com/q/2434990/1158845
Umair
Подобное мнение описано в блоге Велира « Организация конфигураций карт AutoMapper в MVC»
xmedeko,
5

В новой версии AutoMapper использование статического метода Mapper.Map () устарело. Таким образом, вы можете добавить MapperConfiguration в качестве статического свойства в MvcApplication (Global.asax.cs) и использовать его для создания экземпляра Mapper.

App_Start

public class MapperConfig
{
    public static MapperConfiguration MapperConfiguration()
    {
        return new MapperConfiguration(_ =>
        {
            _.AddProfile(new FileProfile());
            _.AddProfile(new ChartProfile());
        });
    }
}

Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    internal static MapperConfiguration MapperConfiguration { get; private set; }

    protected void Application_Start()
    {
        MapperConfiguration = MapperConfig.MapperConfiguration();
        ...
    }
}

BaseController.cs

    public class BaseController : Controller
    {
        //
        // GET: /Base/
        private IMapper _mapper = null;
        protected IMapper Mapper
        {
            get
            {
                if (_mapper == null) _mapper = MvcApplication.MapperConfiguration.CreateMapper();
                return _mapper;
            }
        }
    }

https://github.com/AutoMapper/AutoMapper/wiki/Migrating-from-static-API

Андрей Бурыкин
источник
3

Для тех, кто (потерян) использует:

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1 (с профилями)

Вот как мне удалось интегрировать AutoMapper «по- новому ». Также огромное спасибо за этот ответ (и вопрос)

1 - Создана папка в проекте WebAPI под названием «ProfileMappers». В эту папку я помещаю все классы своих профилей, которые создают мои отображения:

public class EntityToViewModelProfile : Profile
{
    protected override void Configure()
    {
        CreateMap<User, UserViewModel>();
    }

    public override string ProfileName
    {
        get
        {
            return this.GetType().Name;
        }
    }
}

2 - В моем App_Start у меня есть SimpleInjectorApiInitializer, который настраивает мой контейнер SimpleInjector:

public static Container Initialize(HttpConfiguration httpConfig)
{
    var container = new Container();

    container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();

    //Register Installers
    Register(container);

    container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

    //Verify container
    container.Verify();

    //Set SimpleInjector as the Dependency Resolver for the API
    GlobalConfiguration.Configuration.DependencyResolver =
       new SimpleInjectorWebApiDependencyResolver(container);

    httpConfig.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);

    return container;
}

private static void Register(Container container)
{
     container.Register<ISingleton, Singleton>(Lifestyle.Singleton);

    //Get all my Profiles from the assembly (in my case was the webapi)
    var profiles =  from t in typeof(SimpleInjectorApiInitializer).Assembly.GetTypes()
                    where typeof(Profile).IsAssignableFrom(t)
                    select (Profile)Activator.CreateInstance(t);

    //add all profiles found to the MapperConfiguration
    var config = new MapperConfiguration(cfg =>
    {
        foreach (var profile in profiles)
        {
            cfg.AddProfile(profile);
        }
    });

    //Register IMapper instance in the container.
    container.Register<IMapper>(() => config.CreateMapper(container.GetInstance));

    //If you need the config for LinqProjections, inject also the config
    //container.RegisterSingleton<MapperConfiguration>(config);
}

3 - Startup.cs

//Just call the Initialize method on the SimpleInjector class above
var container = SimpleInjectorApiInitializer.Initialize(configuration);

4 - Затем в вашем контроллере просто введите, как обычно, интерфейс IMapper:

private readonly IMapper mapper;

public AccountController( IMapper mapper)
{
    this.mapper = mapper;
}

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
jpgrassi
источник
Немного доработав некоторые особенности, этот подход отлично работает и с MVC - спасибо, парень!
Ник Коад
пожалуйста, добавьте демонстрационный пример в github
Мохаммад Далири
3

Для программистов vb.net, использующих новую версию (5.x) AutoMapper.

Global.asax.vb:

Public Class MvcApplication
    Inherits System.Web.HttpApplication

    Protected Sub Application_Start()
        AutoMapperConfiguration.Configure()
    End Sub
End Class

AutoMapperConfiguration:

Imports AutoMapper

Module AutoMapperConfiguration
    Public MapperConfiguration As IMapper
    Public Sub Configure()
        Dim config = New MapperConfiguration(
            Sub(cfg)
                cfg.AddProfile(New UserProfile())
                cfg.AddProfile(New PostProfile())
            End Sub)
        MapperConfiguration = config.CreateMapper()
    End Sub
End Module

Профили:

Public Class UserProfile
    Inherits AutoMapper.Profile
    Protected Overrides Sub Configure()
        Me.CreateMap(Of User, UserViewModel)()
    End Sub
End Class

Отображение:

Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)
Roland
источник
Я попробовал ваш ответ, но в этой строке отображается ошибка: Dim config = New MapperConfiguration (// Не удалось разрешить перегрузку, поскольку нельзя вызвать доступное «New» с такими аргументами: «Public Overloads Sub New (configurationExpression As MapperConfigurationExpression) Can» Пожалуйста, помогите мне в этом?
Барсан
@barsan: Правильно ли настроены все классы профиля (UserProfile и PostProfile)? Для меня это работает с Automapper версии 5.2.0.
Роланд
Новая версия 6.0 выпущена. Так что Protected Overrides Sub Configure()не рекомендуется. Все , остается такой же , но эта линия должна быть:Public Sub New()
Роланда