首页 文章

在哪里放置AutoMapper.CreateMaps?

提问于
浏览
204

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

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

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

10 回答

  • 32

    不是't matter, as long as it'是一个静态类 . 这都是关于惯例的 .

    我们的惯例是每个"layer"(web,services,data)都有一个名为 AutoMapperXConfiguration.cs 的文件,其中有一个名为 Configure() 的方法,其中 X 是该层 .

    然后 Configure() 方法为每个区域调用 private 方法 .

    以下是我们的Web层配置示例:

    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
    

    它有点像“单词界面” - 不能强制执行它,但是你期望它,所以你可以在必要时编码(和重构) .

    EDIT:

    我想我现在提到我现在使用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>();
        }
    }
    

    更清洁/更强大 .

  • 2

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

    在Global.asax中,您将调用设置所有 Map 的方法 . 见下文:

    文件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?

  • 211

    Update: 此处发布的方法不再有效,因为自AutoMapper v2起已删除 SelfProfiler .

    我会采取与Thoai类似的方法 . 但我会使用内置的 SelfProfiler<> 类来处理 Map ,然后使用 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

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

    • 使用ioc容器

    • 不喜欢为此打破封闭

    • 不喜欢单片配置文件

    我在配置文件和利用我的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,但这是我可以使用的惯例 .

  • 16

    以上所有解决方案都提供了一种静态方法来调用(来自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 .

  • 15

    除了最好的答案,一个好方法是使用Autofac IoC liberary来添加一些自动化 . 有了这个,您 just 无论启动如何定义您的配置文件 .

    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 子类并自动启动它们 .

  • 14

    将所有映射逻辑放在一个位置对我来说不是一个好习惯 . 因为映射类非常大并且很难维护 .

    我建议将映射内容与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>();
        }
    }
    
  • 10

    从新版本的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

  • 5

    对于使用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
    

    Profiles:

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

    Mapping:

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

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

    • WebAPI 2

    • SimpleInjector 3.1

    • AutoMapper 4.2.1(带配置文件)

    以下是我在“new way”中集成AutoMapper的方法 . 一个 Huge 感谢answer(and question)

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

相关问题