参照 草根专栏- 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. 【洛谷P1196】[NOI2002]银河英雄传说

    银河英雄传说 题目链接 并查集时记录下以i为首的队列的长度(如果存在这个队列)num[i],便于合并, 和点i到队首的距离front[i],便于查询(在find时维护) #include<ios ...

  2. Tomcat 服务器体系结构

    connector 监听端口,监听到以后,交给 Engine 引擎 处理,引擎会根据请求找到对应的主机,找到主机后再去找对应的应用. 如果我们将 port 改为 80,那访问的时候就不用输入端口号,因 ...

  3. GOPL第三章练习题3.3 3.4代码

    练习3.3是peak展示为红色,valley展示为蓝色. 练习3.4是将svg图像打印到浏览器中. // Copyright © 2016 Alan A. A. Donovan & Brian ...

  4. 微软.net framework 源码学习

    1. 直接下载.NET Framework源代码(下载地址),然后用Visual Studio打开查看. 2. 在线查看,网址:http://referencesource.microsoft.com ...

  5. Docker官方文档翻译2

    转载请标明出处: https://blog.csdn.net/forezp/article/details/80158062 本文出自方志朋的博客 容器 准备工作 安装Docker,版本为1.13或者 ...

  6. Dubbo源码分析之ExtensionLoader加载过程解析

    ExtensionLoader加载机制阅读: Dubbo的类加载机制是模仿jdk的spi加载机制:  Jdk的SPI扩展加载机制:约定是当服务的提供者每增加一个接口的实现类时,需要在jar包的META ...

  7. Aspose.cell C# 操作excel(通过批注信息给单元格赋值、批注信息获取公式得出结果并转PNG)

    if (fileName == "") return ""; var CurrentRow = 0; Workbook work = new Workbook( ...

  8. ABAP术语-Lock Object

    Lock Object 原文:http://www.cnblogs.com/qiangsheng/archive/2008/02/29/1085742.html Object type in the ...

  9. ABAP术语-Customer Exit

    Customer Exit 原文:http://www.cnblogs.com/qiangsheng/archive/2008/01/19/1045231.html Pre-planned enhan ...

  10. MySQL的日志相关内容

    本篇文章介绍一下mysql的备份和日志,由于备份时需要用到日志,所以在讲备份前,如果日志内容篇幅过长,将会把日志和备份分开单独来讲,先简单介绍一下mysql的日志相关内容. MySQL日志 日志是my ...