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

一、Get返回资源塑形

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

  1. namespace BlogDemo.Infrastructure.Extensions
  2. {
  3. public static class EnumerableExtensions
  4. {
  5. public static IEnumerable<ExpandoObject> ToDynamicIEnumerable<TSource>(this IEnumerable<TSource> source, string fields = null)
  6. {
  7. if (source == null)
  8. {
  9. throw new ArgumentNullException(nameof(source));
  10. }
  11.  
  12. var expandoObjectList = new List<ExpandoObject>();
  13. var propertyInfoList = new List<PropertyInfo>();
  14. if (string.IsNullOrWhiteSpace(fields))
  15. {
  16. var propertyInfos = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
  17. propertyInfoList.AddRange(propertyInfos);
  18. }
  19. else
  20. {
  21. var fieldsAfterSplit = fields.Split(',').ToList();
  22. foreach (var field in fieldsAfterSplit)
  23. {
  24. var propertyName = field.Trim();
  25. if (string.IsNullOrEmpty(propertyName))
  26. {
  27. continue;
  28. }
  29. var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
  30. if (propertyInfo == null)
  31. {
  32. throw new Exception($"Property {propertyName} wasn't found on {typeof(TSource)}");
  33. }
  34. propertyInfoList.Add(propertyInfo);
  35. }
  36. }
  37.  
  38. foreach (TSource sourceObject in source)
  39. {
  40. var dataShapedObject = new ExpandoObject();
  41. foreach (var propertyInfo in propertyInfoList)
  42. {
  43. var propertyValue = propertyInfo.GetValue(sourceObject);
  44. ((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
  45. }
  46. expandoObjectList.Add(dataShapedObject);
  47. }
  48.  
  49. return expandoObjectList;
  50. }
  51. }
  52. }
  1. namespace BlogDemo.Infrastructure.Extensions
  2. {
  3. public static class ObjectExtensions
  4. {
  5. public static ExpandoObject ToDynamic<TSource>(this TSource source, string fields = null)
  6. {
  7. if (source == null)
  8. {
  9. throw new ArgumentNullException(nameof(source));
  10. }
  11.  
  12. var dataShapedObject = new ExpandoObject();
  13. if (string.IsNullOrWhiteSpace(fields))
  14. {
  15. var propertyInfos = typeof(TSource).GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
  16. foreach (var propertyInfo in propertyInfos)
  17. {
  18. var propertyValue = propertyInfo.GetValue(source);
  19. ((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
  20. }
  21. return dataShapedObject;
  22. }
  23. var fieldsAfterSplit = fields.Split(',').ToList();
  24. foreach (var field in fieldsAfterSplit)
  25. {
  26. var propertyName = field.Trim();
  27. var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
  28. if (propertyInfo == null)
  29. {
  30. throw new Exception($"Can't found property ¡®{typeof(TSource)}¡¯ on ¡®{propertyName}¡¯");
  31. }
  32. var propertyValue = propertyInfo.GetValue(source);
  33. ((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
  34. }
  35.  
  36. return dataShapedObject;
  37. }
  38. }
  39. }

2、Controller修改Action方法:

(1) 集合塑形:

  1. [HttpGet(Name = "GetPosts")]
  2. public async Task<IActionResult> Get(PostParameters parameters)
  3. {
  4. var posts = await _postRepository.GetPostsAsync(parameters);
  5. var postDto=_mapper.Map<IEnumerable<Post>,IEnumerable<PostDTO>>(posts);
  6.  
  7. var shapePostDTO= postDto.ToDynamicIEnumerable(parameters.Fields);
  8.  
  9. var previousPageLink = posts.HasPrevious ?
  10. CreatePostUri(parameters, PaginationResourceUriType.PreviousPage) : null;
  11.  
  12. var nextPageLink = posts.HasNext ?
  13. CreatePostUri(parameters, PaginationResourceUriType.NextPage) : null;
  14. var meta = new
  15. {
  16. PageSize = posts.PageSize,
  17. PageIndex = posts.PageIndex,
  18. TotalItemCount = posts.TotalItemsCount,
  19. PageCount = posts.PageCount,
  20. previousPageLink,
  21. nextPageLink
  22. };
  23. Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(meta, new JsonSerializerSettings
  24. {
  25. ContractResolver = new CamelCasePropertyNamesContractResolver()
  26. }));
  27.  
  28. return Ok(shapePostDTO);
  29. }

(2)单个塑形:

  1. [HttpGet("{Id}")]
  2. public async Task<IActionResult> Get(int Id,string fields=null)
  3. {
  4.  
  5. var post = await _postRepository.GetPostId(Id);
  6. if(post==null)
  7. {
  8. return NotFound();
  9. }
  10. var postDTO = _mapper.Map<Post, PostDTO>(post);
  11. var shapePostDTO = postDTO.ToDynamic(fields);
  12. return Ok(shapePostDTO);
  13.  
  14. }

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

  1. services.AddMvc(option => {
  2. option.ReturnHttpNotAcceptable = true;
  3. option.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
  4. }).AddJsonOptions(options=> {
  5. options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
  6. });

4、Postman测试:

(1)集合塑形

(2)单个塑形:

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

  1. //验证排序属性映射是否存在
  2. if (!_propertyMappingContainer.ValidateMappingExistsFor<PostDTO, Post>(parameters.OrderBy))
  3. {
  4. return BadRequest("Can't finds fields for sorting.");
  5. }
  6.  
  7. //验证Filed是否存在
  8. if (!_typeHelperService.TypeHasProperties<PostDTO>(parameters.Fields))
  9. {
  10. return BadRequest("Filed not exits");
  11. }
  1. 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 类

  1. namespace BlogDemo.Infrastructure.Resources
  2. {
  3. public class LinkResource
  4. {
  5. public LinkResource(string href, string rel, string method)
  6. {
  7. Href = href;
  8. Rel = rel;
  9. Method = method;
  10. }
  11.  
  12. public string Href { get; set; }
  13. public string Rel { get; set; }
  14. public string Method { get; set; }
  15. }
  16. }

(2)单个对象

Controller中添加 CreateLinksForPost()  方法

  1. private IEnumerable<LinkResource> CreateLinksForPost(int id, string fields = null)
  2. {
  3. var links = new List<LinkResource>();
  4.  
  5. if (string.IsNullOrWhiteSpace(fields))
  6. {
  7. links.Add(
  8. new LinkResource(
  9. _urlHelper.Link("GetPost", new { id }), "self", "GET"));
  10. }
  11. else
  12. {
  13. links.Add(
  14. new LinkResource(
  15. _urlHelper.Link("GetPost", new { id, fields }), "self", "GET"));
  16. }
  17.  
  18. links.Add(
  19. new LinkResource(
  20. _urlHelper.Link("DeletePost", new { id }), "delete_post", "DELETE"));
  21.  
  22. return links;
  23. }
  1. [HttpGet("{Id}", Name = "GetPost")]
  2. public async Task<IActionResult> Get(int Id,string fields=null)
  3. {
  4. //验证Filed是否存在
  5. if (!_typeHelperService.TypeHasProperties<PostDTO>(fields))
  6. {
  7. return BadRequest("Filed not exits");
  8. }
  9. var post = await _postRepository.GetPostId(Id);
  10. if(post==null)
  11. {
  12. return NotFound();
  13. }
  14. var postDTO = _mapper.Map<Post, PostDTO>(post);
  15. var shapePostDTO = postDTO.ToDynamic(fields);
  16. var links = CreateLinksForPost(Id, fields);
  17.  
  18. var result = (IDictionary<string, object>)shapePostDTO;
  19.  
  20. result.Add("links", links);
  21. return Ok(result);
  22.  
  23. }

(3)集合对象

在Controller中添加  CreateLinksForPosts()  方法:

  1. private IEnumerable<LinkResource> CreateLinksForPosts(PostParameters postResourceParameters,
  2. bool hasPrevious, bool hasNext)
  3. {
  4. var links = new List<LinkResource>
  5. {
  6. new LinkResource(
  7. CreatePostUri(postResourceParameters, PaginationResourceUriType.CurrentPage),
  8. "self", "GET")
  9. };
  10.  
  11. if (hasPrevious)
  12. {
  13. links.Add(
  14. new LinkResource(
  15. CreatePostUri(postResourceParameters, PaginationResourceUriType.PreviousPage),
  16. "previous_page", "GET"));
  17. }
  18.  
  19. if (hasNext)
  20. {
  21. links.Add(
  22. new LinkResource(
  23. CreatePostUri(postResourceParameters, PaginationResourceUriType.NextPage),
  24. "next_page", "GET"));
  25. }
  26.  
  27. return links;
  28. }

      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类

  1. namespace BlogDemo.Api.Helpers
  2. {
  3. [AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
  4. public class RequestHeaderMatchingMediaTypeAttribute : Attribute, IActionConstraint
  5. {
  6. private readonly string _requestHeaderToMatch;
  7. private readonly string[] _mediaTypes;
  8.  
  9. public RequestHeaderMatchingMediaTypeAttribute(string requestHeaderToMatch, string[] mediaTypes)
  10. {
  11. _requestHeaderToMatch = requestHeaderToMatch;
  12. _mediaTypes = mediaTypes;
  13. }
  14.  
  15. public bool Accept(ActionConstraintContext context)
  16. {
  17. var requestHeaders = context.RouteContext.HttpContext.Request.Headers;
  18. if (!requestHeaders.ContainsKey(_requestHeaderToMatch))
  19. {
  20. return false;
  21. }
  22.  
  23. foreach (var mediaType in _mediaTypes)
  24. {
  25. var mediaTypeMatches = string.Equals(requestHeaders[_requestHeaderToMatch].ToString(),
  26. mediaType, StringComparison.OrdinalIgnoreCase);
  27. if (mediaTypeMatches)
  28. {
  29. return true;
  30. }
  31. }
  32.  
  33. return false;
  34. }
  35.  
  36. public int Order { get; } = ;
  37. }
  38. }

(2)注册自定义mediatype

  1. services.AddMvc(option => {
  2. option.ReturnHttpNotAcceptable = true;
  3. // option.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
  4. var outputFormatter = option.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
  5. if(outputFormatter!=null)
  6. {
  7. outputFormatter.SupportedMediaTypes.Add("application/vnd.cfy.hateoas+json");
  8. }
  9.  
  10. })

(3)修改Action

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

  1. [HttpGet(Name = "GetPosts")]
  2. [RequestHeaderMatchingMediaType("Accept", new[] { "application/vnd.cgzl.hateoas+json" })]
  3. public async Task<IActionResult> GetHateoas(PostParameters parameters,[FromHeader(Name ="Accept")] string mediaType)
  4. {
  5. //验证排序属性映射是否存在
  6. if (!_propertyMappingContainer.ValidateMappingExistsFor<PostDTO, Post>(parameters.OrderBy))
  7. {
  8. return BadRequest("Can't finds fields for sorting.");
  9. }
  10.  
  11. //验证Filed是否存在
  12. if (!_typeHelperService.TypeHasProperties<PostDTO>(parameters.Fields))
  13. {
  14. return BadRequest("Filed not exits");
  15. }
  16.  
  17. var posts = await _postRepository.GetPostsAsync(parameters);
  18. var postDto=_mapper.Map<IEnumerable<Post>,IEnumerable<PostDTO>>(posts);
  19.  
  20. var shapePostDTO = postDto.ToDynamicIEnumerable(parameters.Fields);
  21. var previousPageLink = posts.HasPrevious ?
  22. CreatePostUri(parameters, PaginationResourceUriType.PreviousPage) : null;
  23.  
  24. var nextPageLink = posts.HasNext ?
  25. CreatePostUri(parameters, PaginationResourceUriType.NextPage) : null;
  26.  
  27. var shapedWithLinks = shapePostDTO.Select(x =>
  28. {
  29. var dict = x as IDictionary<string, object>;
  30. var postLinks = CreateLinksForPost((int)dict["Id"], parameters.Fields);
  31. dict.Add("links", postLinks);
  32. return dict;
  33. });
  34. var links = CreateLinksForPosts(parameters, posts.HasPrevious, posts.HasNext);
  35. var result = new
  36. {
  37. value = shapedWithLinks,
  38. links
  39. };
  40.  
  41. var meta = new
  42. {
  43. PageSize = posts.PageSize,
  44. PageIndex = posts.PageIndex,
  45. TotalItemCount = posts.TotalItemsCount,
  46. PageCount = posts.PageCount,
  47. previousPageLink,
  48. nextPageLink
  49. };
  50. Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(meta, new JsonSerializerSettings
  51. {
  52. ContractResolver = new CamelCasePropertyNamesContractResolver()
  53. }));
  54.  
  55. return Ok(result);
  56. }

 --> MediaType="application/json"

  1. [HttpGet(Name = "GetPosts")]
  2. [RequestHeaderMatchingMediaType("Accept", new[] { "application/json" })]
  3. public async Task<IActionResult> Get(PostParameters postParameters)
  4. {
  5. if (!_propertyMappingContainer.ValidateMappingExistsFor<PostDTO, Post>(postParameters.OrderBy))
  6. {
  7. return BadRequest("Can't finds fields for sorting.");
  8. }
  9.  
  10. if (!_typeHelperService.TypeHasProperties<PostDTO>(postParameters.Fields))
  11. {
  12. return BadRequest("Fields not exist.");
  13. }
  14.  
  15. var postList = await _postRepository.GetPostsAsync(postParameters);
  16.  
  17. var postResources = _mapper.Map<IEnumerable<Post>, IEnumerable<PostDTO>>(postList);
  18.  
  19. var previousPageLink = postList.HasPrevious ?
  20. CreatePostUri(postParameters,
  21. PaginationResourceUriType.PreviousPage) : null;
  22.  
  23. var nextPageLink = postList.HasNext ?
  24. CreatePostUri(postParameters,
  25. PaginationResourceUriType.NextPage) : null;
  26.  
  27. var meta = new
  28. {
  29. postList.TotalItemsCount,
  30. postList.PageSize,
  31. postList.PageIndex,
  32. postList.PageCount,
  33. previousPageLink,
  34. nextPageLink
  35. };
  36.  
  37. Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(meta, new JsonSerializerSettings
  38. {
  39. ContractResolver = new CamelCasePropertyNamesContractResolver()
  40. }));
  41.  
  42. return Ok(postResources.ToDynamicIEnumerable(postParameters.Fields));
  43. }

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. 【luogu P1343 地震逃生】 题解

    题目链接:https://www.luogu.org/problemnew/show/P1343 菜 #include <queue> #include <cstdio> #i ...

  2. HTML5 小实例

    1.时钟: <!doctype html> <html> <head></head> <body> <canvas id=" ...

  3. mssql数据库迁移到mysql

    使用mysql migration toolkit工具来进行迁移.(需要安装jdk6 java的安装包) 发现数据量大的表却没能迁过来.软件使用比较容易,配置下源数据库信息,和目标数据库信息就可以进行 ...

  4. centos 安装配置 rabbitmq 以及nginx转发

    安装erlang cd /tmp wget http://erlang.org/download/otp_src_18.3.tar.gz . cd /opt/otp_src_18. yum -y in ...

  5. 课时92.CSS元素显示模式转换(掌握)

    我们之前学习的显示模式都可以不用记忆,因为这节课我们要学习转换,我们可以任意来进行一个转换的,上面这些东西有一个了解就行了.所有的标签都有一个属性叫做display,display的中文含义就是显示的 ...

  6. zepto 基础知识(4)

    61.prev prev() 类型:collection prev(selector) 类型:collection 获取对相集合中每一个元素的钱一个兄弟节点,通过选择器来进行过滤 62.prev pr ...

  7. Lucene作为一个全文检索引擎

    Lucene作为一个全文检索引擎,其具有如下突出的优点: (1)索引文件格式独立于应用平台.Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件. ...

  8. git merge最简洁

    一.开发分支(dev)上的代码达到上线的标准后,要合并到 master 分支 git checkout devgit pullgit checkout mastergit merge devgit p ...

  9. dedesmc 手机端生成静态页

    dedesmc 手机端生成静态页 1.首先下载插件,下载地址:https://pan.baidu.com/s/1Nfx_KBYuxRkZ7VzoPxy28g 密码:83x7 2.进入 dedecms ...

  10. 源码安装CentOs7下的PHP7

    首先安装APACHE环境,直接用yum安装 yum install httpd httpd-devel /etc/httpd/ systemctl start httpd.service #启动apa ...