Де розмістити AutoMapper.CreateMaps?


216

Я використовую AutoMapperв ASP.NET MVCдодатку. Мені сказали, що я повинен переїхати в AutoMapper.CreateMapінше місце, оскільки у них багато накладних витрат. Я не надто впевнений, як створити свою програму, щоб розмістити ці дзвінки лише на 1 місці.

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

Отже, які ваші налаштування для AutoMapper'CreateMap'? Куди ти її поклав? Як ви це називаєте?

Відповіді:


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
}

Ми створюємо метод для кожного "сукупності" (Користувач, Повідомлення), тому речі добре розділені.

Тоді ваше 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>();
    }
}

Значно чистіший / надійніший.


2
@ AliRızaAdıyahşi Обидва проекти повинні мати файл зіставлення. Core повинен мати AutoMapperCoreConfiguration, а інтерфейс повинен мати AutoMapperWebConfiguration. Веб-конфігурація повинна додавати профілі з конфігурації Core.
RPM1984

7
Чи виклик Mapper.Initializeу кожному класі конфігурації перезаписує попередні додані профілі? Якщо так, то що слід використовувати замість ініціалізації?
Коді

4
Хіба це не робить ваш проект веб-API посиланням на ваші сервісні та доменні шари?
Chazt3n

3
Якщо у мене є Web -> Сервіс -> BLL -> DAL. Мої сутності знаходяться в моєму доступі. Я не хочу посилатися на свій DAL ні з Інтернету, ні з Сервісу. Як я його ініціалізую?
Vyache

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 порушує принцип відкритого закриття?


13
Це. Кожен крок до належної "жорсткої" архітектури, здається, включає в себе експоненціально більше коду. Це легко; цього вистачить для 99,9% кодерів; і ваші колеги оцінять простоту. Так, кожен повинен прочитати питання щодо принципу відкритого закриття, але кожен також повинен подумати про компроміс.
анонс

де ви створили клас AutoMapperBootStrapper?
користувач6395764

16

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

Я би застосував подібний підхід, як і Thoai. Але я б використовував вбудований 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()

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


3
Тільки FYI, клас SelfProfiler пішов з Automapper v2.
Метт Хонікотт

15

Для тих, хто дотримується наступного:

  1. використовуючи контейнер з йоком
  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, але це умова, з якою я можу жити.


14

Усі вищезазначені рішення забезпечують статичний метод для виклику (від app_start або будь-якого іншого), який повинен викликати інші методи для налаштування частин відображення-конфігурації. Але якщо у вас є модульна програма, ці модулі можуть підключатись та виходити із програми в будь-який час, ці рішення не працюють. Я пропоную використовувати 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
@Vyache це досить зрозуміло! у 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

Розміщення всієї логіки відображення в 1 місці для мене не є хорошою практикою. Тому що клас картографування буде надзвичайно великим і дуже важким для обслуговування.

Рекомендую розмістити дані про картографування разом із класом 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);

Трохи налаштувавшись на деякі особливості, цей підхід чудово працює і з MVC - дякую хлопце!
Nick Coad

будь ласка, додайте демонстраційний приклад у github
Mohammad Daliri

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)

Я спробував вашу відповідь, але в цьому рядку відображається помилка: Dim config = New MapperConfiguration (// Розв’язання перевантаження не вдалося, тому що недоступне "Нове" не можна викликати цими аргументами: "Public Overloads Sub New (configurationExpression As MapperConfigurationExpression) Можна ви мені, будь ласка, допоможіть?
barsan

@barsan: Чи правильно ви налаштували всі класи профілю (UserProfile та PostProfile)? Для мене він працює з версією Automapper версії 5.2.0.
roland

Випущена нова версія 6.0. Тож Protected Overrides Sub Configure()застаріле. Все так само, але ця лінія повинна бути:Public Sub New()
roland
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.