2011-07-26 119 views
192

我在ASP.NET MVC應用程序中使用AutoMapper。有人告訴我,我應該把AutoMapper.CreateMap移到其他地方,因爲它們有很多開銷。我不太清楚如何設計我的應用程序來將這些調用放在一個地方。哪裏可以放置AutoMapper.CreateMaps?

我有一個Web層,服務層和數據層。每個項目都是自己的。我使用Ninject來DI一切。我將在網頁和服務層使用AutoMapper

那麼你對AutoMapper的CreateMap有什麼設置?你把它放在哪裏?你怎麼稱呼它?

回答

201

沒有關係,只要它是一個靜態類。這全是關於約定

我們慣例是每個「層」(網絡,服務,數據)具有稱爲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 

這有點像「字樣的接口」 - 不能強制執行,但你想到吧,這樣你就可以在需要的代碼(重構)。

編輯:

只是想我會提到,我現在用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>(); 
    } 
} 

更清潔/更穩健。

+0

我有兩個項目。 MyApp.UI(mvc)和MyApp.Core(CLR)。 ViewModels在MyApp.UI中,而EntityModels在MyApp.Core中。問題:「放置AutoMapperXConfiguration.cs的位置」。 (mvc或clr)?要在clr中使用它,我應該將mvc項目引用到clr。所以我認爲,在MVC是合乎邏輯的? –

+2

@AliRızaAdıyahşi這兩個項目都應該有一個映射文件。核心應該有AutoMapperCoreConfiguration,並且UI應該有AutoMapperWebConfiguration。 Web配置應該添加來自Core配置的配置文件。 – RPM1984

+6

在每個配置類中調用'Mapper.Initialize'是否覆蓋以前的配置文件?如果是這樣,應該用什麼來代替Initialize? – Cody

29

只要您的Web項目引用它所在的程序集,就可以將它放在任何位置。在您的情況下,我會將它放入服務層,因爲Web層和服務層可以訪問它,稍後如果你決定做一個控制檯應用程序,或者你正在做一個單元測試項目,映射配置也可以從這些項目中獲得。

在Global.asax中,您將調用設置所有地圖的方法。請看下圖:

文件AutoMapperBootStrapper.cs

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


    } 
} 

的Global.asax在應用程序啓動

就叫

AutoMapperBootStrapper.BootStrap(); 

現在有些人會反對這個方法違反了一些堅實的原則,他們有有效的論點。他們在這裏閱讀。

Configuring Automapper in Bootstrapper violates Open-Closed Principle?

+10

這個。走向適當的「硬核」架構的每一步似乎都涉及指數級的更多代碼。這很容易; 99.9%的編碼者就足夠了;和你的同事會欣賞簡單。是的,每個人都應該閱讀關於開放 - 封閉原則的問題,但每個人都應該考慮權衡。 – anon

6

把所有的映射邏輯放在1個位置對我來說不是一個好習慣。因爲地圖繪製班級非常龐大而且很難維護。

我建議把映射的東西和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

我會遵循每個文件規則的一個類:http://stackoverflow.com/q/2434990/1158845 – Umair

+0

類似soultion在Velir的博客中描述[在MVC中組織AutoMapper的地圖配置](http://www.velir.com/ blog/index.php/2012/08/27/organizing-automappers-map-configurations-in-mvc /) – xmedeko

14

更新:張貼在這裏的做法是不合法的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

僅供參考,SelfProfiler類自從Automapper v2以來一直沒有了。 –

13

以上所有解決方案都提供了一種靜態方法來調用(從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

+2

我最近也得出了同樣的結論。它使您的地圖創建代碼接近消耗它的代碼。這種方法使得MVC控制器更加可維護。 – mfras3r

+0

@ mfras3r +1我同意你的意見。感謝評論 –

+0

我如何在任何地方啓動它,你能提供一個例子嗎?你的博客鏈接不起作用... – Vyache

14

對於那些誰堅持以下幾點:使用IoC容器


  1. 不喜歡破開此
  2. 關閉不喜歡整體配置文件

我做了概況,並利用我的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,但這是一個我可以生活的約定。

9

除了最好的答案之外,一個好方法是使用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(); 

上述代碼查找所有資料子類和自動啓動它們。

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

對於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) 
+0

我試過你的答案,但它顯示在這一行上的錯誤:Dim config = New MapperConfiguration(//重載解析失敗,因爲沒有可訪問的'New'可以用這些參數調用:'Public Overloads Sub New(configurationExpression As MapperConfigurationExpression) – barsan

+0

@barsan:您是否正確配置了所有配置文件類(UserProfile和PostProfile)?對我來說它適用於Automapper版本5.2.0。 – roland

+0

新版本6.0發佈了,所以'Protected Overrides Sub配置()'不推薦使用,一切都保持不變,但是這一行應該是:'Public Sub New()' – roland

2

對於使用那些誰(丟失):

  • 的WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1(使用配置文件)

下面是如何管理的整合AutoMapper在「new way」中。此外, 一個巨大的由於這個answer(and question)

1 - 創建名爲 「ProfileMappers」 中的WebAPI項目的文件夾。在此文件夾,我把我所有的配置文件類這造成我的映射:

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

稍微調整一些具體細節,這種方法對MVC也很好 - 謝謝! –

相關問題