参照 草根专栏- ASP.NET Core + Ng6 实战:https://v.qq.com/x/page/d07652pu1zi.html

一、Get返回资源塑形

1、添加集合塑形EnumerableExtensions.cs,单个塑形类ObjectExtensions.cs:

namespace BlogDemo.Infrastructure.Extensions
{
public static class EnumerableExtensions
{
public static IEnumerable<ExpandoObject> ToDynamicIEnumerable<TSource>(this IEnumerable<TSource> source, string fields = null)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} var expandoObjectList = new List<ExpandoObject>();
var propertyInfoList = new List<PropertyInfo>();
if (string.IsNullOrWhiteSpace(fields))
{
var propertyInfos = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
propertyInfoList.AddRange(propertyInfos);
}
else
{
var fieldsAfterSplit = fields.Split(',').ToList();
foreach (var field in fieldsAfterSplit)
{
var propertyName = field.Trim();
if (string.IsNullOrEmpty(propertyName))
{
continue;
}
var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo == null)
{
throw new Exception($"Property {propertyName} wasn't found on {typeof(TSource)}");
}
propertyInfoList.Add(propertyInfo);
}
} foreach (TSource sourceObject in source)
{
var dataShapedObject = new ExpandoObject();
foreach (var propertyInfo in propertyInfoList)
{
var propertyValue = propertyInfo.GetValue(sourceObject);
((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
}
expandoObjectList.Add(dataShapedObject);
} return expandoObjectList;
}
}
}
namespace BlogDemo.Infrastructure.Extensions
{
public static class ObjectExtensions
{
public static ExpandoObject ToDynamic<TSource>(this TSource source, string fields = null)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} var dataShapedObject = new ExpandoObject();
if (string.IsNullOrWhiteSpace(fields))
{
var propertyInfos = typeof(TSource).GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
foreach (var propertyInfo in propertyInfos)
{
var propertyValue = propertyInfo.GetValue(source);
((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
}
return dataShapedObject;
}
var fieldsAfterSplit = fields.Split(',').ToList();
foreach (var field in fieldsAfterSplit)
{
var propertyName = field.Trim();
var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo == null)
{
throw new Exception($"Can't found property ¡®{typeof(TSource)}¡¯ on ¡®{propertyName}¡¯");
}
var propertyValue = propertyInfo.GetValue(source);
((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
} return dataShapedObject;
}
}
}

2、Controller修改Action方法:

(1) 集合塑形:

        [HttpGet(Name = "GetPosts")]
public async Task<IActionResult> Get(PostParameters parameters)
{
var posts = await _postRepository.GetPostsAsync(parameters);
var postDto=_mapper.Map<IEnumerable<Post>,IEnumerable<PostDTO>>(posts); var shapePostDTO= postDto.ToDynamicIEnumerable(parameters.Fields); var previousPageLink = posts.HasPrevious ?
CreatePostUri(parameters, PaginationResourceUriType.PreviousPage) : null; var nextPageLink = posts.HasNext ?
CreatePostUri(parameters, PaginationResourceUriType.NextPage) : null;
var meta = new
{
PageSize = posts.PageSize,
PageIndex = posts.PageIndex,
TotalItemCount = posts.TotalItemsCount,
PageCount = posts.PageCount,
previousPageLink,
nextPageLink
};
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(meta, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})); return Ok(shapePostDTO);
}

(2)单个塑形:

        [HttpGet("{Id}")]
public async Task<IActionResult> Get(int Id,string fields=null)
{ var post = await _postRepository.GetPostId(Id);
if(post==null)
{
return NotFound();
}
var postDTO = _mapper.Map<Post, PostDTO>(post);
var shapePostDTO = postDTO.ToDynamic(fields);
return Ok(shapePostDTO); }

3. 将json返回的首字母转化为小写:

            services.AddMvc(option => {
option.ReturnHttpNotAcceptable = true;
option.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
}).AddJsonOptions(options=> {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});

4、Postman测试:

(1)集合塑形

(2)单个塑形:

5、Action中验证filed是否存在:

            //验证排序属性映射是否存在
if (!_propertyMappingContainer.ValidateMappingExistsFor<PostDTO, Post>(parameters.OrderBy))
{
return BadRequest("Can't finds fields for sorting.");
} //验证Filed是否存在
if (!_typeHelperService.TypeHasProperties<PostDTO>(parameters.Fields))
{
return BadRequest("Filed not exits");
}
             services.AddTransient<ITypeHelperService, TypeHelperService>();

