Di mana menempatkan AutoMapper.CreateMaps?

216

Saya menggunakan AutoMapperdalam ASP.NET MVCaplikasi. Saya diberitahu bahwa saya harus pindah tempat AutoMapper.CreateMaplain karena mereka memiliki banyak overhead. Saya tidak terlalu yakin bagaimana merancang aplikasi saya untuk melakukan panggilan ini hanya di 1 tempat.

Saya memiliki lapisan web, lapisan layanan, dan lapisan data. Masing-masing proyek sendiri. Saya gunakan Ninjectuntuk DI segalanya. Saya akan menggunakan AutoMapperlapisan web dan layanan.

Jadi apa pengaturan Anda untuk AutoMapperCreateMap? Di mana Anda meletakkannya? Bagaimana Anda menyebutnya?

Shawn Mclean
sumber

Jawaban:

219

Tidak masalah, asalkan itu kelas statis. Ini semua tentang konvensi .

Konvensi kami adalah bahwa setiap "lapisan" (web, layanan, data) memiliki satu file yang disebut AutoMapperXConfiguration.cs, dengan metode tunggal yang disebut Configure(), di mana Xlayer tersebut.

The Configure()Metode kemudian memanggil privatemetode untuk masing-masing daerah.

Berikut ini contoh konfigurasi tingkat web kami:

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

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

   // ... etc
}

Kami membuat metode untuk setiap "agregat" (Pengguna, Posting), sehingga semuanya dipisahkan dengan baik.

Maka Anda Global.asax:

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

Ini seperti "antarmuka kata-kata" - tidak dapat menegakkannya, tetapi Anda mengharapkannya, sehingga Anda dapat membuat kode (dan refactor) jika perlu.

EDIT:

Hanya berpikir saya akan menyebutkan bahwa saya sekarang menggunakan profil AutoMapper , jadi contoh di atas menjadi:

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

Jauh lebih bersih / lebih kuat.

RPM1984
sumber
2
@ AliRızaAdıyahşi Kedua proyek harus memiliki file pemetaan. Core harus memiliki AutoMapperCoreConfiguration, dan UI harus memiliki AutoMapperWebConfiguration. Konfigurasi web harus menambahkan profil dari konfigurasi Core.
RPM1984
7
Apakah memanggil Mapper.Initializedi setiap kelas Konfigurasi menimpa profil sebelumnya yang ditambahkan? Jika demikian, apa yang harus digunakan alih-alih Inisialisasi?
Cody
4
Bukankah ini membuat proyek API web Anda memiliki referensi ke layanan Anda dan lapisan domain?
Chazt3n
3
Jika saya memiliki Web -> Layanan -> BLL -> DAL. Entitas saya ada di DAL saya. Saya tidak ingin memberikan referensi ke DAL saya baik dari web atau Layanan. Bagaimana cara menginisialisasi itu?
Vyache
19
Pada AutoMapper 4.2 Mapper.CreateMap()sekarang dihapus. '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.'. Bagaimana Anda memperbarui contoh Anda agar sesuai dengan persyaratan baru?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
34

Anda dapat benar-benar meletakkannya di mana saja selama proyek web Anda merujuk perakitan yang ada di dalamnya. Dalam situasi Anda, saya akan meletakkannya di lapisan layanan karena akan dapat diakses oleh lapisan web dan lapisan layanan dan kemudian jika Anda memutuskan untuk lakukan aplikasi konsol atau Anda sedang melakukan proyek uji unit konfigurasi pemetaan akan tersedia dari proyek-proyek tersebut juga.

Di Global.asax Anda, Anda kemudian akan memanggil metode yang mengatur semua peta Anda. Lihat di bawah:

File AutoMapperBootStrapper.cs

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


     }
}

Global.asax pada awal aplikasi

panggil saja

AutoMapperBootStrapper.BootStrap();

Sekarang beberapa orang akan menentang metode ini melanggar beberapa prinsip SOLID, yang mereka miliki argumen yang valid. Inilah mereka untuk dibaca.

