Data shaping under. Net core webapi


Today, when the front and back ends are developed separately, the front end realizes the display of page data by calling the API interface provided by the back end. In the actual scenario, the data called by the two sections are extremely similar. The list displayed on page A and page B only differs by a few fields.

If we choose to return all the fields of the data together at this time, the volume of the HTTP request will be increased. The advantage is that when the requirements of subsequent sections change, the front end can directly replace the corresponding fields, and the back end does not need to modify the returned data. However, this obviously does not comply with the specification, and will lead to information disclosure under specific circumstances.

Another method is that we return a dto or VO for page A and page B respectively, so that the information will not be leaked. However, this violates the principle of restful and increases the coupling between the view (UI) and the application layer. In principle, what data the application layer returns should not be determined by the view.

Therefore, we need a way that the front end can specify the returned fields of the API, that is, data shaping.

First, a shaping interface is designed for dto, and the interface method is implemented by default:

1 public interface IShapeDto
 2 {
 3     /// 
 4 // data shaping
 5     /// 
 6 // specified return field; Fields are separated by
 7     /// 
 8     dynamic ShapeData(string fields)
 9     {
10 // verify that the field is empty
11         if (string.IsNullOrEmpty(fields))
12             return this;
13 // split field
14         var fieldsAfterSplit = fields.Split(',', StringSplitOptions.RemoveEmptyEntries);
15 // verify the available quantity
16         if (fieldsAfterSplit.Length == 0)
17             return this;
18 // get the type of the current dto
19         var dtoType = GetType();
20 // open up a memory for storing valid attributes
21         var newFields = new Queue();
22 // public specifies that public members should be included in the search. Instance specifies that instance members should be included in the search. Ignorecase specifies that the case of member names should not be considered when binding
23         var bindingFlasgs = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
24         foreach (var field in fieldsAfterSplit)
25         {
26 // search the attribute with the specified name
27             var propertyInfo = dtoType.GetProperty(field, bindingFlasgs);
28 // press the matching attributes
29             if (propertyInfo != null)
30                 newFields.Enqueue(propertyInfo);
31         }
32 // create a dto object to be returned
33         var newDto = new ExpandoObject();
34         while (newFields.Count > 0)
35         {
36 // a matching attribute pops up
37             var newField = newFields.Dequeue();
38 // add custom fields and field values
39             newDto.TryAdd(newField.Name, newField.GetValue(this));
40         }
41         return newDto;
42     }

What needs to be mentioned here is,The data structure of expandoobject is essentially a dictionary object, which implements idictionary itselfTherefore, we can add custom attributes and values through tryadd() to build a dynamic object:

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

Tryadd() under system. Collections. Generic. Collectionextensions:

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

Next, design a dto class according to the functional requirements and implement 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 }

Then, we write business in service:

1 public async Task GetPerson(int id)
2 {
3     var personEntity = await personRepository.GetPersonAsync(id);
4     return mapper.Map(personEntity);// Any form of mapping converts entity to dto
5 }

Finally, shape in the controller:

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

It should be noted that,The above code can only be applied to the shaping of a single dto object, if it is based on IEnumerable, the form of select (dto = > dto. Shapedata (fields)) is not recommended, because the code reflecting the attribute name in shapedata () does not need to be executed multiple times, it is recommended to extract it.

In this case, we should aim at IEnumerableWrite extension methods. Partners who don’t understand can refer to my code and write them by themselves. I won’t repeat it here.

public static IEnumerable ShapeData(this IEnumerable dtos, string fields)

Finally, some small partners may ask, should the shaping action be placed in the interface layer or in the application layer? Why doesn’t ishapedto inherit as an abstract class? Why not write an extension method to extend all objects? Similar to these problems, I just want to say that it is OK. The above code is completely my personal coding habit and can not be a guiding thing. At the level of code design, everyone will have different views. You are welcome to comment and exchange.