ตำแหน่งที่จะวาง AutoMapper.CreateMaps?


216

ฉันใช้AutoMapperในASP.NET MVCการประยุกต์ใช้ ฉันบอกว่าฉันควรย้ายที่AutoMapper.CreateMapอื่นเพราะมีค่าใช้จ่ายมากมาย ฉันไม่แน่ใจว่าจะออกแบบแอปพลิเคชันของฉันให้วางสายเหล่านี้ได้ในที่เดียว

ฉันมีชั้นเว็บชั้นบริการและชั้นข้อมูล แต่ละโครงการของตัวเอง ฉันใช้NinjectDI ทุกอย่าง ฉันจะใช้ประโยชน์จาก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
}

เราสร้างวิธีการสำหรับ "สรุปรวม" (ผู้ใช้โพสต์) แต่ละรายการดังนั้นสิ่งต่างๆจึงถูกแยกออกมาอย่างสวยงาม

จากนั้นGlobal.asax:

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

มันเป็นเหมือน "อินเทอร์เฟซของคำ" - ไม่สามารถบังคับได้ แต่คุณคาดหวังดังนั้นคุณสามารถโค้ด (และ refactor) หากจำเป็น

แก้ไข:

แค่คิดว่าฉันจะพูดถึงว่าตอนนี้ฉันใช้โปรไฟล์ 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 และ UI ควรมี AutoMapperWebConfiguration การกำหนดค่าเว็บควรเพิ่มโปรไฟล์จากการกำหนดค่า Core
RPM1984

7
การโทรMapper.Initializeในแต่ละคลาสการกำหนดค่าจะเขียนทับโปรไฟล์ก่อนหน้านี้หรือไม่ ถ้าเป็นเช่นนั้นสิ่งที่ควรใช้แทนการเริ่มต้น?
Cody

4
สิ่งนี้ทำให้โครงการเว็บ API ของคุณมีการอ้างอิงถึงบริการและเลเยอร์โดเมนของคุณหรือไม่
Chazt3n

3
ถ้าฉันมีเว็บ -> บริการ -> BLL -> DAL เอนทิตีของฉันอยู่ใน DAL ของฉัน ฉันไม่ต้องการให้การอ้างอิงถึง DAL ของฉันจากเว็บหรือบริการ ฉันจะเริ่มต้นได้อย่างไร
Vyache

19
ในฐานะของMapper.CreateMap()AutoMapper 4.2 ตอนนี้ถูกปฏิเสธ '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();

ตอนนี้บางคนจะโต้เถียงกับวิธีนี้ละเมิดหลักการบางอย่างที่พวกเขามีข้อโต้แย้งที่ถูกต้อง ที่นี่พวกเขามีไว้สำหรับอ่าน

การกำหนดค่า Automapper ใน Bootstrapper เป็นการละเมิดหลักการ Open-Closed หรือไม่?


13
นี้. ทุกย่างก้าวสู่สถาปัตยกรรม "ฮาร์ดคอร์" ที่เหมาะสมดูเหมือนว่าจะเกี่ยวข้องกับรหัสที่เพิ่มขึ้นแทน มันง่ายมาก มันจะพอเพียงสำหรับ 99.9% ของ coders ที่นั่น และเพื่อนร่วมงานของคุณจะประทับใจกับความเรียบง่าย ใช่ทุกคนควรอ่านปัญหาเกี่ยวกับหลักการ Open-Closed แต่ทุกคนควรคิดถึงการแลกเปลี่ยน
anon

คุณสร้างคลาส AutoMapperBootStrapper ที่ไหน
user6395764

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
Matt Honeycutt

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 แบบคงที่ แต่นั่นเป็นแบบแผนที่ฉันสามารถมีชีวิตอยู่ได้


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

Gotcha ที่จริงฉันแค่ลองมัน ฉันเพิ่ม WebActivator จาก Nuget ใน Class Library (DAL) ของฉันและสร้างคลาส AutoMapperDalConfiguration แบบคงที่ในนั้นฉันได้สร้างการใช้งาน @ RPM1984 เพื่อกำหนดค่าและเริ่มต้นแผนที่ ฉันไม่ได้ใช้โปรไฟล์ผ่าน ขอบคุณ.
Vyache

10

นอกจากคำตอบที่ดีที่สุดแล้ววิธีที่ดีคือการใช้Autofac IoC liberary เพื่อเพิ่มระบบอัตโนมัติ ด้วยวิธีนี้คุณเพียงแค่กำหนดโปรไฟล์ของคุณโดยไม่คำนึงถึงการเริ่มต้น

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

โค้ดด้านบนค้นหาคลาสย่อยProfileทั้งหมดและเริ่มต้นโดยอัตโนมัติ


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
คุณจะเรียกมันว่าอย่างไร?
Shawn Mclean

1
ฉันจะติดตามหนึ่งคลาสต่อกฎไฟล์: stackoverflow.com/q/2434990/1158845
Umair

soultion ที่คล้ายกันอธิบายไว้ในบล็อกของ Velir การจัดการการกำหนดค่าแผนที่ของ
AutoMapper

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 ที่ใช้ AutoMapper เวอร์ชั่นใหม่ (5.x)

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 = MapperConfiguration ใหม่ (// การแก้ปัญหาการโอเวอร์โหลดล้มเหลวเนื่องจากไม่สามารถเรียกใช้ 'ใหม่' ได้ด้วยอาร์กิวเมนต์เหล่านี้: 'Public Overloads Sub ใหม่ (configuration Expression As MapperConfigurationExpression) คุณช่วยฉันในเรื่องนั้นได้ไหม
บาซาน

@barsan: คุณกำหนดค่าคลาสโปรไฟล์ทั้งหมดอย่างถูกต้อง (UserProfile และ PostProfile) หรือไม่ สำหรับฉันมันใช้งานได้กับ Automapper เวอร์ชั่น 5.2.0
roland

เวอร์ชั่นใหม่ 6.0 เปิดตัวแล้ว ดังนั้นProtected Overrides Sub Configure()เลิกใช้แล้ว ทุกอย่างยังคงเหมือนเดิม แต่บรรทัดนี้ควรเป็น:Public Sub New()
roland
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.