Gdzie umieścić AutoMapper.CreateMaps?

216

Używam AutoMapperw ASP.NET MVCaplikacji. Powiedziano mi, że powinienem przenieśćAutoMapper.CreateMap gdzie indziej, ponieważ mają one duże koszty ogólne. Nie jestem zbyt pewien, jak zaprojektować moją aplikację, aby umieścić te połączenia w jednym miejscu.

Mam warstwę internetową, warstwę usługi i warstwę danych. Każdy jest własnym projektem. Używam Ninjectdo wszystkiego DI. Wykorzystam AutoMapperzarówno w warstwie internetowej, jak i usługowej.

Jakie są twoje ustawienia dla AutoMapperCreateMap? Gdzie to położyłeś? Jak to nazywasz?

Shawn Mclean
źródło

Odpowiedzi:

219

Nie ma znaczenia, o ile jest to klasa statyczna. Chodzi o konwencję .

Naszą konwencją jest to, że każda „warstwa” (sieć, usługi, dane) ma jeden plik o nazwie AutoMapperXConfiguration.cs, z jedną metodą o nazwie Configure(), gdzie Xjest warstwa.

Następnie Configure()metoda wywołuje privatemetody dla każdego obszaru.

Oto przykład naszej konfiguracji warstwy internetowej:

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

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

   // ... etc
}

Tworzymy metodę dla każdego „agregatu” (użytkownika, posta), więc wszystko jest ładnie rozdzielone.

Następnie twój Global.asax:

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

To trochę jak „interfejs słów” - nie można go narzucić, ale można się tego spodziewać, więc w razie potrzeby można kodować (i refaktoryzować).

EDYTOWAĆ:

Pomyślałem, że wspomnę, że teraz używam profili AutoMapper , więc powyższy przykład wygląda następująco :

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

Dużo czystszy / bardziej wytrzymały.

RPM1984
źródło
2
@ AliRızaAdıyahşi Oba projekty powinny mieć plik mapowania. Rdzeń powinien mieć AutoMapperCoreConfiguration, a interfejs użytkownika powinien mieć AutoMapperWebConfiguration. Konfiguracja internetowa powinna dodać profile z konfiguracji Core.
RPM1984,
7
Czy wywołanie Mapper.Initializew każdej klasie konfiguracji zastępuje poprzednie dodane profile? Jeśli tak, to czego należy użyć zamiast Inicjalizacji?
Cody
4
Czy to nie powoduje, że projekt interfejsu API sieci Web zawiera odniesienie do Twojej warstwy usług i domeny?
Chazt3n
3
Jeśli mam Web -> Service -> BLL -> DAL. Moje podmioty są w moim DAL. Nie chcę podawać odniesienia do mojego DAL ani z sieci, ani z usługi. Jak to zainicjować?
Vyache,
19
Począwszy od AutoMapper 4.2 Mapper.CreateMap()jest teraz obselete. '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.'. Jak zaktualizowałbyś swój przykład, aby spełniał nowe wymagania?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
34

Możesz naprawdę umieścić go w dowolnym miejscu, o ile projekt sieciowy odwołuje się do zestawu, w którym się znajduje. W twojej sytuacji umieściłbym go w warstwie usługi, ponieważ będzie on dostępny dla warstwy sieci i warstwy usługi, a później, jeśli zdecydujesz się zrobić aplikację na konsolę lub wykonujesz projekt testu jednostkowego, konfiguracja mapowania będzie również dostępna z tych projektów.

Następnie w Global.asax wywołujesz metodę, która ustawia wszystkie twoje mapy. Patrz poniżej:

Plik AutoMapperBootStrapper.cs

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


     }
}

Global.asax przy starcie aplikacji

Zadzwoń

AutoMapperBootStrapper.BootStrap();

Teraz niektórzy ludzie będą argumentować przeciwko tej metodzie naruszającej niektóre zasady SOLID, które mają uzasadnione argumenty. Tutaj są do czytania.

Konfigurowanie Automappera w Bootstrapperie narusza zasadę Open-Closed?

Brett Allred
źródło
13
To. Każdy krok w kierunku właściwej „hardkorowej” architektury wydaje się obejmować wykładniczo więcej kodu. To jest łatwe; wystarczy na 99,9% koderów; a twoi współpracownicy docenią prostotę. Tak, każdy powinien przeczytać kwestię dotyczącą zasady Otwarte-Zamknięte, ale każdy powinien również pomyśleć o kompromisie.
anon
gdzie stworzyłeś klasę AutoMapperBootStrapper?
user6395764
16

Aktualizacja: Podane tutaj podejście nie jest już aktualne jakoSelfProfiler zostało usunięte z wersji AutoMapper v2.

Przyjąłbym podobne podejście jak Thoai. Ale użyłbym wbudowanej SelfProfiler<>klasy do obsługi map, a następnie użyłemMapper.SelfConfigure funkcji do zainicjowania.

Używając tego obiektu jako źródła:

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

A te jako cel:

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

Możesz utworzyć te profile:

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

Aby zainicjować w aplikacji, utwórz tę klasę

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

Dodaj tę linię do pliku global.asax.cs: AutoMapperConfiguration.Initialize()

Teraz możesz umieścić swoje klasy mapowania tam, gdzie mają dla ciebie sens, i nie martwić się o jedną monolityczną klasę mapowania.

codeprogresja
źródło
3
Po prostu FYI, klasa SelfProfiler odeszła od Automapper v2.
Matt Honeycutt
15

Dla tych z was, którzy przestrzegają następujących zasad:

  1. za pomocą kontenera ioc
  2. nie lubię otwierać za to zamkniętego
  3. nie lubię monolitycznego pliku konfiguracyjnego

Zrobiłem kombinację między profilami i wykorzystując mój kontener ioc:

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

