1. 程式人生 > 實用技巧 >.NET Core WebApi下的資料塑形

.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 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/