Nơi đặt AutoMapper.CreateMaps?


216

Tôi đang sử dụng AutoMappertrong một ASP.NET MVCứng dụng. Tôi đã nói rằng tôi nên di chuyển AutoMapper.CreateMapnơi khác vì họ có rất nhiều chi phí. Tôi không chắc chắn làm thế nào để thiết kế ứng dụng của mình để thực hiện các cuộc gọi này chỉ trong 1 địa điểm.

Tôi có một lớp web, lớp dịch vụ và lớp dữ liệu. Mỗi một dự án của riêng mình. Tôi dùng Ninjectđể DI mọi thứ. Tôi sẽ sử dụng AutoMappertrong cả hai lớp web và dịch vụ.

Vì vậy, thiết lập của bạn cho AutoMapperCreatMap là gì? Bạn đặt nó ở đâu? Bạn gọi cái đó như thế nào?

Câu trả lời:


219

Không quan trọng, miễn là nó là một lớp tĩnh. Đó là tất cả về quy ước .

Quy ước của chúng tôi là mỗi "lớp" (web, dịch vụ, dữ liệu) có một tệp được gọi AutoMapperXConfiguration.cs, với một phương thức duy nhất được gọi Configure(), đó Xlà lớp nào.

Các Configure()phương pháp sau đó gọi privatephương pháp cho từng khu vực.

Đây là một ví dụ về cấu hình tầng web của chúng tôi:

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

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

   // ... etc
}

Chúng tôi tạo một phương thức cho mỗi "tổng hợp" (Người dùng, Bài đăng), để mọi thứ được phân tách độc đáo.

Sau đó, của bạn Global.asax:

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

Nó giống như một "giao diện của các từ" - không thể thực thi nó, nhưng bạn mong đợi nó, vì vậy bạn có thể mã hóa (và cấu trúc lại) nếu cần thiết.

BIÊN TẬP:

Chỉ cần nghĩ rằng tôi hiện đang sử dụng các cấu hình AutoMapper , vì vậy ví dụ trên trở thành:

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

Sạch sẽ hơn / mạnh mẽ hơn.


2
@ AliRızaAdıyahşi Cả hai dự án nên có một tệp ánh xạ. Core nên có AutoMapperCoreConfiguration và UI nên có AutoMapperWebConfiguration. Cấu hình web nên thêm các cấu hình từ cấu hình Core.
RPM1984

7
Việc gọi Mapper.Initializetrong mỗi lớp Cấu hình có ghi đè lên các cấu hình trước đó được thêm không? Nếu vậy, những gì nên được sử dụng thay vì Khởi tạo?
Cody

4
Điều này có làm cho dự án API web của bạn có tham chiếu đến các lớp dịch vụ và miền của bạn không?
Chazt3n

3
Nếu tôi có Web -> Dịch vụ -> BLL -> DAL. Các thực thể của tôi là trong DAL của tôi. Tôi không muốn đưa ra một tham chiếu đến DAL của mình từ web hoặc Dịch vụ. Làm thế nào để tôi khởi tạo nó?
Vyache

19
Kể từ AutoMapper 4.2 Mapper.CreateMap()hiện đã bị lỗi. '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.'. Làm thế nào bạn sẽ cập nhật ví dụ của bạn để phù hợp với các yêu cầu mới?
ᴍᴀᴛᴛ

34

Bạn thực sự có thể đặt nó ở bất cứ đâu miễn là dự án web của bạn tham chiếu tổ hợp mà nó đang ở. Trong tình huống của bạn, tôi sẽ đặt nó trong lớp dịch vụ vì lớp web và lớp dịch vụ có thể truy cập được và sau đó nếu bạn quyết định làm một ứng dụng giao diện điều khiển hoặc bạn đang thực hiện một dự án thử nghiệm đơn vị, cấu hình ánh xạ cũng sẽ có sẵn từ các dự án đó.

Trong Global.asax của bạn, sau đó bạn sẽ gọi phương thức đặt tất cả các bản đồ của bạn. Xem bên dưới:

Tệp AutoMapperBootStrapper.cs

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


     }
}

Global.asax khi bắt đầu ứng dụng

chỉ cần gọi

AutoMapperBootStrapper.BootStrap();

Bây giờ một số người sẽ tranh luận chống lại phương pháp này vi phạm một số nguyên tắc RẮN, mà họ có lý lẽ hợp lệ. Ở đây họ dành cho việc đọc.

