.NET Core WebApi下的数据塑形

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

  若是这个时候,咱们选择将数据的全部字段一块儿返回,则会增大了Http请求的体积,好处是后续版块需求变化时,前端直接替换对应的字段便可,后端不须要修改返回的数据,但这样明显是不符合规范的,并且在特定状况下会致使信息泄露。后端

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

  因此,咱们须要一个能够由前端来指定api返回字段的方式,那就是数据塑形。数据结构

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

 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下:async

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

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

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中编写业务:this

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/

相关文章
相关标签/搜索