Mengkonfigurasi Automapper di Bootstrapper melanggar Prinsip Terbuka-Tertutup?

Brett Allred
sumber
13
Ini. Setiap langkah menuju arsitektur "hardcore" yang tepat tampaknya melibatkan lebih banyak kode secara eksponensial. Ini mudah; itu sudah cukup untuk 99,9% dari coders di luar sana; dan rekan kerja Anda akan menghargai kesederhanaan. Ya, setiap orang harus membaca masalah tentang prinsip Terbuka-Tertutup, tetapi semua orang juga harus memikirkan tradeoff.
anon
di mana Anda telah membuat kelas AutoMapperBootStrapper?
user6395764
16

Pembaruan: Pendekatan yang diposting di sini tidak lagi valid karena SelfProfilertelah dihapus pada AutoMapper v2.

Saya akan mengambil pendekatan yang sama dengan Thoai. Tapi saya akan menggunakan SelfProfiler<>kelas bawaan untuk menangani peta, kemudian menggunakan Mapper.SelfConfigurefungsi untuk menginisialisasi.

Menggunakan objek ini sebagai sumber:

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

Dan ini sebagai tujuannya:

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

Anda dapat membuat profil ini:

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

Untuk menginisialisasi aplikasi Anda, buat kelas ini

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

Tambahkan baris ini ke file global.asax.cs Anda: AutoMapperConfiguration.Initialize()

Sekarang Anda dapat menempatkan kelas pemetaan Anda di mana mereka masuk akal bagi Anda dan tidak khawatir tentang satu kelas pemetaan monolitik.

agresi kode
sumber
3
Hanya FYI, kelas SelfProfiler hilang sejak Automapper v2.
Matt Honeycutt
15

Bagi Anda yang mematuhi hal-hal berikut:

  1. menggunakan wadah ioc
  2. tidak suka membuka tutup untuk ini
  3. tidak suka file konfigurasi monolitik

Saya melakukan kombo antara profil dan meningkatkan wadah ioc saya:

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

Contoh konfigurasi:

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

Contoh penggunaan:

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

Imbalannya adalah Anda harus mereferensikan Mapper dengan antarmuka IMappingEngine alih-alih Mapper statis, tapi itu adalah konvensi yang bisa saya jalani.

Marius
sumber
14

Semua solusi di atas menyediakan metode statis untuk memanggil (dari app_start atau di mana saja) bahwa itu harus memanggil metode lain untuk mengonfigurasi bagian pemetaan-konfigurasi. Tetapi, jika Anda memiliki aplikasi modular, modul-modul itu dapat masuk dan keluar dari aplikasi kapan saja, solusi ini tidak berfungsi. Saya sarankan menggunakan WebActivatorperpustakaan yang dapat mendaftarkan beberapa metode untuk dijalankan app_pre_startdan di app_post_startmana saja:

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

Anda dapat menginstal WebActivatormelalui NuGet.

Amiry ravy
sumber
2
Saya baru-baru ini sampai pada kesimpulan yang sama. Itu membuat kode pembuatan peta Anda dekat dengan kode yang memakannya. Metode ini membuat pengontrol MVC jauh lebih mudah dikelola.
mfras3r
Bagaimana saya memulainya di mana saja, dapatkah Anda memberikan contoh? Tautan blog Anda tidak berfungsi ...
Vyache
1
@ Vyache sudah cukup jelas! dalam MyModule1proyek (atau apa pun nama proyek Anda), buat kelas bernama InitMapInModule1dan letakkan kode di dalam file; untuk modul lain, lakukan hal yang sama.
ravy amiry
Gotcha, saya sebenarnya baru mencobanya. Saya menambahkan WebActivator dari Nuget ke perpustakaan Kelas saya (DAL) dan membuat kelas AutoMapperDalConfiguration statis di sana saya membuat implementasi @ RPM1984 untuk mengkonfigurasi dan menginisialisasi peta. Saya tidak menggunakan profil melalui. Terima kasih.
Vyache
10

