.NET Core WebApi下的数据塑形

  • A+
所属分类:.NET技术
摘要

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

  在这个前后端分离开发的今天,前端通过调用后端提供的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 GetPersonDto GetPerson(int id) 2 { 3     var personEntity = await personRepository.GetPerson(id); 4     return mapper.Map<GetPersonDto>(personEntity);//任意形式的映射 将 Entity 转 DTO 5 }

  最后,在Controller中塑形:

1 [HttpGet("{id}")] 2 public static 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> Shape(this IEnumerable<IShapeDto> dtos,string fields),不了解的伙伴可以参考我的代码自行编写,这里我就不过多赘述。

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

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