AutoMapper在MVC中的运用01-配置、使用、单元测试、举例
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的各种使用场景。