二、HATEOAS (Hypermedia as the Engine of Application State)

 1、 REST里最复杂的约束, 构建成熟REST API的核心

  • 可进化性, 自我描述
  • 超媒体(Hypermedia, 例如超链接)驱动如何消费和使用API

2、不使用HATEOAS

  • 客户端更多的需要了解API内在逻辑
  • 如果API发生了一点变化(添加了额外的规则, 改变规则)都会破坏API的消费者.
  • API无法独立于消费它的应用进行进化.

3、使用HATEOAS

  • 这个response里面包含了若干link, 第一个link包含着获取当前响应的链接, 第二个link则告诉客户端如何去更新该post.
  • 不改变响应主体结果的情况下添加另外一个删除的功能(link), 客户端通过响应里的links就会发现这个删除功能, 但是对其他部分都没有影响.

       4、HATEOAS – 展示链接

  • JSON和XML并没有如何展示link的概念. 但是HTML的anchor元素却知道: <a href="uri" rel="type" type="media type">.
  1. href包含了URI
  2. rel则描述了link如何和资源的关系
  3. type是可选的, 它表示了媒体的类型
  • 我们的例子:
  1. method: 定义了需要使用的方法
  2. rel: 表明了动作的类型
  3. href: 包含了执行这个动作所包含的URI.

      5、如何实现HATEOAS

  • 静态基类
  1. 需要基类(包含link)和包装类, 也就是返回的资源里面都含有link, 通过继承于同一个基类来实现
  • 动态类型, 需要使用例如匿名类或ExpandoObject等
  1. 对于单个资源可以使用ExpandoObject
  2. 对于集合类资源则使用匿名类.

      6、HATEOAS – 动态类型方案

(1)  建立 LinkResource.cs 类

namespace BlogDemo.Infrastructure.Resources
{
public class LinkResource
{
public LinkResource(string href, string rel, string method)
{
Href = href;
Rel = rel;
Method = method;
} public string Href { get; set; }
public string Rel { get; set; }
public string Method { get; set; }
}
}

(2)单个对象

Controller中添加 CreateLinksForPost()  方法

        private IEnumerable<LinkResource> CreateLinksForPost(int id, string fields = null)
{
var links = new List<LinkResource>(); if (string.IsNullOrWhiteSpace(fields))
{
links.Add(
new LinkResource(
_urlHelper.Link("GetPost", new { id }), "self", "GET"));
}
else
{
links.Add(
new LinkResource(
_urlHelper.Link("GetPost", new { id, fields }), "self", "GET"));
} links.Add(
new LinkResource(
_urlHelper.Link("DeletePost", new { id }), "delete_post", "DELETE")); return links;
}
        [HttpGet("{Id}", Name = "GetPost")]
public async Task<IActionResult> Get(int Id,string fields=null)
{
//验证Filed是否存在
if (!_typeHelperService.TypeHasProperties<PostDTO>(fields))
{
return BadRequest("Filed not exits");
}
var post = await _postRepository.GetPostId(Id);
if(post==null)
{
return NotFound();
}
var postDTO = _mapper.Map<Post, PostDTO>(post);
var shapePostDTO = postDTO.ToDynamic(fields);
var links = CreateLinksForPost(Id, fields); var result = (IDictionary<string, object>)shapePostDTO; result.Add("links", links);
return Ok(result); }

(3)集合对象

在Controller中添加  CreateLinksForPosts()  方法:

        private IEnumerable<LinkResource> CreateLinksForPosts(PostParameters postResourceParameters,
bool hasPrevious, bool hasNext)
{
var links = new List<LinkResource>
{
new LinkResource(
CreatePostUri(postResourceParameters, PaginationResourceUriType.CurrentPage),
"self", "GET")
}; if (hasPrevious)
{
links.Add(
new LinkResource(
CreatePostUri(postResourceParameters, PaginationResourceUriType.PreviousPage),
"previous_page", "GET"));
} if (hasNext)
{
links.Add(
new LinkResource(
CreatePostUri(postResourceParameters, PaginationResourceUriType.NextPage),
"next_page", "GET"));
} return links;
}

      7、自定义Media Type

创建供应商特定媒体类型 Vendor-specific media type    上例中使用application/json会破坏了资源的自我描述性这条约束, API消费者无法从content-type的类型来正确的解析响应.

  • application/vnd.mycompany.hateoas+json
  1. vnd是vendor的缩写,这一条是mime type的原则,表示这个媒体类型是供应商特定的
  2. 自定义的标识,也可能还包括额外的值,这里我是用的是公司名,随后是hateoas表示返回的响应里面要包含链接
  3. “+json”
  • 在Startup里注册.

