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

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

使用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(500, "保存时出错");

            }


            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结构. 有空我再写一个翻页的吧.

原文地址 https://www.cnblogs.com/cgzl/p/8745631.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321709.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 BenchmarkDotnet 测试代码性能

先来点题外话&#xff0c;清明节前把工作辞了&#xff08;去 tm 的垃圾团队&#xff0c;各种拉帮结派、勾心斗角&#xff09;。这次找工作就得慢慢找了&#xff0c;不能急了&#xff0c;希望能找到个好团队&#xff0c;好岗位吧。顺便这段时间也算是比较闲&#xff0c;也能学习…

2017西安交大ACM小学期数论 [阅兵式]

阅兵式 发布时间: 2017年6月25日 12:53 最后更新: 2017年7月3日 09:27 时间限制: 1000ms 内存限制: 128M 描述 阅兵式上&#xff0c;将士们排成一个整齐的方阵&#xff0c;每个将士面朝前方。问正中心的将士能向前看到几个将士&#xff1f;注意&#xff0c;一条直线上的将…

28、jdbc操作数据库(5)

介绍一个稍微封装了jdbc的工具类org.apache.commons.dbutils&#xff0c;使用dbutils可以简化对数据库操作程序的开发。 API介绍 接下来通过实例的方式说一下dbutils的具体使用 添加jar包&#xff1a;commons-dbutils-1.7.jar 增、删、改 进行增、删、改操作&#xff0c;在…

2017西安交大ACM小学期数论 [等差数列]

等差数列 发布时间: 2017年6月25日 13:42 最后更新: 2017年7月3日 09:27 时间限制: 1000ms 内存限制: 128M 描述 给定正整数n&#xff0c;试问存在多少个和为n的等差数列&#xff1f; 当然&#xff0c;等差数列中每一项要为非负整数&#xff0c;且不考虑降序的等差数列。…

上古时期(大雾)的数据结构pdf

分块点分治Treap byWYCby\ WYCby WYC Part1 分块 概念 就是将nnn个数分成若干个块&#xff0c;然后要处理的时候整块一起的加上局部的直接暴力。 如果将块的大小分配好一般每次都是O(n)O(\sqrt n)O(n​)的。 而且因为十分暴力&#xff0c;所以有很多优秀的性质。 实现方法 …

33、JAVA_WEB开发基础之会话机制

会话是什么 一个客户端浏览器与web服务器之间连续发生的一系列请求和响应过程就是会话&#xff0c;这些过程中产生的一系列信息就是会话信息&#xff0c;会话机制就是用于维护这些信息一致性的一种技术。通俗的说就是&#xff0c;一个A账号访问服务器&#xff0c;进行多次交互…

35、JAVA_WEB开发基础之过滤器

是什么 过滤器javaweb的一个重要组件&#xff0c;一种规范&#xff0c;可以对发送到serlvet的请求进行拦截和响应进行过滤。实际开发中可以使用过滤器来对访问服务器的请求进行过滤&#xff0c;以提高安全性 过滤器的原理 可以配置过滤器对指定的请求进行过滤&#xff0c;就…

2、安装和连接mysql

安装mysql 1、官网下载mysql 下载网址&#xff1a;https://www.mysql.com/ 2、解压并配置mysql 解压下载的&#xff08;前提下载的zip版本的mysql&#xff09;mysql安装包&#xff0c;放到指定磁盘 配置环境变量&#xff1a;将mysql下的bin目录的全路径名配置到环境变量的p…

6、mysql中字段

对数据表的操作是比较重要的&#xff0c;在实际开发中&#xff0c;日常做的主要工作就是对数据表的操作 对数据表的操作分为两大部分&#xff1a;操作数据表的结构、操作数据表中的数据 组成数据表的基本单元就是字段&#xff0c;所以&#xff0c;接下来先介绍一下mysql中的字…

在Linux环境下使用Apache部署ASP.NET Core

在前几篇文章中我们一起探讨了如何在Linux环境中安装ASP.NET Core运行时环境及将ASP.NET Core项目部署在Jexus中&#xff0c;这篇文章中我们将探讨如何将ASP.NET Core部署于Apache&#xff08;阿帕奇&#xff09;中。 很幸运能够和大家一起学习和探讨ASP.NET Core本文章运行…

