ASP.NET Web API中展示实体Link相关的方面

有时候,向服务端请求一个实体,我们希望返回如下的格式:

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();
        }
    }
}
原文地址:https://www.cnblogs.com/darrenji/p/5144162.html