HATEOAS
Hypermedia As The Engine Of Application State
- HATEOAS是rest架构风格中最复杂的约束,也是构建成熟Rest服务的核心,它的重要性打破了客户端与服务端的严格的契约,也让客户端更加的智能和自适应,而Rest本身的演化和更新则变得更容易。
- 有助于自我描述性和可进化性。
- 超媒体驱动如何消费和使用Api
相较于纯粹的资源
- 客户端更多地需要了解Api的内在逻辑
- 如果Api发生了一点变化,都会破坏消费者
- Api无法独立于消费它的应用单独地进行进化。
支持HATEOAS
- 需要给相应添加一个额外的属性“Links”,而客户端程序只需要检查这些Uri即可。
Html对HATEOAS的支持:
- Json与Xml并没有展示Link的概念。
- 而Html的anchor标签可以。
- Href包含了URI。
- rel描述了Link和资源的关系。
- type可选,表示了媒体的类型。
方法
- 静态类型方法:通过基类和包装类,也就是返回的资源中都含有link,通过继承同一个基类实现。
- 动态类型方法:需要使用匿名类或者ExpandoObject等,单个资源使用ExpandoObject,集合类资源则使用匿名类。
代码
Link生成
private IEnumerable<LinkDto> CreateLinksForCollege(long CollegeId,string fields)
{
var links = new List<LinkDto>();
if (string.IsNullOrWhiteSpace(fields))
{
links.Add(new LinkDto(Url.Link(nameof(GetCollege), new { CollegeId }), "self", "GET"));
}
else
{
links.Add(new LinkDto(Url.Link(nameof(GetCollege), new { CollegeId , fields }), "self", "GET"));
}
links.Add(new LinkDto(Url.Link(nameof(DeleteCollege), new { CollegeId }), "delete_college", "DELETE"));
links.Add(new LinkDto(Url.Link(nameof(IndustryController.GetIndustriesForCollege), new { CollegeId }), "industries", "GET"));
links.Add(new LinkDto(Url.Link(nameof(IndustryController.CreateIndustryForCollege), new { CollegeId }), "create_industary_for_college", "POST"));
return links;
}
单个资源link添加到Model
var link = CreateLinksForCollege(CollegeId, fields);
var linkDict = _Mapper.Map<CollegeInfoDto>(college).ShapeData(fields) as IDictionary<string, object>;
linkDict.Add("links", link);
多个资源Links生成
private IEnumerable<LinkDto> CreateLinksForColleges(CollegeDtoParameters parameters,bool HasPrevious,bool HasNext)
{
var links = new List<LinkDto>();
links.Add(new LinkDto(CreateCollegesResourceUri(parameters, ResourceUriType.CurrentPage), "self", "GET"));
if(HasPrevious) links.Add(
new LinkDto(CreateCollegesResourceUri(parameters, ResourceUriType.PreviousPage), "previous_page", "GET"));
if (HasNext) links.Add(
new LinkDto(CreateCollegesResourceUri(parameters, ResourceUriType.NextPage), "next_page", "GET"));
return links;
}
将Links添加到多个资源的Model中
var shapeData = _Mapper.Map<IEnumerable<CollegeInfoDto>>(colleges).ShapeData(parameters.Fields);
var links = CreateLinksForColleges(parameters, colleges.HasPrevious, colleges.HasNext);
var shapeCollegesWithLinks = shapeData.Select(c =>
{
var collegeDict = c as IDictionary<string, object>;
var collegeLinks = CreateLinksForCollege((long)collegeDict["Id"], null);
collegeDict.Add("links", collegeLinks);
return collegeDict;
});
var linkedCollegesResource = new
{
value = shapeCollegesWithLinks,
links,
};
Root目录
[Route("Api")]
[ApiController]
public class RootController : Controller
{
[HttpGet(Name =nameof(GetRoot))]
public IActionResult GetRoot()
{
var links = new List<LinkDto>();
links.Add(new LinkDto(Url.Link(nameof(GetRoot), new { }), "self", "GET"));
links.Add(new LinkDto(Url.Link(nameof(ConllegeController.GetColleges), new { }), "colleges", "GET"));
links.Add(new LinkDto(Url.Link(nameof(ConllegeController.CreateCollege), new { }), "create_college", "POST"));
return Ok(links);
}
}