有時候,向服務端請求一個實體,我們希望返回如下的格式:links: [ href: http://localhost:8901/api/user/diaries/2013-08-17, rel: "self", method: "GET", isTemplated: false],currentDa...
有時候,向服務端請求一個實體,我們希望返回如下的格式:
links: [
href: http://localhost:8901/api/user/diaries/2013-08-17,
rel: "self",
method: "GET",
isTemplated: false
],
currentDate:"2013-08-17"
首先抽象出一個與Link相關的類:
public class LinkModel { public stirng Href{get;set;} public stirng Rel{get;set;} public string Method{get;set;} public bool IsTemplated{get;set;} }
再放到某個視圖模型中:
public class DiaryModel { //存儲和模型相關的鏈接 public ICollection<LinkModel> Links{get;set;} public DateTime CurrentDate{get;set;} } public class Diary { public int Id{get;set;} public DateTime CurrentDate{get;set;} }
ModelFactory用來實現視圖模型和領域模型之間的轉化。
public class ModelFactory { private UrlHelper _urlHelper; public ModelFactory(HttpRequestMessage request) { _urlHelper = new UrlHelper(request); } //領域模型轉換成視圖模型 public DiaryModel Create(Diary d) { return new DiaryModel() { Links = new List<LinkModel> { CreateLink(_urlHelper.Link("Diaryis", new {diaryid=d.CurrentDate.ToString("yyyy-MM-dd")}),"self"); }, CurrentDate = d.CurrentDate } } public LinkModel CreateLink(string href, string rel, string method = "GET", bool isTemplated = false) { return new LinkModel() { Href = href, Rel = rel, Method = method, IsTemplated = isTemplated } } //視圖模型轉換成領域模型 public Diary Parse(DiaryModel model) { try { var entity = new Diary(); var selfLink = model.Links.Where(l => l.Rel == "self").FirstOrDefault(); if(selfLink != null && !string.IsNullOrWhiteSpace(selfLink.Href)) { //從Uri中取出主鍵 var uri = new Uri(selfLink.Href); entity.Id = int.Parse(uri.Segments.Last()); } entity.CurrentDate = model.CurrentDate; return entity; } catch(Exception ex) { } } }
Diaries這個controller略,路由方面:
//api/user/diaries //api/user/diaries/2001-01-01 config.Routes.MapHttpRoute( name: "Diaries", routeTemplate: "api/user/diaries/{dairyid}", defaults: new {controller="diaries", diaryid=RouteParameter.Optional} )
這樣,在客戶端發出 http://localhost:8901/api/user/diaries/2013-08-17 GET請求,得到如下的響應:
links: [
href: http://localhost:8901/api/user/diaries/2013-08-17,
rel: "self",
method: "GET",
isTemplated: false
],
currentDate:"2013-08-17"
在返回分頁相關的action中,也可以返回相關的Link部分。
先定義一個基類控制器:
public abstract class BaseController : ApiController { ICountingKsRepository _repo; ModelFactory _modelFactory; public BaseController(ICountingKsRepository repo) { _repo = repo; //寫在構造函數里的話有點遲,必須等實例化_modelFactory才有值 //_modelFactory = new ModelFactory(this.Request); } protected ModelFactory TheModelFactory { get { if(_modelFactory == null) { _modelFactory = new ModelFactory(this.Request, TheRepository); } return _modelFactory; } } protected ICountingsRepository TheRepoisitory { get { return _repo; } } }
可見,把共同的部分封裝到基類控制器中是很好的習慣,然後基類控制器的子類通過屬性獲取一些方面。
再到具體的控制器:
public class FoodsController : BaseController { ICountingKsRepoisotry _repo; ModelFactory _modelFactory; public FoodsController(ICountingKsRepository repo) : base(repo) { } const int PAGE_SIZE = 50; public object Get(bool includeMeasures = true, int page = 0) { IQueryable<Food> query; if(includeMeausres) { query = TheRepository.GetAllFoodsWithMeausres(); } else { query = TheRepository.GetAllFoods(); } //方便統計總數 var baseQuery = query.OrderBy(f =>f.Description); //using System.Web.Http.Routing var helper = new UrlHelper(Request); var links = new List<LinkModel>(); if(page > 0) { links.Add(TheModelFactory.CreateLink(helper.Link("Food", new {page = page - 1},"prevPage")); } if(page < totalPages - 1) { links.Add(TheModelFactory.CreateLink(helper.Link("Food", new {page = page + 1},"nextPage")); } //把上一頁和下一頁的url保存下來 //var prevUrl = page > 0 ? helper.Link("Food", new {page = page - 1}) : ""; //var nextUrl = page > 0 ? helper.Link("Food", new {page = page + 1}) : ""; //輸出總數 var totalCount = baseQuery.Count(); var totalPages = Math.Ceiling((double)totalCount/PAGE_SIZE); var result = baseQuery .Skip(PAGE_SIZE * page) .Take(PAGE_SIZE) .ToList() .Select(f => TheModelFactory.FoodFromDomainToView(f)); //方便客戶端接收 return new { TotalCount = totalCount, TotalPages = totalPages, Result = result, Links = links //PrevPageUrl = prevUrl, //NextPageUrl = nextUrl, } } public FoodModel Get(int foodid) { return TheModelFactory.FoodFromDomainToView(TheRepository.GetFood(foodid)); } }
客戶端請求:localhost:8901/api/nutrition/foods
{
totalCount:800,
totalPages:151,
links: [
{
href: http://localhost:8901/api/nutrition/foods?page=1,
rel: "prevPage",
method: "GET",
isTemplated: false,
},
{
href: http://localhost:8901/api/nutrition/foods?page=2,
rel: "nextPage",
method: "GET",
isTemplated: false
}
],
result: [...]
}
另外,還可以控制序列化過程。
在LinkModel這個視圖中:
public class LinkModel { public stirng Href{get;set;} public stirng Rel{get;set;} public string Method{get;set;} public bool IsTemplated{get;set;} }
在顯示的時候,可能不想讓IsTemplated顯示出來,如何在序列化的過程中做到呢?
--通過jsonFormatter.SerializerSettings.Converts屬性,用來控制序列化為json數據時的顯示方式。
在WebApiConfig.cs中:
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().FirstOrDefault(); jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNameContractResolver(); jsonFormatter.SerializerSettings.Converts(new LinkModelConverter());
而LinkModelConverter類需要繼承JsonConverter類。
public class LinkModelConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType.Equals(typeof(LinkModel)); } public override object ReadJson(JsonReader reader, Type object) { return reader.Value; } public override void WriteJson(JsonWriter wrtier, object value) { var model = value as LinkModel; if(model != null) { wirter.WriteStartObject(); writer.WirteProeprtyName("href"); writer.WriteValue(model.Href); writer.WriteProeprtyName("rel"); writer.WriteValue(model.Rel); if(!model.Method.Equals("GET",StringComparison.ordinalIgnoreCase)) { writer.WritePropertyName("method"); writer.WriteValue(model.Method); } if(model.IsTemplated) { writer.WriterPropertyName("isTemplated"); writer.WriteValue(model.IsTemplated); } writer.WriteEndObject(); } } }