在哪里放置AutoMapper.CreateMaps?

时间:2011-07-26 04:25:02

标签: asp.net-mvc design-patterns configuration automapper

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

我有一个网络层,服务层和一个数据层。每个项目都有自己的。我使用Ninject来DI所有内容。我将在Web和服务层中使用AutoMapper

那么你对AutoMapper的CreateMap有什么设置?你把它放在哪里?你怎么称呼它?

10 个答案:

答案 0 :(得分:215)

无关紧要,只要它是静态类。这完全是关于惯例

我们的约定是每个“层”(网络,服务,数据)都有一个名为AutoMapperXConfiguration.cs的文件,其中有一个名为Configure()的方法,其中X 1}}是图层。

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 profiles,所以上面的例子变成了:

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

更清洁/更强大。

答案 1 :(得分:33)

只要您的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原则,他们有一些有效的论据。在这里他们是为了阅读。

Configuring Automapper in Bootstrapper violates Open-Closed Principle?

答案 2 :(得分:16)

更新:此处发布的方法不再有效,因为自{AutoMapper v2}以来SelfProfiler已被删除。

我会采取与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 :(得分:15)

对于那些坚持以下内容的人:

  1. 使用ioc容器
  2. 不喜欢为此打开关闭
  3. 不喜欢单片配置文件
  4. 我在个人资料和利用我的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,但这是我可以接受的惯例。

答案 4 :(得分: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...

您可以通过NuGet安装WebActivator

答案 5 :(得分: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 子类并自动启动它们。

答案 6 :(得分: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>();
    }
}

答案 7 :(得分:5)

从新版本的AutoMapper使用静态方法不推荐使用Mapper.Map()。因此,您可以将MapperConfiguration作为静态属性添加到MvcApplication(Global.asax.cs),并使用它来创建Mapper的实例。

<强> App_Start

INSERT INTO test.data (NAME, TIMESTAMP, AMOUNT) values ('Name1', 123, 4) 
IF not EXIST; OTHERWISE UPDATE test.data set AMOUNT + NEWAMOUNT where TIMESTAMP = oldTimestamp and NAME = oldName

<强>的Global.asax.cs

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

<强> BaseController.cs

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

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

https://github.com/AutoMapper/AutoMapper/wiki/Migrating-from-static-API

答案 8 :(得分:3)

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

<强> Global.asax.vb:

@Html.ViewData.TemplateInfo.HtmlFieldPrefix

<强> AutoMapperConfiguration:

Public Class MvcApplication
    Inherits System.Web.HttpApplication

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

<强>配置文件:

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

答案 9 :(得分:3)

对于那些(迷失)使用的人:

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1(使用个人资料)

以下是我在&#34; new way&#34;中管理AutoMapper的方法。也, 一个巨大,感谢这个answer(and question)

1 - 在WebAPI项目中创建了一个名为&#34; ProfileMappers&#34;的文件夹。在这个文件夹中,我放置了我创建映射的所有配置文件类:

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