傲娇码农的自我修养

一个热爱自己职业的人一定会对自己的工作充满自豪感&#xff0c;同样&#xff0c;也应该对自己的工作充满热情和自信。对自己的专业能力骄傲而不自满。身为一个码农&#xff0c;如果你热爱自己的工作&#xff0c;我想&#xff0c;你很有可能也是一位傲娇码农。在我的眼里&#…

中国到底有多少个.NET 程序员?都在哪个城市写代码?

中国到底多少个.NET 程序员&#xff0c;对于这个问题&#xff0c;似乎没有一个准确的答案&#xff0c;而且最近很多使用.NET 开发技术的老板在抱怨找不到.NET 开发人员&#xff0c;所以我想基于我的公众号粉丝数据给大家分享下中国的.NET程序员到底有多少&#xff0c;他们也都是…

微软西雅图总部DevOps交流总结

本文转自Study4台湾社区。Study4台湾社区&#xff0c;成立于2011/9/25&#xff0c;希望藉由社群推广的力量&#xff0c;让台下的朋友听到来自不同县市的大师讲课&#xff0c;也让台上年轻一辈的技术传教士能不断的琢磨并且追上大师这是一个社群&#xff0c;社区希望透过分享&am…

C# 快速高效率复制对象另一种方式 表达式树

一、需求在代码中经常会遇到需要把对象复制一遍&#xff0c;或者把属性名相同的值复制一遍。比如&#xff1a;public class Student{public int Id { get; set; }public string Name { get; set; } public int Age { get; set; } }public class StudentSecond{public int Id { …

用C# (.NET Core) 实现抽象工厂设计模式

本文的概念性内容来自深入浅出设计模式一书.上一篇文章讲了简单工厂和工厂方法设计模式 使用的是披萨店的例子. 文将继续使用这个例子, 这里要用到抽象工厂.披萨店的需求变更现在披萨店在各地授权了很多连锁分店, 但是有的分店偷工减料, 使用劣质原料代替标准原料.披萨店老板现…

14、mysql中事务的应用

是什么 事务是一种保护连续操作同时满足&#xff08;实现&#xff09;的一种机制&#xff0c;用来保护数据的完整性&#xff0c;只适用于数据操作&#xff0c;不适用于结构操作&#xff0c;只有 innodb引擎的表具有事务安全的机制。就是说&#xff0c;在一个事务中做一系列的…

Summer Training day4 欧拉降幂

Input2 Output2Hint 1. For N 2, S(1) S(2) 1.2. The input file consists of multiple test cases. Sample Input2 Sample Output2这道题的公式非常简单&#xff0c;就是求2^(N-1) %1e97 由于N实在是太大了&#xff0c;不能直接求快速幂&#xff0c;考虑到2^x % MOD是有循…

Project Honolulu 正式版发布为 Windows Admin Center

微软今天正式发布了 Project Honolulu 的正式版&#xff0c;其正式的名称为 Windows Admin Center&#xff0c;gOxiA 之前一直在这个 TAP 中&#xff0c;从 1711 到 1804 可以看出微软现在的开发速度之快&#xff0c;从测试情况看 WAC 质量非常高。正如之前日志说讲 Windows Ad…

使用C# (.NET Core) 实现命令设计模式 (Command Pattern)

本文的概念内容来自深入浅出设计模式一书.项目需求有这样一个可编程的新型遥控器, 它有7个可编程插槽, 每个插槽可连接不同的家用电器设备. 每个插槽对应两个按钮: 开, 关(ON, OFF). 此外还有一个全局的取消按钮(UNDO).现在客户想使用这个遥控器来控制不同厂家的家用电器, 例如…

Summer Training day4上帝与集合的正确用法 欧拉函数+降幂公式

这个题的指数太大了&#xff0c;因此要考虑用降幂公式进行降幂 记f(p) 2^2^2... % p f(p) 2^(2^2^2...%phi(p) phi(p)) % p 2^(f(phi(p)) phi(p)) % p 到这里我们得到了一个递推方程&#xff0c;边界f(1) 0 #include <iostream> #include <cstdio> #inclu…