程序地带

.NET Core WebApi下的数据塑形


  在这个前后端分离开发的今天,前端通过调用后端提供的api接口,实现页面数据的展示。而往往在实际场景中,会出现两个版块调用的数据极度相似的情况,A页面与B页面所展示的列表,仅仅相差了几个字段。


  如果这个时候,我们选择将数据的所有字段一起返回,则会增大了Http请求的体积,好处是后续版块需求变化时,前端直接替换对应的字段即可,后端不需要修改返回的数据,但这样明显是不符合规范的,而且在特定情况下会导致信息泄露。


  还有一种方法,我们针对A页面与B页面各自返回一个DTO或VO,这样信息就不会泄露,但这样却违背了RESTful的原则,且加大了视图(UI)与应用层(Application)之间的耦合度,应用层返回什么数据原则上不应该由视图决定。


  所以,我们需要一个可以由前端来指定api返回字段的方式,那就是数据塑形。


  首先,为DTO设计一个塑形接口,并将接口方法默认实现:


1 public interface IShapeDto
2 {
3 /// <summary>
4 /// 数据塑形
5 /// </summary>
6 /// <param name="fields">指定的返回字段;字段之间用,分隔</param>
7 /// <returns></returns>
8 dynamic ShapeData(string fields)
9 {
10 //验证字段是否为空
11 if (string.IsNullOrEmpty(fields))
12 return this;
13 //拆分字段
14 var fieldsAfterSplit = fields.Split(",", StringSplitOptions.RemoveEmptyEntries);
15 //验证可用数量
16 if (fieldsAfterSplit.Length == 0)
17 return this;
18 //得到当前DTO的类型
19 var dtoType = GetType();
20 //开辟一个用于存储有效属性的内存
21 var newFields = new Queue<PropertyInfo>();
22 //public指定公共成员要包括在搜索中 Instance指定实例成员要包括在搜索中 IgnoreCase指定在绑定时不应考虑成员名称的大小写
23 var bindingFlasgs = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
24 foreach (var field in fieldsAfterSplit)
25 {
26 //搜索指定名称的属性
27 var propertyInfo = dtoType.GetProperty(field, bindingFlasgs);
28 //压入符合的属性
29 if (propertyInfo != null)
30 newFields.Enqueue(propertyInfo);
31 }
32 //创建一个即将返回的DTO对象
33 var newDto = new ExpandoObject();
34 while (newFields.Count > 0)
35 {
36 //弹出符合的属性
37 var newField = newFields.Dequeue();
38 //添加自定义字段及字段值
39 newDto.TryAdd(newField.Name, newField.GetValue(this));
40 }
41 return newDto;
42 }

  这里需要提到的是,ExpandoObject的数据结构本质上是一个Dictionary对象,其自身实现了IDictionary<string,object>,所以我们可以通过TryAdd()为其添加自定义的属性和值,从而构建了一个动态对象:


//
// 摘要:
// Represents an object whose members can be dynamically added and removed at run
// time.
public sealed class ExpandoObject : ICollection<KeyValuePair<string, object>>, IEnumerable<KeyValuePair<string, object>>, IEnumerable, IDictionary<string, object>,
INotifyPropertyChanged, IDynamicMetaObjectProvider
{
//
// 摘要:
// Initializes a new ExpandoObject that does not have members.
public ExpandoObject();
}

   TryAdd()在System.Collections.Generic.CollectionExtensions下:


public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value) where TKey : notnull;

   接下来,根据功能需求设计一个DTO类,并实现IShapeDto:


1 public class GetPersonDto : IShapeDto
2 {
3 public string Name { set; get; }
4 public int Age { set; get; }
5 public string Address { set; get; }
6 }

  然后,我们在Service中编写业务:


1 public async Task<GetPersonDto> GetPerson(int id)
2 {
3 var personEntity = await personRepository.GetPersonAsync(id);
4 return mapper.Map<GetPersonDto>(personEntity);//任意形式的映射 将 Entity 转 DTO
5 }

  最后,在Controller中塑形:


1 [HttpGet("{id}")]
2 public async Task<ActionResult<string>> Get(int id, string fields)
3 {
4 var resultDto= await personService.GetPerson(id) as IShapeDto;
5 return new JsonResult(resultDto.ShapeData(fields));
6 }

  需要注意的是,上面的代码只能运用于单个DTO对象的塑形,如果是基于IEnumerable<IShapeDto>,不建议使用Select(dto => dto.ShapeData(fields))的形式,原因是ShapeData()中反射属性名的代码不需要执行多次,建议将其提取出来。


  而碰到这个情况,我们应该针对IEnumerable<IShapeDto>编写扩展方法,不了解的伙伴可以参考我的代码自行编写,这里我就不过多赘述。


public static IEnumerable<dynamic> ShapeData(this IEnumerable<IShapeDto> dtos, string fields)

  最后,有的小伙伴可能会问,塑形动作应该是放在接口层还是放在应用层?IShapeDto为什么不写成抽象类进行继承?为什么不写扩展方法对所有object进行扩展?类似于这些问题,我只想说都可以,上面的代码完全是我个人的一个编码习惯,不能成为一个指导性的东西。在代码设计层面,每个人都会有不一样的看法,欢迎大家评论交流。


  author:https://www.cnblogs.com/abnerwong/


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/abnerwong/p/14200201.html

随机推荐

pytorch与gpu版本的适配问题

今天跑了一下yolov5报了一个错误:CUDAerror:nokernelimageisavailableforexecutiononthedevice于是乎,根据显卡的型...

榨菜rose 阅读(190)

【论文写作-3】Word中如何从任意页插入页码

在平时写论文的时候,我们需要在任意页插入页码,那么我们应该如何在我们想要的任意页插入页码嘞?如下图所示:下面将用图文详细阐述在文章中从任意页插入...

DU-KE 阅读(240)

服务器SSH连接失败的解决办法

背景今天做Android上传头像的时候,将服务器的目录:var/www设置了chown-Rapachevar/即是将目录都设置成apache为拥有者。等我用Xshell连接...

何祥建 阅读(796)

kafka学习--04(消费者低级API)

实现使用低级API读取指定topic,指定partition,指定offset的数据。1)消费者使用低级API的主要步骤:步骤主要工作1根据指定的分区从主题元...

小靳一族 阅读(123)

索引失效的几种情况

1.查询的数量是大表的大部分说明:单次查询如果查出表的大部分数据,这会导致编译器认为全表扫描性能比走索引更好,从而导致索引失效。一般单次查询数量大概占大表的3...

彼岸-花已开 阅读(603)