MVC中,如果想在Domain Model和View Model之間建立對映,用AutoMapper是一個不錯的選擇。不僅如此,AutoMapper能在不同物件之間建立對映,比如string與int型別, DateTime與int型別,介面與實現類,等等。本篇主要總結AutoMapper在MVC中的配置、使用、單元測試,以及各種對映場景。
注意:
如果通過NuGet下載最新版的AutoMapper,需要注意的是:有些方法,比如ForMember方法,和以前不一樣。還有一些方法已經過期。
配置
□ 全域性配置
1 public class MvcApplication : System.Web.HttpApplication 2 3 { 4 5 protected void Application_Start() 6 7 { 8 9 ... 10 11 ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); 12 13 CreateMaps(); 14 15 } 16 17 18 public void CreateMaps() 19 20 { 21 22 AutoMapper.Mapper.Reset(); 23 24 AutoMapper.Mapper.CreateMap<CustomerCreateEditViewModel, Customer>(); 25 26 AutoMapper.Mapper.AssertConfigurationIsValid(); 27 28 } 29 30 }
這種方法不太利於單元測試。
□ 全域性配置 + 靜態類配置
AutoMapper靜態配置類:
1 public static class AutoMapperWebConfiguration 2 3 { 4 5 public static void Configure() 6 7 { 8 9 Mapper.Initialize(cfg => 10 11 { 12 13 cfg.AddProfile(new UserProfile()); 14 15 }); 16 17 } 18 19 }
UserProfile繼承於AutoMapper的Profile類。通過這種繼承,我們可以建立不同的對映規則。比如一套規則用於Domain Model轉換成View Model,一套規則用於View Model轉換成Domain Model。
1 public class UserProfile : Profile 2 3 { 4 5 protected override void Configure() 6 7 { 8 9 AddFormatter<MoneyFormatter>(); 10 11 Mapper.CreateMap<Order, OrderListViewModel>(); 12 13 } 14 15 }
最後在全域性註冊。
AutoMapperWebConfiguration.Configure();
單元測試
當專案中有比較多的model的時候,通過單元測試,可以發現對映中存在的問題,而不是等到程式執行的時候。
1 [TestClass] 2 3 public class AutoMapperConfigurationTester 4 5 { 6 7 [TestMethod] 8 9 public void TestMethod1() 10 11 { 12 13 AutoMapperWebConfiguration.Configure(); 14 15 Mapper.AssertConfigurationIsValid(); 16 17 } 18 19 }
簡單例子
□ Domain Models
1 public class Customer 2 3 { 4 5 public string FirstName{get;set;} 6 7 public string LastName{get;set;} 8 9 public string Email{get;set;} 10 11 pubic Address HomeAddress{get;set;} 12 13 public string GetFullName() 14 15 { 16 17 return string.Format("{0}{1}", FirstName, LastName); 18 19 } 20 21 } 22 23 24 public class Address 25 26 { 27 28 public string Address1{get;set;} 29 30 public string Address2{get;set;} 31 32 public string City{get;set;} 33 34 public string PostalCode{get;set;} 35 36 public string Country{get;set;} 37 38 }
□ View Model
1 public class CustomerListViewModel 2 3 { 4 5 public string FullName{get;set;} 6 7 public string Email{get;set;} 8 9 public string HomeAddressCountry{get;set;} 10 11 }
□ Controller
1 public class CustomersController : Controller 2 3 { 4 5 private readonly ICustomerService m_CustomerService; 6 7 public CustomersController(ICustomerService customerService) 8 9 { 10 11 m_CustomerService = customerService; 12 13 } 14 15 16 public ActionResult Index() 17 18 { 19 20 IList<Customer> customers = m_CustomerService.GetCustomers(); 21 22 //為了演示方便,對映規則沒有寫在統一的靜態類中 23 24 Mapper.CreateMap<Customer, CustomerListViewModel>(); 25 26 IList<CustomerListViewModel> viewModelList = Mapper.Map<IList<Customer>, IList<CustomerListViewModel>>(customers); 27 28 return View(viewModelList); 29 30 } 31 32 }
□ 要點
AutoMapper的"神奇"是建立在慣例和配置之上的。
○ 目標和源的屬性名要儘可能保持一致。
○ 當源的屬性是複雜型別時,目標屬性如果遵循"源屬性+源屬性所對應類中的某個欄位"的慣例,就像這裡的HomeAddressCountry,就能拿到源中複雜型別屬性所對應類中的欄位。
○ 源中的"Get+其它"形成的方法,在目標中只要把"其它"作為屬性名,就可以拿到源中方法的返回值,就像源中的GetFullName()方法,對應目標中的FullName屬性。
○ 建立對映永遠是類與類間的對映,而通過源獲取目標,這裡的源可以是單個類,也可以是集合,就像 Mapper.Map<IList<Customer>, IList<CustomerListViewModel>>(customers)。
□ 出處
以上參考了這篇博文:http://bengtbe.com/blog/2009/04/14/using-automapper-to-map-view-models-in-asp-net-mvc/
把Domain Model與View Model的對映放到系統屬性裡實現
有時,為了程式碼更大程度的簡潔,我們可以把系統屬性裡,以Aspect Oriented Programming(AOP),面向切面程式設計的思想來實現。
通過ActionFilterAttribute過濾系統屬性可以控制發生在Action方法之前和之後的事件。
如果Domain Model轉換成View Model,那我們就讓自定義事件發生在Action方法之後。
如果View Model轉換成Domain Model,那我們就讓自定義事件發生在Action方法之前。
□ Domain Model轉換成View Model
1 public class DomainToViewAttribute : ActionFilterAttribute 2 3 { 4 5 private readonly Type _destType; 6 7 private readonly Type _sourceType; 8 9 10 public DomainToViewAttribute(Type sourceType, Type desType) 11 12 { 13 14 _sourceType = sourceType; 15 16 _destType = desType; 17 18 } 19 20 21 public override void OnActionExecuted(ActionExecutedContext filterContext) 22 23 { 24 25 var domainModel = filterContext.Controller.ViewData.Model; 26 27 var viewModel = Mapper.Map(domainModel, _sourceType, _destType); 28 29 filterContext.Controller.ViewData.Model = viewModel; 30 31 } 32 33 }
□ View Model轉換成Domain Model
1 public class ViewToDomainAttribute : ActionFilterAttribute 2 3 { 4 5 private readonly Type _desType; 6 7 private readonly Type _sourseType; 8 9 10 public ViewToDomainAttribute(Type sourceType, Type desType) 11 12 { 13 14 _sourseType = sourceType; 15 16 _desType = desType; 17 18 } 19 20 21 public override void OnActionExecuting(ActionExecutingContext filterContext) 22 23 { 24 25 var viewModel = filterContext.Controller.ViewData.Model; 26 27 var domainModel = Mapper.Map(viewModel, _sourseType, _desType); 28 29 filterContext.Controller.ViewData.Model = domainModel; 30 31 } 32 33 }
□ 把自定義系統屬性打到Action方法之上
[DomainToView(typeof(IEnumerable<Customer>), typeof(IEnumerable<CustomerInfo>))]
public ViewResult Index()
接下來的幾篇將介紹AutoMapper的各種使用場景。