Cấu hình Automapper trong Bootstrapper vi phạm Nguyên tắc đóng mở?


13
Điều này. Mỗi bước hướng tới kiến ​​trúc "Hardcore" thích hợp dường như liên quan đến mã nhiều hơn theo cấp số nhân. Điều này thật dễ dàng; nó sẽ đủ cho 99,9% các lập trình viên ngoài kia; và đồng nghiệp của bạn sẽ đánh giá cao sự đơn giản. Vâng, mọi người nên đọc vấn đề liên quan đến nguyên tắc Mở-Đóng, nhưng mọi người cũng nên nghĩ về sự đánh đổi.
anon

Bạn đã tạo lớp AutoMapperBootStrapper ở đâu?
user6395764

16

Cập nhật: Cách tiếp cận được đăng ở đây không còn giá trị như SelfProfilerđã bị xóa kể từ AutoMapper v2.

Tôi sẽ có một cách tiếp cận tương tự như Thoai. Nhưng tôi sẽ sử dụng SelfProfiler<>lớp tích hợp để xử lý các bản đồ, sau đó sử dụng Mapper.SelfConfigurehàm để khởi tạo.

Sử dụng đối tượng này làm nguồn:

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

Và đây là đích đến:

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

Bạn có thể tạo các hồ sơ này:

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

Để khởi tạo trong ứng dụng của bạn, hãy tạo lớp này

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

Thêm dòng này vào tệp global.asax.cs của bạn: AutoMapperConfiguration.Initialize()

Bây giờ bạn có thể đặt các lớp ánh xạ của bạn ở đó chúng có ý nghĩa với bạn và không phải lo lắng về một lớp ánh xạ nguyên khối.


3
Chỉ cần FYI, lớp SelfProfiler đã biến mất kể từ Automapper v2.
Matt Honeycutt

15

Đối với những người bạn tuân thủ những điều sau đây:

  1. sử dụng một thùng chứa ioc
  2. không muốn mở đóng cửa cho điều này
  3. không giống như một tập tin cấu hình nguyên khối

Tôi đã thực hiện kết hợp giữa các cấu hình và tận dụng thùng chứa ioc của mình:

Cấu hình 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;
        }));
    }
}

Ví dụ cấu hình:

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

Ví dụ sử dụng:

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

Sự đánh đổi là bạn phải tham chiếu Mapper bằng giao diện IMapsEngine thay vì Mapper tĩnh, nhưng đó là một quy ước mà tôi có thể sống cùng.


14

Tất cả các giải pháp trên cung cấp một phương thức tĩnh để gọi (từ app_start hoặc bất kỳ nơi nào) mà nó sẽ gọi các phương thức khác để định cấu hình các phần của cấu hình ánh xạ. Nhưng, nếu bạn có một ứng dụng mô-đun, các mô-đun đó có thể cắm vào và ra khỏi ứng dụng bất cứ lúc nào, các giải pháp này không hoạt động. Tôi đề nghị sử dụng WebActivatorthư viện có thể đăng ký một số phương thức để chạy app_pre_startapp_post_startbất kỳ nơi nào:

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

Bạn có thể cài đặt WebActivatorqua NuGet.


2
Gần đây tôi đã đi đến kết luận tương tự. Nó giữ mã tạo bản đồ của bạn gần với mã tiêu thụ nó. Phương pháp này làm cho bộ điều khiển MVC dễ bảo trì hơn nhiều.
mfras3r

Làm thế nào để tôi bắt đầu nó ở bất cứ đâu, bạn có thể cung cấp một ví dụ? Liên kết blog của bạn không hoạt động ...
Vyache

1
@Vyache nó khá rõ ràng! trong MyModule1dự án (hoặc bất kể tên dự án của bạn là gì) chỉ cần tạo một lớp có tên InitMapInModule1và đặt mã bên trong tệp; đối với các mô-đun khác, làm tương tự.
rir amiry

Gotcha, tôi thực sự đã thử nó. Tôi đã thêm WebActivator từ Nuget vào thư viện Lớp của tôi (DAL) và tạo một lớp AutoMapperDalConfiguration tĩnh trong đó tôi đã tạo triển khai @ RPM1984 để định cấu hình và Khởi tạo bản đồ. Tôi không sử dụng hồ sơ thông qua. Cảm ơn bạn.
Vyache

10