(1) 创建RequestHeaderMatchingMediaTypeAttribute.cs类

namespace BlogDemo.Api.Helpers
{
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class RequestHeaderMatchingMediaTypeAttribute : Attribute, IActionConstraint
{
private readonly string _requestHeaderToMatch;
private readonly string[] _mediaTypes; public RequestHeaderMatchingMediaTypeAttribute(string requestHeaderToMatch, string[] mediaTypes)
{
_requestHeaderToMatch = requestHeaderToMatch;
_mediaTypes = mediaTypes;
} public bool Accept(ActionConstraintContext context)
{
var requestHeaders = context.RouteContext.HttpContext.Request.Headers;
if (!requestHeaders.ContainsKey(_requestHeaderToMatch))
{
return false;
} foreach (var mediaType in _mediaTypes)
{
var mediaTypeMatches = string.Equals(requestHeaders[_requestHeaderToMatch].ToString(),
mediaType, StringComparison.OrdinalIgnoreCase);
if (mediaTypeMatches)
{
return true;
}
} return false;
} public int Order { get; } = ;
}
}

(2)注册自定义mediatype

            services.AddMvc(option => {
option.ReturnHttpNotAcceptable = true;
// option.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
var outputFormatter = option.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
if(outputFormatter!=null)
{
outputFormatter.SupportedMediaTypes.Add("application/vnd.cfy.hateoas+json");
} })

(3)修改Action

 --> MediaType="application/vnd.cgzl.hateoas+json"

        [HttpGet(Name = "GetPosts")]
[RequestHeaderMatchingMediaType("Accept", new[] { "application/vnd.cgzl.hateoas+json" })]
public async Task<IActionResult> GetHateoas(PostParameters parameters,[FromHeader(Name ="Accept")] string mediaType)
{
//验证排序属性映射是否存在
if (!_propertyMappingContainer.ValidateMappingExistsFor<PostDTO, Post>(parameters.OrderBy))
{
return BadRequest("Can't finds fields for sorting.");
} //验证Filed是否存在
if (!_typeHelperService.TypeHasProperties<PostDTO>(parameters.Fields))
{
return BadRequest("Filed not exits");
} var posts = await _postRepository.GetPostsAsync(parameters);
var postDto=_mapper.Map<IEnumerable<Post>,IEnumerable<PostDTO>>(posts); var shapePostDTO = postDto.ToDynamicIEnumerable(parameters.Fields);
var previousPageLink = posts.HasPrevious ?
CreatePostUri(parameters, PaginationResourceUriType.PreviousPage) : null; var nextPageLink = posts.HasNext ?
CreatePostUri(parameters, PaginationResourceUriType.NextPage) : null; var shapedWithLinks = shapePostDTO.Select(x =>
{
var dict = x as IDictionary<string, object>;
var postLinks = CreateLinksForPost((int)dict["Id"], parameters.Fields);
dict.Add("links", postLinks);
return dict;
});
var links = CreateLinksForPosts(parameters, posts.HasPrevious, posts.HasNext);
var result = new
{
value = shapedWithLinks,
links
}; var meta = new
{
PageSize = posts.PageSize,
PageIndex = posts.PageIndex,
TotalItemCount = posts.TotalItemsCount,
PageCount = posts.PageCount,
previousPageLink,
nextPageLink
};
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(meta, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})); return Ok(result);
}

 --> MediaType="application/json"

        [HttpGet(Name = "GetPosts")]
[RequestHeaderMatchingMediaType("Accept", new[] { "application/json" })]
public async Task<IActionResult> Get(PostParameters postParameters)
{
if (!_propertyMappingContainer.ValidateMappingExistsFor<PostDTO, Post>(postParameters.OrderBy))
{
return BadRequest("Can't finds fields for sorting.");
} if (!_typeHelperService.TypeHasProperties<PostDTO>(postParameters.Fields))
{
return BadRequest("Fields not exist.");
} var postList = await _postRepository.GetPostsAsync(postParameters); var postResources = _mapper.Map<IEnumerable<Post>, IEnumerable<PostDTO>>(postList); var previousPageLink = postList.HasPrevious ?
CreatePostUri(postParameters,
PaginationResourceUriType.PreviousPage) : null; var nextPageLink = postList.HasNext ?
CreatePostUri(postParameters,
PaginationResourceUriType.NextPage) : null; var meta = new
{
postList.TotalItemsCount,
postList.PageSize,
postList.PageIndex,
postList.PageCount,
previousPageLink,
nextPageLink
}; Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(meta, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})); return Ok(postResources.ToDynamicIEnumerable(postParameters.Fields));
}

