博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTful API
阅读量:5891 次
发布时间:2019-06-19

本文共 10165 字,大约阅读时间需要 33 分钟。

上一篇写的是使用静态基类方法的实现步骤:  

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

返回一个对象

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

using System;using SalesApi.Core.Abstractions.DomainModels;namespace SalesApi.ViewModels{    public class CustomerViewModel: EntityBase    {        public string Company { get; set; }        public string Name { get; set; }        public DateTimeOffset EstablishmentTime { get; set; }    }}

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

using System;using System.Collections.Generic;using System.Dynamic;using System.Reflection;namespace SalesApi.Shared.Helpers{    public static class ObjectExtensions    {        public static ExpandoObject ToDynamic
(this TSource source, string fields = null) { if (source == null) { throw new ArgumentNullException("source"); } var dataShapedObject = new ExpandoObject(); if (string.IsNullOrWhiteSpace(fields)) { // 所有的 public properties 应该包含在ExpandoObject里 var propertyInfos = typeof(TSource).GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); foreach (var propertyInfo in propertyInfos) { // 取得源对象上该property的值 var propertyValue = propertyInfo.GetValue(source); // 为ExpandoObject添加field ((IDictionary
)dataShapedObject).Add(propertyInfo.Name, propertyValue); } return dataShapedObject; } // field是使用 "," 分割的, 这里是进行分割动作. var fieldsAfterSplit = fields.Split(','); foreach (var field in fieldsAfterSplit) { var propertyName = field.Trim(); // 使用反射来获取源对象上的property // 需要包括public和实例属性, 并忽略大小写. var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); if (propertyInfo == null) { throw new Exception($"没有在‘{typeof(TSource)}’上找到‘{propertyName}’这个Property"); } // 取得源对象property的值 var propertyValue = propertyInfo.GetValue(source); // 为ExpandoObject添加field ((IDictionary
)dataShapedObject).Add(propertyInfo.Name, propertyValue); } return dataShapedObject; } }}

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

然后在CustomerController里面:

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

private IEnumerable
CreateLinksForCustomer(int id, string fields = null) { var links = new List
(); if (string.IsNullOrWhiteSpace(fields)) { links.Add( new LinkViewModel(_urlHelper.Link("GetCustomer", new { id = id }), "self", "GET")); } else { links.Add( new LinkViewModel(_urlHelper.Link("GetCustomer", new { id = id, fields = fields }), "self", "GET")); } links.Add( new LinkViewModel(_urlHelper.Link("DeleteCustomer", new { id = id }), "delete_customer", "DELETE")); links.Add( new LinkViewModel(_urlHelper.Link("CreateCustomer", new { id = id }), "create_customer", "POST")); return links; }

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

然后修改Get和Post的Action:

[HttpGet]        [Route("{id}", Name = "GetCustomer")]        public async Task
Get(int id, string fields) { var item = await _customerRepository.GetSingleAsync(id); if (item == null) { return NotFound(); } var customerVm = Mapper.Map
(item); var links = CreateLinksForCustomer(id, fields); var dynamicObject = customerVm.ToDynamic(fields) as IDictionary
; dynamicObject.Add("links", links); return Ok(dynamicObject); } [HttpPost(Name = "CreateCustomer")] public async Task
Post([FromBody] CustomerViewModel customerVm) { if (customerVm == null) { return BadRequest(); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var newItem = Mapper.Map
(customerVm); _customerRepository.Add(newItem); if (!await UnitOfWork.SaveAsync()) { return StatusCode(500, "保存时出错"); } var vm = Mapper.Map
(newItem); var links = CreateLinksForCustomer(vm.Id); var dynamicObject = vm.ToDynamic() as IDictionary
; dynamicObject.Add("links", links); return CreatedAtRoute("GetCustomer", new { id = dynamicObject["Id"] }, dynamicObject); }

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

下面测试一下.

POST:

结果:

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

下面试一下GET:

 

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

OK, 效果都如预期.

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

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

services.AddMvc(options =>            {                options.ReturnHttpNotAcceptable = true;                // the default formatter is the first one in the list.                options.OutputFormatters.Remove(new XmlDataContractSerializerOutputFormatter());                // set authorization on all controllers or routes                var policy = new AuthorizationPolicyBuilder()                    .RequireAuthenticatedUser()                    .Build();                options.Filters.Add(new AuthorizeFilter(policy));            })            .AddJsonOptions(options =>            {                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();            })            .AddFluetValidations();

然后再试试:

OK.

 

返回集合

 首先编写创建links的方法:

private IEnumerable
CreateLinksForCustomers(string fields = null) { var links = new List
(); if (string.IsNullOrWhiteSpace(fields)) { links.Add( new LinkViewModel(_urlHelper.Link("GetAllCustomers", new { fields = fields }), "self", "GET")); } else { links.Add( new LinkViewModel(_urlHelper.Link("GetAllCustomers", new { }), "self", "GET")); } return links; }

这个很简单.

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

using System;using System.Collections.Generic;using System.Dynamic;using System.Reflection;namespace SalesApi.Shared.Helpers{    public static class IEnumerableExtensions    {        public static IEnumerable
ToDynamicIEnumerable
(this IEnumerable
source, string fields) { if (source == null) { throw new ArgumentNullException("source"); } var expandoObjectList = new List
(); var propertyInfoList = new List
(); if (string.IsNullOrWhiteSpace(fields)) { var propertyInfos = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance); propertyInfoList.AddRange(propertyInfos); } else { var fieldsAfterSplit = fields.Split(','); 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($"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
)dataShapedObject).Add(propertyInfo.Name, propertyValue); } expandoObjectList.Add(dataShapedObject); } return expandoObjectList; } }}

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

然后修改GetAll方法:

[HttpGet(Name = "GetAllCustomers")]        public async Task
GetAll(string fields) { var items = await _customerRepository.GetAllAsync(); var results = Mapper.Map
>(items); var dynamicList = results.ToDynamicIEnumerable(fields); var links = CreateLinksForCustomers(fields); var dynamicListWithLinks = dynamicList.Select(customer => { var customerDictionary = customer as IDictionary
; var customerLinks = CreateLinksForCustomer( (int)customerDictionary["Id"], fields); customerDictionary.Add("links", customerLinks); return customerDictionary; }); var resultWithLink = new { Value = dynamicListWithLinks, Links = links }; return Ok(resultWithLink); }

 

红色部分是相关代码.

测试一下:

不选择属性:

选择部分属性:

OK.

 

HATEOAS这部分就写到这.

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

 

下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:

转载地址:http://azbsx.baihongyu.com/

你可能感兴趣的文章
一个IO的传奇一生(2)
查看>>
linux文件描述符
查看>>
C++ const 详解
查看>>
给Github上的项目添加开源协议
查看>>
imx53 start board 开箱照
查看>>
免费的编程中文书籍索引
查看>>
struts2引入标签时遇到的问题
查看>>
Hibernate例子-自己写的一个干净的给予注解的Hibernate例子
查看>>
WorkFlow入门Step.6—Building a Console Application -For-WF4.0
查看>>
sota系统服务进程的启动与停止(章节:4.2)
查看>>
CentOS6.3搭建Samba服务器
查看>>
java线程安全
查看>>
数据结构--排序
查看>>
Fix Project Properties.
查看>>
dom4j相当全的资料
查看>>
搭建SpringMVC开发环境
查看>>
二叉树的镜像
查看>>
HTTP 报文详解
查看>>
Active Directory 基础回顾 (一) 如何理解group的类型
查看>>
awk和sed字符串处理速度比较与处理速度的测试方法
查看>>