Gdzie umieścić AutoMapper.CreateMaps?

Używam AutoMapper w ASP.NET MVC aplikacji. Powiedziano mi, że powinienem przenieść AutoMapper.CreateMap gdzie indziej, ponieważ mają dużo kosztów. Nie jestem zbyt pewien, jak zaprojektować moją aplikację, aby umieścić te połączenia w zaledwie 1 miejscu.

Mam warstwę internetową, warstwę usług i warstwę danych. Każdy projekt własny. Używam Ninject do wszystkiego. Wykorzystam AutoMapper zarówno w warstwie web jak i service.

Więc jakie są Twoje ustawienia dla AutoMapper'S CreateMap? Gdzie go położysz? Jak to się nazywa?

Author: Leniel Maccaferri, 2011-07-26

10 answers

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

Nasza konwencja jest taka, że każda "warstwa" (web, services, data) ma jeden plik o nazwie AutoMapperXConfiguration.cs, z jedną metodą o nazwie Configure(), Gdzie X jest warstwą.

Metoda Configure() następnie wywołuje metody private dla każdego obszaru.

Oto przykład naszej konfiguracji warstwy sieci web:
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" (User, Post), więc rzeczy są oddzielone ładnie.

Wtedy twoje Global.asax:

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

To coś w rodzaju "interfejsu słów" - nie można go wymusić, ale oczekujesz go, więc możesz kodować (i refaktorować) w razie potrzeby.

EDIT:

Pomyślałem, że wspomnę, że teraz używam profili AutoMapper , więc powyższy przykład staje się:

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 czystsze/bardziej wytrzymałe.
 210
Author: RPM1984,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-18 08:19:08

Możesz umieścić go w dowolnym miejscu, o ile twój projekt internetowy odwołuje się do zestawu, w którym jest. W twojej sytuacji umieściłbym go w warstwie usługowej, ponieważ będzie on dostępny przez warstwę internetową i warstwę usługową, a później, jeśli zdecydujesz się na aplikację konsolową lub wykonujesz projekt testu jednostkowego, konfiguracja mapowania będzie dostępna również z tych projektów.

W Twojej globalnej.asax następnie wywołasz 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...


     }
}

Globalny.asax przy uruchomieniu aplikacji

Po Prostu zadzwoń

AutoMapperBootStrapper.BootStrap();

Teraz niektórzy ludzie będą argumentować przeciwko tej metodzie narusza pewne solidne zasady, które mają ważne argumenty. Oto one do czytania.

Konfigurowanie Automapp w Bootstrapperze narusza zasadę Open-Closed?

 31
Author: Brett Allred,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:55

Update: podejście opublikowane tutaj nie jest już ważne, ponieważ SelfProfiler zostało usunięte z AutoMapper v2.

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

Użycie 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);
    }
}

I te jako miejsce przeznaczenia:

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 tworzyć 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 ten Klasa

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

Dodaj tę linię do swojego globalnego.asax.plik 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.

 15
Author: codeprogression,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-24 17:00:19

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

  1. używanie kontenera ioc
  2. don ' t like to break open closed for this
  3. nie lubię monolitycznego pliku konfiguracyjnego

Zrobiłem kombinację między profilami i wykorzystałem 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łać się do Mapera przez interfejs IMappingEngine zamiast statycznego Mapera, ale z takim konwentem mogę żyć.

 15
Author: Marius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-24 17:01:23

Wszystkie powyższe rozwiązania zapewniają statyczną metodę wywołania (z app_start lub dowolnego miejsca), która powinna wywoływać inne metody w celu skonfigurowania części mapowania-konfiguracji. Jeśli jednak masz modułową aplikację, która może w dowolnym momencie zostać podłączona i wyłączona z aplikacji, rozwiązania te nie działają. Sugeruję użycie biblioteki WebActivator, która może zarejestrować niektóre metody do uruchomienia na app_pre_start i app_post_start w dowolnym miejscu:

// 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ć WebActivator poprzez NuGet.

 14
Author: javad amiry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-14 16:50:59

Oprócz najlepszej odpowiedzi, dobrym sposobem jest użycie Autofac IoC liberary, aby dodać trochę automatyzacji. Dzięki temu możesz po prostu zdefiniować swoje profile 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 metodzie Application_Start:

MapperConfig.Configure();

Powyższy kod znajduje wszystkieProfile podklasy i inicjuje je automatycznie.

 10
Author: Mahmoud Moravej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-27 13:39:25

Umieszczenie całej logiki mapowania w jednym miejscu nie jest dla mnie dobrą praktyką. Ponieważ Klasa mapowania będzie bardzo duża i bardzo trudna do utrzymania.

Polecam umieścić mapowanie razem z klasą ViewModel w tym samym pliku cs. Możesz łatwo przejść do definicji mapowania zgodnie z tą konwencją. Co więcej, podczas tworzenia klasy mapowania można szybciej odwoływać się do właściwości ViewModel, ponieważ znajdują się one w tym samym pliku.

Więc twój pogląd Klasa modelu 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>();
    }
}
 7
Author: Van Thoai Nguyen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-27 04:23:58

Z nowej wersji Automapp przy użyciu statycznej metody Mapper.Map() jest przestarzała. Możesz więc dodać MapperConfiguration jako właściwość statyczną do MvcApplication (Global.asax.cs) i używać go do tworzenia instancji 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

 5
Author: Andrey Burykin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-20 11:56:26

Dla vb.net Programiści korzystający z nowej wersji (5.x) Automapp.

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

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)
 3
Author: roland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-21 11:27:41

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

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1 (Z Profilami)

Oto jak udało mi się zintegrować AutoMapper w " new way". Również, a Ogromne dzięki temu odpowiedź (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, I posiadaj 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-Start.cs

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

4-Następnie w 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);
 2
Author: jpgrassi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:35