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

使用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<TSource>(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<string, object>)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<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
} return dataShapedObject;
}
}
}

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

然后在CustomerController里面:

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

        private IEnumerable<LinkViewModel> CreateLinksForCustomer(int id, string fields = null)
{
var links = new List<LinkViewModel>();
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<IActionResult> Get(int id, string fields)
{
var item = await _customerRepository.GetSingleAsync(id);
if (item == null)
{
return NotFound();
}
var customerVm = Mapper.Map<CustomerViewModel>(item);
var links = CreateLinksForCustomer(id, fields);
var dynamicObject = customerVm.ToDynamic(fields) as IDictionary<string, object>;
dynamicObject.Add("links", links);
return Ok(dynamicObject);
} [HttpPost(Name = "CreateCustomer")]
public async Task<IActionResult> Post([FromBody] CustomerViewModel customerVm)
{
if (customerVm == null)
{
return BadRequest();
} if (!ModelState.IsValid)
{
return BadRequest(ModelState);
} var newItem = Mapper.Map<Customer>(customerVm);
_customerRepository.Add(newItem);
if (!await UnitOfWork.SaveAsync())
{
return StatusCode(, "保存时出错");
} var vm = Mapper.Map<CustomerViewModel>(newItem); var links = CreateLinksForCustomer(vm.Id);
var dynamicObject = vm.ToDynamic() as IDictionary<string, object>;
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<LinkViewModel> CreateLinksForCustomers(string fields = null)
{
var links = new List<LinkViewModel>();
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<ExpandoObject> ToDynamicIEnumerable<TSource>(this IEnumerable<TSource> source, string fields)
{
if (source == null)
{
throw new ArgumentNullException("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(',');
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<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
}
expandoObjectList.Add(dataShapedObject);
} return expandoObjectList;
}
}
}

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

然后修改GetAll方法:

        [HttpGet(Name = "GetAllCustomers")]
public async Task<IActionResult> GetAll(string fields)
{
var items = await _customerRepository.GetAllAsync();
var results = Mapper.Map<IEnumerable<CustomerViewModel>>(items);
var dynamicList = results.ToDynamicIEnumerable(fields);
var links = CreateLinksForCustomers(fields);
var dynamicListWithLinks = dynamicList.Select(customer =>
{
var customerDictionary = customer as IDictionary<string, object>;
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结构. 有空我再写一个翻页的吧.

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

  1. 使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTful 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. Unreachable statement

    public boolean onQueryTextSubmit(String s) { if (sv != null) { // 得到输入管理对象 InputMethodManager imm = ...

  2. SQL Server将自己的查询结果作为待查询数据子列之一

    嵌套子查询是SQL语句中比较常用的一种查询方法,开发过程中遇到查询需要将自己的某列作为待查询的数据,在参考别人的SQL语句的写法终于实现了自己需要的功能. 查询语句如下: SELECT DISTINC ...

  3. Intellij IDEA查看所有断点

    项目中打的断点太多,有时自己也想不到打在哪里了,也不知道哪些方法.哪些代码行上打了断点,在IDEA中如何查看所有断点呢? 方法如下: step 1 IDEA的debug面板中有一个名称为View Br ...

  4. JVM内存越多,能创建的线程越少,越容易发生java.lang.OutOfMemoryError: unable to create new native thread。

    一.认识问题: 首先我们通过下面这个 测试程序 来认识这个问题:运行的环境 (有必要说明一下,不同环境会有不同的结果):32位 Windows XP,Sun JDK 1.6.0_18, eclipse ...

  5. 记一次 synchronized 锁字符串引发的坑兼再谈 Java 字符串

    业务有一个需求,我把问题描述一下: 通过代理IP访问国外某网站N,每个IP对应一个固定的网站N的COOKIE,COOKIE有失效时间. 并发下,取IP是有一定策略的,取到IP之后拿IP对应的COOKI ...

  6. 20165226 预备作业3 Linux安装及学习

    20165226 预备作业3 Linux安装及学习 Linux安装 一.下载安装VirtualBox 1.首先在官网下载,点开基于VirtualBox虚拟机安装Ubuntu图文教程 找到版本进行下载安 ...

  7. TypeScript入门(一)

    TypeScript是微软官方的一种语言,是JavaScript的超集.它遵循的ECMA Script 6.0是下一代的JavaScript.浏览器还没有完全支持ES6,而ES5是弱类型的语言,还没有 ...

  8. C语言最后一次作业--总结报告

    1.当初你是如何做出选择计算机专业的决定的? 经过一个学期,你的看法改变了么,为什么? 你觉得计算机是你喜欢的领域吗,它是你擅长的领域吗? 为什么? 当时选择计算机专业,是基于自己的高考分数和想出省的 ...

  9. Microsoft AI - Custom Vision in C#

    概述 前面一篇 Microsoft AI - Custom Vision 中,我们介绍了 Azure 认知服务中的自定义影像服务:Custom Vision,也介绍了如果通过这个在线服务,可视化的完成 ...

  10. USB Audio设计与实现

    1 前言 本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子. 2 设计构思 所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,P ...