本文參考了Taswar Bhatti的部落格,他寫了《Instant AutoMapper》這本書。遺憾的是,這本電子版書在國內還買不到,也下載不到。也只能從他的有限幾篇博文中來窺探一二了。
本文模擬了一個關於訂單的應用場景,涉及到的方面包括:
※ 顯示所有訂單
※ 顯示客戶資訊
※ 顯示訂單,但不顯示view model OrderDto中的集合導航屬性
※ 把源中的DateTime型別轉換成int型別
※ 把源中的bool型別轉換成string型別
※ 把源中的集合導航屬性IEnumerable<OrderItems> LineItems轉換成目標中的計算數量的string型別
顯示所有訂單
□ 思路
從資料庫獲取Domain model,再轉換成View model.
View Model的屬性,要方便讀取。
Domain model的一些屬性、方法是ViewModel不需要的,比如複雜屬性Customer,計算總額的方法。
□ Domian model
//訂單模型,差不多包含以下方面:
//客戶:複雜型別
//下單時間
//一個LineItem的集合
//一個計算總價的方法
//等等
public class Order
{
public string OrderNo{get;set;}
public Customer Customer{get;set;}
public DateTime PurchaseDate{get;set;}
public bool ShipToHomeSddress{get;set;}
public Guid InternalId{get;set;}
public IEnumerable<OrderItems> LineItems{get;set;}
public decimal GetTotal()
{
return LineItems == null ? 0 : LineItems.Sum(x => x.GetTotalPrice());
}
}
//LineItems
//名稱、價格、數量,計算每個Line的金額=價格*數量
public class OrderItems
{
public decimal Price{get;set;}
public string Name{get;set;}
public int Quantity{get;set;}
public decimal GetTotalPrice()
{
return Price * Quantity;
}
}
public class Customer
{
public string FirstName{get;set;}
public string LastName{get;set;}
public string Bio{get;set;}
public string GetName()
{
return FirstName + ' ' + LastName;
}
}
□ View model
public class OrderDto
{
public string CustomerName{get;set;} //對應源中複雜屬性Customer+Customer的屬性,符合慣例
public decimal Total{get;set;} //對應源中GetTotal方法,符合慣例
public string OrderNumber{get;set;} //對應domain model中的OrderNo,需要對映配置
public IEnumerable<OrderItemDto> LineItems{get;set;} //與源中屬性儲存一致,別忘了OrdeerItemDto需要配置
}
public class OrderItemsDto
{
public string Name{get;set;}
public int Quantity{get;set;}
public decimal Price{get;set;}
}
public class CustomerDto
{
public string Bio{get;set;}
public string Name{get;set;} //與源中的GetName對應
}
□ 控制器
public ActionResult OrderItems()
{
var orders = _repository.GetAll();
Mapper.CreateMap<Order, OrderDto>
.ForMember(dest => dest.OrderNumber, opt => opt.MapFrom(src => src.orderNo));
Mapper.CreateMap<OrderItems, OrderItemsDto>();
var model = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderDto>>(orders);
return View(model);
}
□ 檢視
@model IEnumerable<OrderDto>
@foreach(var item in Model)
{
@item.OrderNumber
@item.CustomerName
@item.Total
@foreach(var child in item.LineItems)
{
@string.Format("({0}) {1} - {2}", @child.Quantity, @Child.Name, @Child.Price)<br />
}
}
顯示客戶資訊
□ 思路
假設Domain model Customer的屬性Bio有可能是null,如果對映到CustomerDto的Bio屬性,也會是null值。
用到了某屬性是null的替換方法.NullSubstitute();
□ 控制器
public ActionResult Index()
{
var customers = _repository.GetAll();
AutoMapper.Mapper.CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.Bio, opt => opt.NullSubstitute("N/A"))
var model = AutoMapper.Mapper.Map<IEnumerable<Customer>, IEnumerable<CustomerDto>>(customers);//集合與集合的對映
return View(model);
}
□ 檢視
@model IEnumerable<CustomerDto>
@foreach(var customer in Model)
{
@customer.Name
@Customer.Bio
}
顯示訂單,但不顯示view model OrderDto中的集合導航屬性
□ 思路
方法Ignore(),把目標中的某個屬性忽略。
□ Domain model
public class Order
{
public string OrderNo { get; set; }
public Customer Customer { get; set; }
public DateTime PurchaseDate { get; set; }
public IEnumerable<OrderItems> LineItems { get; set; }
public bool ShipToHomeAddress { get; set; }
public decimal GetTotal()
{
return LineItems == null ? 0 : LineItems.Sum(x => x.GetTotalPrice());
}
public Guid InternalId { get; set; }
}
public class OrderItems
{
public decimal Price{get;set;}
public string Name{get;set;}
public int Quantity{get;set;}
public decimal GetTotalPrice()
{
return Price * Quantity;
}
}
□ View model
public class OrderDto
{
public string CustomerName { get; set; } //與源model的導航屬性的欄位對應
public decimal Total { get; set; } //與源model的GetTotal()方法對應
public string OrderNumber { get; set; }
public IEnumerable<OrderItemsDto> LineItems { get; set; }
}
□ 控制器
public ActionResult Index()
{
var orders = _reposiotry.GetAll();
AutoMapper.Mapper.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.OrderNumber, opt => opt.MapFrom(src => src.OrderNo))
.ForMember(dest => dest.OrderItemsDto, opt => opt.Ignore());//把目標model中的集合導航屬性單體忽略
}
□ 檢視
@model IEnumerable<OrderDto>
@foreach(var item in Model)
{
@item.OrderNumber
@item.CustomerName
@item.Total()
}
把源中的DateTime型別轉換成int型別
□ 思路
要麼直接通過MapFrom(src => src.DateTime.Hour)
要麼實現IValueFormatter,TypeConverter<,>,ValueResolver<,>
□ Domain model
public class Order
{
public string OrderNo{get;set;}
public Customer Customer {get;set;}
public DateTime PurchaseDate{get;set;} //轉換本屬性
public IEnumerable<OrderItems> LineItmes{get;set;}
public bool ShipToHomeAddress{get;set;}
public decimal GetTotal()
{
return LineItems == null? : : LineItems.Sum(x => x.GetTotalPrice());
}
public Guid InternalId{get;set;}
}
□ View model
public class OrderDateDto
{
public int PurchaseHour{get;set;}
public int PurchaseMinute{get;set;}
public string CustomerName{get;set;}
}
□ 控制器
public ActionResult OrderDate()
{
var order = _repository.Get(3);
order.PurchaseDate = new DateTime(2011, 3, 15, 20, 30, 0);
Mapper.CreateMap<Order, OrderDateDto>()
.ForMember(dest => dest.PurchaseHour, opt => opt.MapFrom(src => src.PurcahseDate.Hour))
.ForMember(dest => dest.PurcaseMinute, opt => opt.MapFrom(src => src.PurchaseDate.Minute));
var model = Mapper.Map<Order, OrderDateDto>(order);
return View(model);
}
□ 檢視
@model OrderDateDto
@Model.CustomerName
@Model.PurchaseHour
@Model.PurchaseMinute
把源中的bool型別轉換成string型別
□ Domain model
public class Order
{
public string OrderNo { get; set; }
public Customer Customer { get; set; }
public DateTime PurchaseDate { get; set; }
public IEnumerable<OrderItems> LineItems { get; set; }
public bool ShipToHomeAddress { get; set; } //轉換本屬性
public decimal GetTotal()
{
return LineItems == null ? 0 : LineItems.Sum(x => x.GetTotalPrice());
}
public Guid InternalId { get; set; }
}
□ View model
public class OrderShipDto
{
public string ShipHome { get; set; }
public string CustomerName { get; set; }
}
□ 自定義解析器
public class CustomBoolResolver : ValueResolver<Order, string>
{
protected override string ResolveCore(Order source)
{
return source.ShipToHomeAddress? "Yes" : "No";
}
}
控制器
public ActionResult OrderShip()
{
var orders = _repository.GetAll();
Mapper.CreateMap<Order, OrderShipDto>()
.ForMember(dest => dest.ShipHome, opt => opt.ResolveUsing<CustomBoolResolver>());
var model = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderShipDto>>(orders);
return View(model);
}
檢視
@item.CustomerName
@item.ShipHome
把源中的集合導航屬性IEnumerable<OrderItems> LineItems轉換成目標中的計算數量的string型別
□ 思路
對於LineItems同時用到自定義ValueResolver<Order, int>和IValueFormatter。
□ Domain model
public class Order
{
public string OrderNo { get; set; }
public Customer Customer { get; set; }
public DateTime PurchaseDate { get; set; }
public IEnumerable<OrderItems> LineItems { get; set; }
public bool ShipToHomeAddress { get; set; }
public decimal GetTotal()
{
return LineItems == null ? 0 : LineItems.Sum(x => x.GetTotalPrice());
}
public Guid InternalId { get; set; }
}
□ View model
public class NumberOfOrderDto
{
public string CustomerName { get; set; }
public string NumberOfOrders { get; set; }
}
□ 自定義解析
public class CustomOrderCount : ValueResolver<Order, int>
{
protected override int ResolveCore(Order source)
{
return source.LineItems.Count();
}
}
□ 自定義formatter格式
public class FormatOrderCount : IValueFormatter
{
public string FormatValue(ResolutionContext context)
{
int num = (int).context.SourceValue;
if(num<=1)
return "Number of Order:" + num;
return "Number of Orders:" + num;
}
}
□ 控制器
public ActionResult NumberOfOrders()
{
var orders = _repository.GetAll();
orders.First().LineItems = new List<OrderItems>(); //在order類中的建構函式中沒有初始化,所以這裡要初始化
Mapper.CreateMap<Order, NumerOfOrderDto>()
.ForMember(dest => dest.NumberOfOrders, opt => {
opt.ResolveUsing<CutomOrderCount>();
opt.AddFormatter<FormatOrderCount>();
})
var model = Mapper.Map<IEnumerable<Order>, IEnumerable<NumberOfOrderDto>>(orders);
return View(model);
}
□ 檢視
@model IEnumerable<NumberOfOrderDto>
@foreach(var order in Model)
{
@order.CustomerName
@order.NumberOfOrders
}