Przykład konfiguracji:

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

Przykład użycia:

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

Kompromis polega na tym, że musisz odwoływać się do Mappera za pomocą interfejsu IMappingEngine zamiast statycznego Mappera, ale jest to konwencja, z którą mogę żyć.

Marius
źródło
14

Wszystkie powyższe rozwiązania zapewniają statyczną metodę wywoływania (z app_start lub dowolnego innego miejsca), która powinna wywoływać inne metody konfigurowania części konfiguracji mapowania. Ale jeśli masz aplikację modułową, moduły te mogą się w dowolnym momencie podłączać i wyłączać z aplikacji, te rozwiązania nie działają. Sugeruję użycie WebActivatorbiblioteki, która może zarejestrować niektóre metody do uruchomienia app_pre_starti app_post_startdowolne gdzie:

// 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...

Możesz zainstalować WebActivatorza pomocą NuGet.

ravy amiry
źródło
2
Niedawno doszedłem do tego samego wniosku. Utrzymuje kod tworzenia mapy blisko kodu, który go zużywa. Ta metoda znacznie ułatwia konserwację kontrolera MVC.
mfras3r
Jak mogę go uruchomić w dowolnym miejscu, czy możesz podać przykład? Twoje linki do blogów nie działają ...
Vyache,
1
@ Vyache jest całkiem jasne! w MyModule1projekcie (lub jakiejkolwiek nazwie jest Twój projekt) po prostu stwórz klasę o nazwie InitMapInModule1i umieść kod w pliku; w przypadku innych modułów zrób to samo.
ravy amiry
Gotcha, właśnie go wypróbowałem. Dodałem WebActivator z Nuget do mojej biblioteki klas (DAL) i utworzyłem statyczną klasę AutoMapperDalConfiguration tam stworzyłem implementację @ RPM1984 w celu skonfigurowania i zainicjowania map. Nie używam profilu przez. Dziękuję Ci.
Vyache,
10

Oprócz najlepszej odpowiedzi, dobrym sposobem jest użycie oprogramowania Autofac IoC liberary w celu dodania automatyzacji. Mając to ty właśnie zdefiniowanie profili niezależnie od inicjacji.

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

            }

        }
    }

i wywołanie tej linii w Application_Startmetodzie:

MapperConfig.Configure();

Powyższy kod wyszukuje wszystkie podklasy Profile i inicjuje je automatycznie.

Mahmoud Moravej
źródło
7

Umieszczenie całej logiki mapowania w 1 lokalizacji nie jest dla mnie dobrą praktyką. Ponieważ klasa mapowania będzie bardzo duża i bardzo trudna w utrzymaniu.

Polecam umieszczenie mapowania razem z klasą ViewModel w tym samym pliku cs. Możesz łatwo przejść do definicji mapowania, która ma być zgodna z tą konwencją. Ponadto podczas tworzenia klasy odwzorowania można szybciej odwoływać się do właściwości ViewModel, ponieważ znajdują się one w tym samym pliku.

Twoja klasa modelu widoku będzie wyglądać następująco:

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>();
    }
}
Van Thoai Nguyen
źródło
9
Jak to nazywasz?
Shawn Mclean
1
Postępowałbym zgodnie z jedną klasą dla reguły pliku: stackoverflow.com/q/2434990/1158845
Umair
Podobna dusza została opisana w blogu Velira Organizowanie konfiguracji map AutoMappera w MVC
xmedeko
5

Od nowej wersji AutoMapper przy użyciu metody statycznej Mapper.Map () jest przestarzały. Możesz więc dodać MapperConfiguration jako właściwość statyczną do MvcApplication (Global.asax.cs) i użyć jej do utworzenia instancji Mappera.

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

Andrey Burykin
źródło
3

Dla tych, którzy są (zagubieni) używając:

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1 (z profilami)

Oto jak udało mi się zintegrować AutoMapper w „ nowy sposób ”. Również Ogromne dzięki tej odpowiedzi (i pytanie)

1 - Utworzono folder w projekcie WebAPI o nazwie „ProfileMappers”. W tym folderze umieszczam wszystkie moje klasy profili, które tworzą moje mapowania:

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

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

2 - W moim App_Start mam SimpleInjectorApiInitializer, który konfiguruje mój kontener 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 - Następnie w swoim kontrolerze po prostu wstrzyknij jak zwykle interfejs IMapper:

private readonly IMapper mapper;

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

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
jpgrassi
źródło
Z drobnymi poprawkami do niektórych szczegółów, to podejście doskonale sprawdza się również w MVC - dzięki stary!
Nick Coad,
dodaj przykładowy przykład w github
Mohammad Daliri,
3

Dla programistów vb.net korzystających z nowej wersji AutoMapper (5.x).

Global.asax.vb:

Public Class MvcApplication
    Inherits System.Web.HttpApplication

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

Konfiguracja AutoMapper:

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

Profile:

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

Mapowanie:

Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)
roland
źródło
Próbowałem twojej odpowiedzi, ale pokazuje błąd w tym wierszu: Dim config = New MapperConfiguration (// Rozdzielenie przeciążenia nie powiodło się, ponieważ nie można wywołać dostępnego „New” z tymi argumentami: „Public Overloads Sub New (configurationExpression As MapperConfigurationExpression) Can Can proszę, pomóżcie mi w tym?
Barsan
@barsan: Czy poprawnie skonfigurowałeś wszystkie klasy profili (UserProfile i PostProfile)? Dla mnie działa z wersją Automapper w wersji 5.2.0.
roland
Nowa wersja 6.0 została wydana. Więc to Protected Overrides Sub Configure()jest przestarzałe. Wszystko pozostaje takie samo, ale ta linijka powinna brzmieć:Public Sub New()
roland