ASP NET Core --- 资源塑形, HATEOAS, Media Type的更多相关文章

  1. ASP.NET Core 资源打包与压缩

    ASP.NET Core 资源打包与压缩 在ASP.NET 中可以使用打包与压缩来提高Web应用程序页面加载的性能. 打包是将多个文件(CSS,JS等资源文件)合并或打包到单个文件.文件合并可减少We ...

  2. 【ASP.Net】 web api中的media type

    1. 有什么用? 通常用来标识http请求中的内容的类型用来告诉server端如何解析client端发送的message, 或者标识client希望从server端得到的资源是什么样的类型.又被称为M ...

  3. asp.net core 3.0 JObject The collection type 'Newtonsoft.Json.Linq.JObject' is not supported

    在asp.net core 3.0 中,如果直接在Controller中返回 Jobject 类型,会抛出如下错误: The collection type 'Newtonsoft.Json.Linq ...

  4. 用ASP.NET Core 2.1 建立规范的 REST API -- HATEOAS

    本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314 ...

  5. 使用静态基类方案让 ASP.NET Core 实现遵循 HATEOAS Restful Web API

    Hypermedia As The Engine Of Application State (HATEOAS) HATEOAS(Hypermedia as the engine of applicat ...

  6. 用ASP.NET Core 2.1 建立规范的 REST API -- 翻页/排序/过滤等

    本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314 ...

  7. ASP.NET Core 2 学习笔记(十二)REST-Like API

    Restful几乎已算是API设计的标准,通过HTTP Method区分新增(Create).查询(Read).修改(Update)和删除(Delete),简称CRUD四种数据存取方式,简约又直接的风 ...

  8. Asp.Net Core 3.0 学习3、Web Api 文件上传 Ajax请求以及跨域问题

    1.创建Api项目 我用的是VS2019 Core3.1 .打开Vs2019 创建Asp.Net Core Web应用程序命名CoreWebApi 创建选择API 在Controller文件夹下面添加 ...

  9. ASP.NET Core多语言 (转载)

    ASP.NET Core中提供了一些本地化服务和中间件,可将网站本地化为不同的语言文化.ASP.NET Core中我们可以使用Microsoft.AspNetCore.Localization库来实现 ...

随机推荐

  1. ASP.NET整体运行机制+asp.net请求管道+页面生命周期+MVC整体运行机制原理图

    在网上找的,个人感觉很好的

  2. hadoop二次排序

    import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.IOException ...

  3. AngularJS 二 指令介绍

    初始化AngularJS框架 ng-app指令: 在NG-程序指令是AngularJS应用程序的起点.它自动初始化AngularJS框架.AngularJS框架将在加载整个文档之后首先检查HTML文档 ...

  4. 协议类接口 - UART

    一.何为协议类接口? 双方约定信号的协议和满足时序要求. 二.UART如何传数据 通用异步收发器简称 UART,即“Universal Asynchronous Receiver Transmitte ...

  5. Flask—06-理解掌握flask数据模型(02)

    数据模型 模型关系 一对多(使用最多) 一:学生(Student) 需要添加反向引用 多:文章(Article) 需要添加外键关联 一对一 一:学生(Student),主表 需要添加反向引用,在一对多 ...

  6. CALayer创建图层(转)

    一.添加一个图层  添加图层的步骤:  1.创建layer  2.设置layer的属性(设置了颜色,bounds才能显示出来)  3.将layer添加到界面上(控制器view的layer上) @int ...

  7. swiper不能手指滑动翻页的解决办法

    /*当swiper中的slide的里面放入长度在手机上不能滑动的时候 放入这段代码就可以了*/    var startScroll, touchStart, touchCurrent;        ...

  8. 【TOJ 3660】家庭关系(hash+并查集)

    描述 给定若干家庭成员之间的关系,判断2个人是否属于同一家庭,即2个人之间均可以通过这些关系直接或者间接联系. 输入 输入数据有多组,每组数据的第一行为一个正整数n(1<=n<=100), ...

  9. django项目创建requirements.txt文件

    workon+虚拟环境名 然后输入 pip freeze > requirements.txt

  10. django环境搭建和学习

    由于服务器down了好几天,前几天做的django的project全都在上面,无法继续开展工作,所以决定在本地重新部署一套virtualenv 之前没有好好整理过部署过程(其实也不难),所以决定写个随 ...