上一篇写的是使用静态基类方法的实现步骤:  http://www.cnblogs.com/cgzl/p/8726805.html

使用dynamic (ExpandoObject)的好处就是可以动态组建返回类型, 之前使用的是ViewModel, 如果想返回结果的话, 肯定需要把ViewModel所有的属性都返回, 如果属性比较多, 就有可能造成性能和灵活性等问题. 而使用ExpandoObject(dynamic)就可以解决这个问题.

返回一个对象

返回一个dynamic类型的对象, 需要把所需要的属性从ViewModel抽取出来并转化成dynamic对象, 这里所需要的属性通常是从参数传进来的, 例如针对下面的CustomerViewModel类, 参数可能是这样的: "Name, Company":

  1. using System;
  2. using SalesApi.Core.Abstractions.DomainModels;
  3.  
  4. namespace SalesApi.ViewModels
  5. {
  6. public class CustomerViewModel: EntityBase
  7. {
  8. public string Company { get; set; }
  9. public string Name { get; set; }
  10. public DateTimeOffset EstablishmentTime { get; set; }
  11. }
  12. }

还需要一个Extension Method可以把对象按照需要的属性转化成dynamic类型:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Dynamic;
  4. using System.Reflection;
  5.  
  6. namespace SalesApi.Shared.Helpers
  7. {
  8. public static class ObjectExtensions
  9. {
  10. public static ExpandoObject ToDynamic<TSource>(this TSource source, string fields = null)
  11. {
  12. if (source == null)
  13. {
  14. throw new ArgumentNullException("source");
  15. }
  16.  
  17. var dataShapedObject = new ExpandoObject();
  18. if (string.IsNullOrWhiteSpace(fields))
  19. {
  20. // 所有的 public properties 应该包含在ExpandoObject里
  21. var propertyInfos = typeof(TSource).GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
  22. foreach (var propertyInfo in propertyInfos)
  23. {
  24. // 取得源对象上该property的值
  25. var propertyValue = propertyInfo.GetValue(source);
  26. // 为ExpandoObject添加field
  27. ((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
  28. }
  29. return dataShapedObject;
  30. }
  31.  
  32. // field是使用 "," 分割的, 这里是进行分割动作.
  33. var fieldsAfterSplit = fields.Split(',');
  34. foreach (var field in fieldsAfterSplit)
  35. {
  36. var propertyName = field.Trim();
  37.  
  38. // 使用反射来获取源对象上的property
  39. // 需要包括public和实例属性, 并忽略大小写.
  40. var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
  41. if (propertyInfo == null)
  42. {
  43. throw new Exception($"没有在‘{typeof(TSource)}’上找到‘{propertyName}’这个Property");
  44. }
  45.  
  46. // 取得源对象property的值
  47. var propertyValue = propertyInfo.GetValue(source);
  48. // 为ExpandoObject添加field
  49. ((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
  50. }
  51.  
  52. return dataShapedObject;
  53. }
  54. }
  55. }

注意: 这里的逻辑是如果没有选择需要的属性的话, 那么就返回所有合适的属性.

然后在CustomerController里面:

首先创建为对象添加link的方法:

  1. private IEnumerable<LinkViewModel> CreateLinksForCustomer(int id, string fields = null)
  2. {
  3. var links = new List<LinkViewModel>();
  4. if (string.IsNullOrWhiteSpace(fields))
  5. {
  6. links.Add(
  7. new LinkViewModel(_urlHelper.Link("GetCustomer", new { id = id }),
  8. "self",
  9. "GET"));
  10. }
  11. else
  12. {
  13. links.Add(
  14. new LinkViewModel(_urlHelper.Link("GetCustomer", new { id = id, fields = fields }),
  15. "self",
  16. "GET"));
  17. }
  18.  
  19. links.Add(
  20. new LinkViewModel(_urlHelper.Link("DeleteCustomer", new { id = id }),
  21. "delete_customer",
  22. "DELETE"));
  23.  
  24. links.Add(
  25. new LinkViewModel(_urlHelper.Link("CreateCustomer", new { id = id }),
  26. "create_customer",
  27. "POST"));
  28.  
  29. return links;
  30. }

针对返回一个对象, 添加了本身的连接, 添加的连接 以及 删除的连接.

然后修改Get和Post的Action:

  1. [HttpGet]
  2. [Route("{id}", Name = "GetCustomer")]
  3. public async Task<IActionResult> Get(int id, string fields)
  4. {
  5. var item = await _customerRepository.GetSingleAsync(id);
  6. if (item == null)
  7. {
  8. return NotFound();
  9. }
  10. var customerVm = Mapper.Map<CustomerViewModel>(item);
  11. var links = CreateLinksForCustomer(id, fields);
  12. var dynamicObject = customerVm.ToDynamic(fields) as IDictionary<string, object>;
  13. dynamicObject.Add("links", links);
  14. return Ok(dynamicObject);
  15. }
  16.  
  17. [HttpPost(Name = "CreateCustomer")]
  18. public async Task<IActionResult> Post([FromBody] CustomerViewModel customerVm)
  19. {
  20. if (customerVm == null)
  21. {
  22. return BadRequest();
  23. }
  24.  
  25. if (!ModelState.IsValid)
  26. {
  27. return BadRequest(ModelState);
  28. }
  29.  
  30. var newItem = Mapper.Map<Customer>(customerVm);
  31. _customerRepository.Add(newItem);
  32. if (!await UnitOfWork.SaveAsync())
  33. {
  34. return StatusCode(500, "保存时出错");
  35. }
  36.  
  37. var vm = Mapper.Map<CustomerViewModel>(newItem);
  38.  
  39. var links = CreateLinksForCustomer(vm.Id);
  40. var dynamicObject = vm.ToDynamic() as IDictionary<string, object>;
  41. dynamicObject.Add("links", links);
  42. return CreatedAtRoute("GetCustomer", new { id = dynamicObject["Id"] }, dynamicObject);
  43. }

红色部分是相关的代码. 创建links之后把vm对象按照需要的属性转化成dynamic对象. 然后往这个dynamic对象里面添加links属性. 最后返回该对象.

下面测试一下.

POST:

结果:

由于POST方法里面没有选择任何fields, 所以返回所有的属性.

下面试一下GET:

再试一下GET, 选择几个fields:

OK, 效果都如预期.

但是有一个问题, 因为返回的json的Pascal case的(只有dynamic对象返回的是Pascal case, 其他ViewModel现在返回的都是camel case的), 而camel case才是更好的选择 .

所以在Startup里面可以这样设置:

  1. services.AddMvc(options =>
  2. {
  3. options.ReturnHttpNotAcceptable = true;
  4. // the default formatter is the first one in the list.
  5. options.OutputFormatters.Remove(new XmlDataContractSerializerOutputFormatter());
  6.  
  7. // set authorization on all controllers or routes
  8. var policy = new AuthorizationPolicyBuilder()
  9. .RequireAuthenticatedUser()
  10. .Build();
  11. options.Filters.Add(new AuthorizeFilter(policy));
  12. })
  13. .AddJsonOptions(options =>
  14. {
  15. options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
  16. })
  17. .AddFluetValidations();

然后再试试:

OK.

返回集合

首先编写创建links的方法:

  1. private IEnumerable<LinkViewModel> CreateLinksForCustomers(string fields = null)
  2. {
  3. var links = new List<LinkViewModel>();
  4. if (string.IsNullOrWhiteSpace(fields))
  5. {
  6. links.Add(
  7. new LinkViewModel(_urlHelper.Link("GetAllCustomers", new { fields = fields }),
  8. "self",
  9. "GET"));
  10. }
  11. else
  12. {
  13. links.Add(
  14. new LinkViewModel(_urlHelper.Link("GetAllCustomers", new { }),
  15. "self",
  16. "GET"));
  17. }
  18. return links;
  19. }

这个很简单.

然后需要针对IEnumerable<T>类型创建把ViewModel转化成dynamic对象的Extension方法:

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

注意: 反射的开销很大, 注意性能.

然后修改GetAll方法:

  1. [HttpGet(Name = "GetAllCustomers")]
  2. public async Task<IActionResult> GetAll(string fields)
  3. {
  4. var items = await _customerRepository.GetAllAsync();
  5. var results = Mapper.Map<IEnumerable<CustomerViewModel>>(items);
  6. var dynamicList = results.ToDynamicIEnumerable(fields);
  7. var links = CreateLinksForCustomers(fields);
  8. var dynamicListWithLinks = dynamicList.Select(customer =>
  9. {
  10. var customerDictionary = customer as IDictionary<string, object>;
  11. var customerLinks = CreateLinksForCustomer(
  12. (int)customerDictionary["Id"], fields);
  13. customerDictionary.Add("links", customerLinks);
  14. return customerDictionary;
  15. });
  16. var resultWithLink = new {
  17. Value = dynamicListWithLinks,
  18. Links = links
  19. };
  20. return Ok(resultWithLink);
  21. }

红色部分是相关代码.

测试一下:

不选择属性:

选择部分属性:

OK.

HATEOAS这部分就写到这.

其实 翻页的逻辑很适合使用HATEOAS结构. 有空我再写一个翻页的吧.

使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTful API的更多相关文章

  1. 使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTtful API

    上一篇写的是使用静态基类方法的实现步骤:  http://www.cnblogs.com/cgzl/p/8726805.html 使用dynamic (ExpandoObject)的好处就是可以动态组 ...

  2. 【ASP.NET Core】体验一下 Mini Web API

    在上一篇水文中,老周给大伙伴们简单演示了通过 Socket 编程的方式控制 MPD (在树莓派上).按照计划,老周还想给大伙伴们演示一下使用 Web API 来封装对 MPD 控制.思路很 Easy, ...

  3. 使用Http-Repl工具测试ASP.NET Core 2.2中的Web Api项目

    今天,Visual Studio中没有内置工具来测试WEB API.使用浏览器,只能测试http GET请求.您需要使用Postman,SoapUI,Fiddler或Swagger等第三方工具来执行W ...

  4. ASP.NET Core项目目录结构介绍

    我们下面通过在Visual Studio 2017中创建一个空的Web应用程序来详细说明下asp.net core项目目录结构: 1.项目结构说明 (1).依赖项 这里主要分两部分SDK, 目前这两部 ...

  5. ASP.NET Core 中文文档 第二章 指南(2)用 Visual Studio 和 ASP.NET Core MVC 创建首个 Web API

    原文:Building Your First Web API with ASP.NET Core MVC and Visual Studio 作者:Mike Wasson 和 Rick Anderso ...

  6. ASP.NET Core的身份认证框架IdentityServer4--(2)API跟WEB端配置

    API配置 可以使用ASP.NET Core Web API模板.同样,我们建议您控制端口并使用与之前一样的方法来配置Kestrel和启动配置文件.端口配置为http://localhost:5001 ...

  7. 为什么 web 开发人员需要迁移到. NET Core, 并使用 ASP.NET Core MVC 构建 web 和 webservice/API

    2018 .NET开发者调查报告: .NET Core 是怎么样的状态,这里我们看到了还有非常多的.net开发人员还在观望,本文给大家一个建议.这仅代表我的个人意见, 我有充分的理由推荐.net 程序 ...

  8. 在ASP.NET Core MVC中构建简单 Web Api

    Getting Started 在 ASP.NET Core MVC 框架中,ASP.NET 团队为我们提供了一整套的用于构建一个 Web 中的各种部分所需的套件,那么有些时候我们只需要做一个简单的 ...

  9. 在ASP.NET Core 2.2 中创建 Web API并结合Swagger

    一.创建 ASP.NET Core WebApi项目 二.添加 三. ----------------------------------------------------------- 一.创建项 ...

随机推荐

  1. Google PageSpeed Insights : 网站性能优化检测工具

    1 1 https://developers.google.com/speed/pagespeed/insights/ PageSpeed Insights 使您的网页在所有设备上都能快速加载. 分析 ...

  2. auto skip function args

    auto skip function args https://repl.it/@xgqfrms/auto-skip-function-args "use strict"; /** ...

  3. 比特币市场活跃,VAST发行在即!

    截至1月25日13:30,BTC合约多空持仓人数比为1.44,市场做多人数占据优势:季度合约基差保持在1255美元上方,永续合约资金费率为正,交割及永续合约持仓总量为19.5亿美元,总体上多军占优:B ...

  4. 全网算力总量暴增,SPC能否成为币圈新宠?

    据最新数据显示,在经历了本周初(1月11日)的下跌之后,比特币市场在本周四(1月14日)终于出现了反弹并试图突破4万美元,重新向4万美元上方发起挑战. 这也让加密市场的生态建设者重拾信心,重新对数字货 ...

  5. 去中心化预言机如何助力NGK DeFi 项目发展?

    早在 2014 年前后,协议智能合约就已经出现了,最初协议很笨重,包含了许多不同的部分,每个部分都是一个单独的智能合约,你需要在区块链本身的协议中添加不同的智能合约,这需要几个月甚至几年的时间,而之后 ...

  6. ubuntu无法连接有线网

    问题描述: ubuntu下仅能连接无线网,不能连接有线网,在有线网的下面是没有选项可供连接. 解决方法: 编辑 /etc/network/interfaces 这个文件 将里面仅仅写两句话 auto ...

  7. HTTP状态响应码解析

    # HTTP响应状态码 ## 1xx:临时响应 #### 表示临时响应并需要请求者继续执行操作的状态代码. 100 **继续**请求者应当继续提出请求.服务器返回此代码表示已收到请求的第一部分,正在等 ...

  8. JQuery:JQuery基本语法,JQuery选择器,JQuery DOM,综合案例 复选框,综合案例 随机图片

    知识点梳理 课堂讲义 1.JQuery快速入门 1.1.JQuery介绍 jQuery 是一个 JavaScript 库. 框架:Mybatis (jar包) 大工具 插件:PageHelper (j ...

  9. TERSUS无代码开发(笔记09)-简单实例前端样式设计

    前端常用样式设计 =========================================================================================== ...

  10. Django Admin 删除文件同时删除资源文件(delete_upload_files)

    一  使用环境 开发系统: windows IDE: pycharm 数据库: msyql,navicat 编程语言: python3.7  (Windows x86-64 executable in ...