在哪里放置AutoMapper.CreateMaps?


216

我正在使用 AutoMapperASP.NET MVC应用程序中使用。有人告诉我,我应该将AutoMapper.CreateMap其他位置移到其他地方,因为它们的开销很大。我不太确定如何设计我的应用程序以将这些调用放在一个地方。

我有一个Web层,服务层和一个数据层。每个项目都有自己的项目。我用Ninject一切都去DI。我将AutoMapper在Web和服务层中利用。

那么,您对AutoMapper“ CreateMap”的设置是什么?你放在哪里?你怎么称呼它?

Answers:


219

没关系,只要它是静态类即可。一切都与惯例有关。

我们的约定是,每个“层”(Web,服务,数据)都有一个名为的文件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

它有点像“单词的界面”-无法强制执行,但是您希望得到它,因此可以在必要时进行编码(和重构)。

编辑:

只是以为我提到我现在使用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两个项目都应有一个映射文件。核心应具有AutoMapperCoreConfiguration,而UI应具有AutoMapperWebConfiguration。Web配置应从Core配置添加配置文件。
RPM1984 '11

7
Mapper.Initialize在每个Configuration类中调用是否会覆盖之前添加的配置文件?如果是这样,应该使用什么代替“初始化”?
科迪2014年

4
这是否会使您的Web API项目引用您的服务和域层?
Chazt3n 2015年

3
如果我有Web->服务-> BLL-> DAL。我的实体在我的DAL中。我不想从Web或服务中引用我的DAL。如何初始化?
Vyache

19
从AutoMapper 4.2开始Mapper.CreateMap(),现在已过时。 '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.'。您将如何更新示例以符合新要求?
2016年

34

只要您的Web项目引用了它所在的程序集,就可以将它放到任何地方。根据您的情况,我会将其放在服务层中,因为Web层和服务层将可以访问它,如果您决定做一个控制台应用程序,或者您正在做一个单元测试项目,映射配置也可以从那些项目中获得。

然后,在Global.asax中,您将调用设置所有地图的方法。见下文:

文件AutoMapperBootStrapper.cs

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


     }
}

应用程序启动时的Global.asax

只是打电话

AutoMapperBootStrapper.BootStrap();

现在,有些人会反对这种方法违反了一些SOLID原则,这些原则具有有效的论据。他们在这里供阅读。

在Bootstrapper中配置Automapper是否违反了Open-Closed Principle?


13
这个。迈向正确的“硬核”架构的每一步似乎都涉及成倍的代码。这很简单; 它足以满足99.9%的编码人员的需求;您的同事会喜欢它的简单性。是的,每个人都应该阅读有关开放-封闭原则的问题,但每个人也都应该考虑权衡问题。
匿名

您在哪里创建了AutoMapperBootStrapper类?
user6395764 '18

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
仅供参考,自Automapper v2起,SelfProfiler类就消失了。
Matt Honeycutt 2014年

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

折衷方案是您必须通过IMappingEngine接口而不是静态Mapper来引用Mapper,但这是我可以接受的约定。


14

上述所有解决方案都提供了一个静态方法来调用(从app_start或任何位置),该静态方法应调用其他方法来配置映射配置的各个部分。但是,如果您具有模块化应用程序,则这些模块可以随时插拔应用程序,这些解决方案将无法正常工作。我建议使用WebActivator可以注册一些方法app_pre_startapp_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 2012年

我如何在任何地方启动它,您能举个例子吗?您的博客链接无效...
Vyache 2015年

1
@Vyache很清楚!在MyModule1项目(或项目的名称)中,只需创建一个名为的类并将InitMapInModule1代码放入文件中即可;对于其他模块,请执行相同操作。
ravy amiry 2015年

知道,我实际上只是尝试过。我将WebActivator从Nuget添加到我的类库(DAL)中,并在其中创建了一个静态AutoMapperDalConfiguration类,并在其中创建了@ RPM1984实现以配置和初始化地图。我没有使用个人资料。谢谢。
Vyache

10

除了最佳答案外,一种好方法是使用Autofac IoC库添加一些自动化功能。这样,您可以定义个人资料,而无需进行任何初始化。

   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
你怎么称呼它?
肖恩·麦克林

1
我会遵循每个文件规则的一个类:stackoverflow.com/q/2434990/1158845
Umair

类似soultion在Velir的博客描述组织AutoMapper的地图配置在MVC
xmedeko

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

对于使用新版AutoMapper(5.x)的vb.net程序员。

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 = New MapperConfiguration(//重载解析失败,因为无法使用以下参数调用可访问的“ New”:'Public Overloads Sub New(configurationExpression as MapperConfigurationExpression)可以你能帮我吗?
baran

@barsan:您是否正确配置了所有配置文件类(UserProfile和PostProfile)?对我来说,它适用于Automapper 5.2.0版。
罗兰

新版本6.0已发布。因此Protected Overrides Sub Configure()已弃用。一切保持不变,但此行应为:Public Sub New()
roland
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.