Selain jawaban terbaik, cara yang baik adalah menggunakan Autofac IoC liberary untuk menambahkan beberapa otomatisasi. Dengan ini, Anda cukup menentukan profil Anda terlepas dari inisiasi.

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

            }

        }
    }

dan memanggil baris ini dalam Application_Startmetode:

MapperConfig.Configure();

Kode di atas menemukan semua sub kelas Profil dan memulai secara otomatis.

Mahmoud Moravej
sumber
7

Menempatkan semua logika pemetaan di 1 lokasi bukanlah praktik yang baik bagi saya. Karena kelas pemetaan akan sangat besar dan sangat sulit untuk dipelihara.

Saya sarankan menempatkan barang pemetaan bersama dengan kelas ViewModel dalam file cs yang sama. Anda dapat dengan mudah menavigasi ke definisi pemetaan yang Anda inginkan setelah konvensi ini. Selain itu, saat membuat kelas pemetaan, Anda bisa merujuk ke properti ViewModel lebih cepat karena mereka berada di file yang sama.

Jadi kelas model tampilan Anda akan terlihat seperti:

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
sumber
9
Bagaimana Anda menyebutnya?
Shawn Mclean
1
Saya akan mengikuti satu kelas per aturan file: stackoverflow.com/q/2434990/1158845
Umair
Soultion serupa dijelaskan di blog Velir's Mengatur Konfigurasi Peta AutoMapper di MVC
xmedeko
5

Dari versi baru AutoMapper menggunakan metode statis Mapper.Map () sudah usang. Jadi Anda bisa menambahkan MapperConfiguration sebagai properti statis ke MvcApplication (Global.asax.cs) dan menggunakannya untuk membuat instance 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

Andrey Burykin
sumber
3

Bagi mereka yang (hilang) menggunakan:

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1 (Dengan Profil)

Inilah cara saya berhasil mengintegrasikan AutoMapper dalam " cara baru ". Juga, besar berkat ini jawaban (dan pertanyaan)

1 - Membuat folder di proyek WebAPI yang disebut "ProfileMappers". Dalam folder ini saya menempatkan semua kelas profil saya yang membuat pemetaan saya:

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

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

2 - Di App_Start saya, saya memiliki SimpleInjectorApiInitializer yang mengkonfigurasi wadah SimpleInjector saya:

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 - Kemudian, di controller Anda hanya menyuntikkan seperti biasanya antarmuka IMapper:

private readonly IMapper mapper;

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

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
jpgrassi
sumber
Dengan sedikit penyesuaian pada beberapa hal spesifik, pendekatan ini bekerja sangat baik dengan MVC - terima kasih kawan!
Nick Coad
tolong tambahkan contoh demo di github
Mohammad Daliri
3

Untuk programmer vb.net menggunakan Versi (5.x) AutoMapper yang baru.

Global.asax.vb:

Public Class MvcApplication
    Inherits System.Web.HttpApplication

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

Konfigurasi 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

Profil:

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

Pemetaan:

Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)
roland
sumber
Saya sudah mencoba jawaban Anda tetapi itu menunjukkan kesalahan pada baris ini: Dim config = New MapperConfiguration (// Resolusi overload gagal karena tidak dapat diakses 'Baru' dapat dipanggil dengan argumen ini: 'Public Overloads Sub New (konfigurasiExpression As MapperConfigurationExpression) Dapat tolong bantu saya dalam hal itu?
Barsan
@barsan: Sudahkah Anda mengkonfigurasi semua kelas profil dengan benar (UserProfile dan PostProfile)? Bagi saya ini berfungsi dengan Automapper versi 5.2.0.
roland
Versi 6.0 baru dirilis. Jadi Protected Overrides Sub Configure()sudah usang. Semuanya tetap sama tetapi baris ini harus:Public Sub New()
roland