AutoMapper在MVC中的運用07-對映在訂單場景的例子

Darren Ji發表於2014-02-26

本文參考了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

}

相關文章