Ngoài câu trả lời tốt nhất, một cách hay là sử dụng Autofac IoC tự do để thêm một số tự động hóa. Với điều này, bạn chỉ cần xác định hồ sơ của bạn bất kể khởi xướng.

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

            }

        }
    }

và gọi dòng này trong Application_Startphương thức:

MapperConfig.Configure();

Đoạn mã trên tìm thấy tất cả các lớp con Hồ sơ và tự động khởi tạo chúng.


7

Đặt tất cả logic ánh xạ vào 1 vị trí không phải là một thực hành tốt đối với tôi. Bởi vì lớp ánh xạ sẽ cực kỳ lớn và rất khó để duy trì.

Tôi khuyên bạn nên đặt các công cụ ánh xạ cùng với lớp ViewModel trong cùng một tệp cs. Bạn có thể dễ dàng điều hướng đến định nghĩa ánh xạ bạn muốn theo quy ước này. Hơn nữa, trong khi tạo lớp ánh xạ, bạn có thể tham chiếu đến các thuộc tính ViewModel nhanh hơn vì chúng nằm trong cùng một tệp.

Vì vậy, lớp mô hình xem của bạn sẽ trông như sau:

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
Bạn gọi cái đó như thế nào?
Shawn Mclean

1
Tôi sẽ tuân theo một lớp cho mỗi quy tắc tệp: stackoverflow.com/q/2434990/1158845
Umair

Linh hồn tương tự được mô tả trong blog của Velir Tổ chức Cấu hình bản đồ của AutoMapper trong MVC
xmedeko

5

Từ phiên bản mới của AutoMapper sử dụng phương thức tĩnh Mapper.Map () không được dùng nữa. Vì vậy, bạn có thể thêm MapperConfiguration dưới dạng thuộc tính tĩnh vào MvcApplication (Global.asax.cs) và sử dụng nó để tạo phiên bản của Mapper.

Ứng dụng_Start

public class MapperConfig
{
    public static MapperConfiguration MapperConfiguration()
    {
        return new MapperConfiguration(_ =>
        {
            _.AddProfile(new FileProfile());
            _.AddProfile(new ChartProfile());
        });
    }
}

Toàn cầu.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    internal static MapperConfiguration MapperConfiguration { get; private set; }

    protected void Application_Start()
    {
        MapperConfiguration = MapperConfig.MapperConfiguration();
        ...
    }
}

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

Đối với những người đang (mất) sử dụng:

  • WebAPI 2
  • Đơn giản 3.1
  • AutoMapper 4.2.1 (Có hồ sơ)

Đây là cách tôi quản lý tích hợp AutoMapper theo " cách mới ". Ngoài ra, rất lớn nhờ câu trả lời này (và câu hỏi)

1 - Tạo một thư mục trong dự án WebAPI có tên "ProfileMappers". Trong thư mục này, tôi đặt tất cả các lớp hồ sơ tạo bản đồ của mình:

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

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

2 - Trong App_Start của tôi, tôi có một SimpleInjectionApiInitializer để cấu hình bộ chứa SimpleInjection của tôi:

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 - Sau đó, trong bộ điều khiển của bạn chỉ cần tiêm như giao diện IMapper:

private readonly IMapper mapper;

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

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);

Với một chút tinh chỉnh cho một số chi tiết cụ thể, cách tiếp cận này cũng hoạt động xuất sắc với MVC - cảm ơn anh chàng!
Nick Coad

vui lòng thêm một ví dụ demo trong github
Mohammad Daliri

3

Dành cho lập trình viên vb.net sử dụng Phiên bản mới (5.x) của AutoMapper.

Toàn cầu.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

Hồ sơ:

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

Ánh xạ:

Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)

Tôi đã thử câu trả lời của bạn nhưng nó đang hiển thị lỗi trên dòng này: Dim config = New MapperConfiguration (// Quá tải độ phân giải không thành công vì không thể truy cập 'New' với các đối số sau: 'Public Overloads Sub New (configureExpression As MapperConfigurationExpression) bạn vui lòng giúp tôi về điều đó?
baran

@barsan: Bạn đã cấu hình chính xác tất cả các lớp hồ sơ (UserProfile và PostProfile) chưa? Đối với tôi, nó hoạt động với phiên bản Automapper 5.2.0.
roland

Phiên bản 6.0 mới được phát hành. Vì vậy, Protected Overrides Sub Configure()là không tán thành. Mọi thứ vẫn giữ nguyên nhưng dòng này phải là:Public Sub New()
roland
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.