参照 草根专栏- 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. java中String、包装类、枚举类的引用传递

    一般情况下,我们认为Java中了除了八种基本数据类型,其他都是对象,进行引用传递: 但是:String.包装类.枚举类作为参数传递后发现,没有达到引用传递的效果,很多人认为它是值传递! 首先,对象肯定 ...

  2. Python 学习笔记(七)Python字符串(四)

    输入输出 输入函数 raw_input (Python3:input) >>> raw_input("请输入一个字母") #获取输入内容的一个函数 请输入一个字母 ...

  3. JavaScript-语法专题

    一.数据类型的转换 概述 JavaScript是一种动态语言,变量没有类型限制,可以随时赋予任意值 强制转换:主要是值Number(),String(),Boolean三个函数 Number函数,可以 ...

  4. 如何在Vue中使用Mockjs模拟数据的增删查改

    之前一直使用json-server在前端开发时,搭建本地数据接口测试,但有时又需要将做好的项目放于 github page上做项目演示.在本地时,json server很好使用,但一旦放在github ...

  5. Keepalived 配置高可用

    VRRP协议及Keepalived原理使用   VRRP 协议即 Virtual Router Redundancy Protocol,虚拟路由器冗余协议, 为了解决局域网内默认网关单点失效的问题.  ...

  6. Scala语法(三)

    模式匹配 1)match val a = 1 val b=a match { *// a match { }返回值赋予变量 b case 1 => "red" case 2 ...

  7. 解决pycharm报错:AttributeError: module 'pip' has no attribute 'main'

    找到pycharm安装目录下 helpers/packaging_tool.py文件,找到如下代码: def do_install(pkgs): try: import pip except Impo ...

  8. Python系列8之socket

    目录 socket 简单的聊天机器人 简单的ftp上传 粘包问题的解决 一. socket模块 socket,俗称套接字,其实就是一个ip地址和端口的组合.类似于这样的形式(ip,  port),其中 ...

  9. DHT11温湿度传感器编程思路以及代码的实现(转载)

    源自:https://blog.csdn.net/qq_34952376/article/details/81193938 在我们刚开始进入单片机的学习中,练习写传感器的时序是必不可少的,其实我比较推 ...

  10. C++拷贝构造函数 的理解

    #include <iostream> using namespace std; //拷贝构造函数的理解 class Point { public: Point(); Point(int ...