{"id":1138,"date":"2025-05-27T14:47:20","date_gmt":"2025-05-27T06:47:20","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1138"},"modified":"2025-05-27T14:47:20","modified_gmt":"2025-05-27T06:47:20","slug":"ultimate-asp-net-core-web-api-20-data-shaping","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1138","title":{"rendered":"Ultimate ASP.NET Core Web API 20 DATA SHAPING"},"content":{"rendered":"<p>20 DATA SHAPING<br \/>\n20 \u6570\u636e\u6574\u5f62<\/p>\n<p>In this chapter, we are going to talk about a neat concept called data shaping and how to implement it in ASP.NET Core Web API. To achieve that, we are going to use similar tools to the previous section. Data shaping is not something that every API needs, but it can be very useful in some cases.\u200c<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u4e00\u4e2a\u79f0\u4e3a\u6570\u636e\u6574\u5f62\u7684\u7b80\u6d01\u6982\u5ff5\uff0c\u4ee5\u53ca\u5982\u4f55\u5728 ASP.NET Core Web API \u4e2d\u5b9e\u73b0\u5b83\u3002\u4e3a\u6b64\uff0c\u6211\u4eec\u5c06\u4f7f\u7528\u4e0e\u4e0a\u4e00\u8282\u7c7b\u4f3c\u7684\u5de5\u5177\u3002\u6570\u636e\u6574\u5f62\u4e0d\u662f\u6bcf\u4e2a API \u90fd\u9700\u8981\u7684\uff0c\u4f46\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u5b83\u53ef\u80fd\u975e\u5e38\u6709\u7528\u3002<\/p>\n<p>Let\u2019s start by learning what data shaping is exactly.<br \/>\n\u8ba9\u6211\u4eec\u9996\u5148\u4e86\u89e3\u4e00\u4e0b\u6570\u636e\u8c03\u6574\u5230\u5e95\u662f\u4ec0\u4e48\u3002<\/p>\n<h2>20.1 What is Data Shaping?<\/h2>\n<p>20.1 \u4ec0\u4e48\u662f\u6570\u636e\u6574\u5f62\uff1f<\/p>\n<p>Data shaping is a great way to reduce the amount of traffic sent from the API to the client. It enables the consumer of the API to select (shape) the data by choosing the fields through the query string.\u200c<br \/>\n\u6570\u636e\u8c03\u6574\u662f\u51cf\u5c11\u4ece API \u53d1\u9001\u5230\u5ba2\u6237\u7aef\u7684\u6d41\u91cf\u7684\u597d\u65b9\u6cd5\u3002\u5b83\u4f7f API \u7684\u4f7f\u7528\u8005\u80fd\u591f\u901a\u8fc7\u67e5\u8be2\u5b57\u7b26\u4e32\u9009\u62e9\u5b57\u6bb5\u6765\u9009\u62e9\uff08\u8c03\u6574\uff09\u6570\u636e\u3002<\/p>\n<p>What this means is something like:<br \/>\n\u8fd9\u610f\u5473\u7740\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\/companyId\/employees?fi\">https:\/\/localhost:5001\/api\/companies\/companyId\/employees?fi<\/a> elds=name,age<\/p>\n<p>By giving the consumer a way to select just the fields it needs, we can potentially reduce the stress on the API. On the other hand, this is not something every API needs, so we need to think carefully and decide whether we should implement its implementation because it has a bit of reflection in it.<br \/>\n\u901a\u8fc7\u4e3a\u6d88\u8d39\u8005\u63d0\u4f9b\u4e00\u79cd\u53ea\u9009\u62e9\u5b83\u9700\u8981\u7684\u5b57\u6bb5\u7684\u65b9\u6cd5\uff0c\u6211\u4eec\u6709\u53ef\u80fd\u51cf\u8f7b API \u7684\u538b\u529b\u3002\u53e6\u4e00\u65b9\u9762\uff0c\u8fd9\u4e0d\u662f\u6bcf\u4e2a API \u90fd\u9700\u8981\u7684\uff0c\u6240\u4ee5\u6211\u4eec\u9700\u8981\u4ed4\u7ec6\u8003\u8651\u5e76\u51b3\u5b9a\u662f\u5426\u5e94\u8be5\u5b9e\u73b0\u5b83\u7684\u5b9e\u73b0\uff0c\u56e0\u4e3a\u5b83\u6709\u4e00\u70b9\u53cd\u5c04\u3002<\/p>\n<p>And we know for a fact that reflection takes its toll and slows our application down.<br \/>\n\u6211\u4eec\u77e5\u9053\u4e00\u4e2a\u4e8b\u5b9e\uff0c\u53cd\u5c04\u4f1a\u9020\u6210\u635f\u5931\u5e76\u51cf\u6162\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u901f\u5ea6\u3002<\/p>\n<p>Finally, as always, data shaping should work well together with the concepts we\u2019ve covered so far \u2013 paging, filtering, searching, and sorting.<br \/>\n\u6700\u540e\uff0c\u4e0e\u5f80\u5e38\u4e00\u6837\uff0c\u6570\u636e\u8c03\u6574\u5e94\u8be5\u4e0e\u6211\u4eec\u5230\u76ee\u524d\u4e3a\u6b62\u4ecb\u7ecd\u7684\u6982\u5ff5\uff08\u5206\u9875\u3001\u7b5b\u9009\u3001\u641c\u7d22\u548c\u6392\u5e8f\uff09\u5f88\u597d\u5730\u534f\u540c\u5de5\u4f5c\u3002<\/p>\n<p>First, we are going to implement an employee-specific solution to data shaping. Then we are going to make it more generic, so it can be used by any entity or any API.<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u5c06\u5b9e\u65bd\u4e00\u4e2a\u7279\u5b9a\u4e8e\u5458\u5de5\u7684\u6570\u636e\u6574\u5f62\u89e3\u51b3\u65b9\u6848\u3002\u7136\u540e\uff0c\u6211\u4eec\u5c06\u4f7f\u5176\u66f4\u52a0\u901a\u7528\uff0c\u4ee5\u4fbf\u4efb\u4f55\u5b9e\u4f53\u6216\u4efb\u4f55 API \u90fd\u53ef\u4ee5\u4f7f\u7528\u5b83\u3002<\/p>\n<p>Let\u2019s get to work.<br \/>\n\u8ba9\u6211\u4eec\u5f00\u59cb\u5de5\u4f5c\u5427\u3002<\/p>\n<h2>20.2 How to Implement Data Shaping<\/h2>\n<p>20.2 \u5982\u4f55\u5b9e\u73b0\u6570\u636e\u8c03\u6574<\/p>\n<p>First things first, we need to extend our RequestParameters class since we are going to add a new feature to our query string and we want it to be available for any entity:\u200c<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u9700\u8981\u6269\u5c55 RequestParameters \u7c7b\uff0c\u56e0\u4e3a\u6211\u4eec\u8981\u5411\u67e5\u8be2\u5b57\u7b26\u4e32\u6dfb\u52a0\u65b0\u529f\u80fd\uff0c\u5e76\u4e14\u6211\u4eec\u5e0c\u671b\u5b83\u53ef\u7528\u4e8e\u4efb\u4f55\u5b9e\u4f53\uff1a<\/p>\n<pre><code>public string? Fields { get; set; }<\/code><\/pre>\n<p>We\u2019ve added the Fields property and now we can use fields as a query string parameter.<br \/>\n\u6211\u4eec\u6dfb\u52a0\u4e86 Fields \u5c5e\u6027\uff0c\u73b0\u5728\u53ef\u4ee5\u5c06 fields \u7528\u4f5c\u67e5\u8be2\u5b57\u7b26\u4e32\u53c2\u6570\u3002<\/p>\n<p>Let\u2019s continue by creating a new interface in the Contracts project:<br \/>\n\u8ba9\u6211\u4eec\u7ee7\u7eed\u5728 Contracts \u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u754c\u9762\uff1a<\/p>\n<pre><code>public interface IDataShaper&lt;T&gt; { IEnumerable&lt;ExpandoObject&gt; ShapeData(IEnumerable&lt;T&gt; entities, string fieldsString); ExpandoObject ShapeData(T entity, string fieldsString); }<\/code><\/pre>\n<p>The IDataShaper defines two methods that should be implemented \u2014 one for the single entity and one for the collection of entities. Both are named ShapeData, but they have different signatures.<br \/>\nIDataShaper \u5b9a\u4e49\u4e86\u4e24\u4e2a\u5e94\u8be5\u5b9e\u73b0\u7684\u65b9\u6cd5 \u2014 \u4e00\u4e2a\u7528\u4e8e\u5355\u4e2a\u5b9e\u4f53\uff0c\u4e00\u4e2a\u7528\u4e8e\u5b9e\u4f53\u96c6\u5408\u3002\u4e24\u8005\u90fd\u540d\u4e3a ShapeData\uff0c\u4f46\u5b83\u4eec\u5177\u6709\u4e0d\u540c\u7684\u7b7e\u540d\u3002<\/p>\n<p>Notice how we use the ExpandoObject from System.Dynamic namespace as a return type. We need to do that to shape our data the way we want it.<br \/>\n\u8bf7\u6ce8\u610f\u6211\u4eec\u5982\u4f55\u4f7f\u7528 System.Dynamic \u547d\u540d\u7a7a\u95f4\u4e2d\u7684 ExpandoObject \u4f5c\u4e3a\u8fd4\u56de\u7c7b\u578b\u3002\u6211\u4eec\u9700\u8981\u8fd9\u6837\u505a\uff0c\u4ee5\u6211\u4eec\u60f3\u8981\u7684\u65b9\u5f0f\u5851\u9020\u6211\u4eec\u7684\u6570\u636e\u3002<\/p>\n<p>To implement this interface, we are going to create a new DataShaping folder in the Service project and add a new DataShaper class:<br \/>\n\u4e3a\u4e86\u5b9e\u73b0\u8fd9\u4e2a\u63a5\u53e3\uff0c\u6211\u4eec\u5c06\u5728 Service \u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 DataShaping \u6587\u4ef6\u5939\uff0c\u5e76\u6dfb\u52a0\u65b0\u7684 DataShaper \u7c7b\uff1a<\/p>\n<pre><code>public class DataShaper&lt;T&gt; : IDataShaper&lt;T&gt; where T : class { public PropertyInfo[] Properties { get; set; } public DataShaper() { Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); } public IEnumerable&lt;ExpandoObject&gt; ShapeData(IEnumerable&lt;T&gt; entities, string fieldsString) {var requiredProperties = GetRequiredProperties(fieldsString); return FetchData(entities, requiredProperties); } public ExpandoObject ShapeData(T entity, string fieldsString) { var requiredProperties = GetRequiredProperties(fieldsString); return FetchDataForEntity(entity, requiredProperties); } private IEnumerable&lt;PropertyInfo&gt; GetRequiredProperties(string fieldsString) { var requiredProperties = new List&lt;PropertyInfo&gt;(); if (!string.IsNullOrWhiteSpace(fieldsString)) { var fields = fieldsString.Split(&#039;,&#039;, StringSplitOptions.RemoveEmptyEntries); foreach (var field in fields) { var property = Properties .FirstOrDefault(pi =&gt; pi.Name.Equals(field.Trim(), StringComparison.InvariantCultureIgnoreCase)); if (property == null) continue; requiredProperties.Add(property); } } else { requiredProperties = Properties.ToList(); } return requiredProperties; }private IEnumerable&lt;ExpandoObject&gt; FetchData(IEnumerable&lt;T&gt; entities, IEnumerable&lt;PropertyInfo&gt; requiredProperties) { var shapedData = new List&lt;ExpandoObject&gt;(); foreach (var entity in entities) { var shapedObject = FetchDataForEntity(entity, requiredProperties); shapedData.Add(shapedObject); } return shapedData; } private ExpandoObject FetchDataForEntity(T entity, IEnumerable&lt;PropertyInfo&gt; requiredProperties) { var shapedObject = new ExpandoObject();foreach (var property in requiredProperties) { var objectPropertyValue = property.GetValue(entity); shapedObject.TryAdd(property.Name, objectPropertyValue); } return shapedObject; } }<\/code><\/pre>\n<p>We need these namespaces to be included as well:<br \/>\n\u6211\u4eec\u8fd8\u9700\u8981\u5305\u542b\u8fd9\u4e9b\u547d\u540d\u7a7a\u95f4\uff1a<\/p>\n<pre><code>using Contracts; \nusing System.Dynamic; \nusing System.Reflection;<\/code><\/pre>\n<p>There is quite a lot of code in our class, so let\u2019s break it down.<br \/>\n\u6211\u4eec\u7684\u7c7b\u4e2d\u6709\u76f8\u5f53\u591a\u7684\u4ee3\u7801\uff0c\u6240\u4ee5\u8ba9\u6211\u4eec\u5206\u89e3\u4e00\u4e0b\u3002<\/p>\n<h2>20.3 Step-by-Step Implementation<\/h2>\n<p>20.3 \u5206\u6b65\u5b9e\u65bd<\/p>\n<p>We have one public property in this class \u2013 Properties. It\u2019s an array of PropertyInfo\u2019s that we\u2019re going to pull out of the input type, whatever it is\u200c \u2014 Company or Employee in our case:<br \/>\n\u8fd9\u4e2a\u7c7b\u4e2d\u6709\u4e00\u4e2a\u516c\u5171\u5c5e\u6027 \u2013 Properties\u3002\u8fd9\u662f\u4e00\u4e2a PropertyInfo \u6570\u7ec4\uff0c\u6211\u4eec\u5c06\u4ece\u8f93\u5165\u7c7b\u578b\u4e2d\u63d0\u53d6\u51fa\u6765\uff0c\u65e0\u8bba\u5b83\u662f\u4ec0\u4e48 \u2014 \u5728\u6211\u4eec\u7684\u4f8b\u5b50\u4e2d\u662f Company \u6216 Employee\uff1a<\/p>\n<pre><code>public PropertyInfo[] Properties { get; set; } public DataShaper() { Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); }<\/code><\/pre>\n<p>So, here it is. In the constructor, we get all the properties of an input class.<br \/>\n\u6240\u4ee5\uff0c\u5c31\u5728\u8fd9\u91cc\u3002\u5728\u6784\u9020\u51fd\u6570\u4e2d\uff0c\u6211\u4eec\u83b7\u53d6 input \u7c7b\u7684\u6240\u6709\u5c5e\u6027\u3002<\/p>\n<p>Next, we have the implementation of our two public ShapeData methods:<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u5b9e\u73b0\u4e86\u4e24\u4e2a\u516c\u5171 ShapeData \u65b9\u6cd5\uff1a<\/p>\n<pre><code>public IEnumerable&lt;ExpandoObject&gt; ShapeData(IEnumerable&lt;T&gt; entities, string fieldsString) { var requiredProperties = GetRequiredProperties(fieldsString); return FetchData(entities, requiredProperties); } public ExpandoObject ShapeData(T entity, string fieldsString) { var requiredProperties = GetRequiredProperties(fieldsString); return FetchDataForEntity(entity, requiredProperties); }<\/code><\/pre>\n<p>Both methods rely on the GetRequiredProperties method to parse the input string that contains the fields we want to fetch.<br \/>\n\u8fd9\u4e24\u79cd\u65b9\u6cd5\u90fd\u4f9d\u8d56\u4e8e GetRequiredProperties \u65b9\u6cd5\u6765\u89e3\u6790\u5305\u542b\u6211\u4eec\u8981\u83b7\u53d6\u7684\u5b57\u6bb5\u7684\u8f93\u5165\u5b57\u7b26\u4e32\u3002<\/p>\n<p>The GetRequiredProperties method does the magic. It parses the input string and returns just the properties we need to return to the controller:<br \/>\nGetRequiredProperties \u65b9\u6cd5\u53ef\u4ee5\u6267\u884c\u795e\u5947\u7684\u4f5c\u3002\u5b83\u89e3\u6790\u8f93\u5165\u5b57\u7b26\u4e32\u5e76\u4ec5\u8fd4\u56de\u6211\u4eec\u9700\u8981\u8fd4\u56de\u7ed9\u63a7\u5236\u5668\u7684\u5c5e\u6027\uff1a<\/p>\n<pre><code>private IEnumerable&lt;PropertyInfo&gt; GetRequiredProperties(string fieldsString) { var requiredProperties = new List&lt;PropertyInfo&gt;(); if (!string.IsNullOrWhiteSpace(fieldsString)) { var fields = fieldsString.Split(&#039;,&#039;, StringSplitOptions.RemoveEmptyEntries); foreach (var field in fields) { var property = Properties .FirstOrDefault(pi =&gt; pi.Name.Equals(field.Trim(), StringComparison.InvariantCultureIgnoreCase)); if (property == null) continue; requiredProperties.Add(property); } } else { requiredProperties = Properties.ToList(); } return requiredProperties; }<\/code><\/pre>\n<p>There\u2019s nothing special about it. If the fieldsString is not empty, we split it and check if the fields match the properties in our entity. If they do, we add them to the list of required properties.<br \/>\n\u5b83\u6ca1\u6709\u4ec0\u4e48\u7279\u522b\u4e4b\u5904\u3002\u5982\u679c fieldsString \u4e0d\u4e3a\u7a7a\uff0c\u6211\u4eec\u5c06\u5176\u62c6\u5206\u5e76\u68c0\u67e5\u5b57\u6bb5\u662f\u5426\u4e0e\u5b9e\u4f53\u4e2d\u7684\u5c5e\u6027\u5339\u914d\u3002\u5982\u679c\u51fa\u73b0\uff0c\u6211\u4eec\u4f1a\u5c06\u5b83\u4eec\u6dfb\u52a0\u5230\u5fc5\u9700\u5c5e\u6027\u5217\u8868\u4e2d\u3002<\/p>\n<p>On the other hand, if the fieldsString is empty, all properties are required.<br \/>\n\u53e6\u4e00\u65b9\u9762\uff0c\u5982\u679c fieldsString \u4e3a\u7a7a\uff0c\u5219\u6240\u6709\u5c5e\u6027\u90fd\u662f\u5fc5\u9700\u7684\u3002<\/p>\n<p>Now, FetchData and FetchDataForEntity are the private methods to extract the values from these required properties we\u2019ve prepared.<br \/>\n\u73b0\u5728\uff0cFetchData \u548c FetchDataForEntity \u662f\u4ece\u6211\u4eec\u51c6\u5907\u7684\u8fd9\u4e9b\u5fc5\u9700\u5c5e\u6027\u4e2d\u63d0\u53d6\u503c\u7684\u79c1\u6709\u65b9\u6cd5\u3002<\/p>\n<p>The FetchDataForEntity method does it for a single entity:<br \/>\nFetchDataForEntity \u65b9\u6cd5\u5bf9\u5355\u4e2a\u5b9e\u4f53\u6267\u884c\u6b64\u4f5c\uff1a<\/p>\n<pre><code>private ExpandoObject FetchDataForEntity(T entity, IEnumerable&lt;PropertyInfo&gt; requiredProperties) { var shapedObject = new ExpandoObject();foreach (var property in requiredProperties) { var objectPropertyValue = property.GetValue(entity); shapedObject.TryAdd(property.Name, objectPropertyValue); } return shapedObject; }<\/code><\/pre>\n<p>Here, we loop through the requiredProperties parameter. Then, using a bit of reflection, we extract the values and add them to our ExpandoObject. ExpandoObject implements <code>IDictionary&lt;string,object&gt;<\/code>, so we can use the TryAdd method to add our property using its name as a key and the value as a value for the dictionary.<br \/>\n\u5728\u8fd9\u91cc\uff0c\u6211\u4eec\u904d\u5386 requiredProperties \u53c2\u6570\u3002\u7136\u540e\uff0c\u4f7f\u7528\u4e00\u4e9b\u53cd\u5c04\uff0c\u6211\u4eec\u63d0\u53d6\u503c\u5e76\u5c06\u5b83\u4eec\u6dfb\u52a0\u5230\u6211\u4eec\u7684 ExpandoObject \u4e2d\u3002ExpandoObject \u5b9e\u73b0 <code>IDictionary&lt;string,object&gt;<\/code>\uff0c\u56e0\u6b64\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 TryAdd \u65b9\u6cd5\u6dfb\u52a0\u5c5e\u6027\uff0c\u4f7f\u7528\u5176\u540d\u79f0\u4f5c\u4e3a\u952e\uff0c\u5c06\u503c\u7528\u4f5c\u5b57\u5178\u7684\u503c\u3002<\/p>\n<p>This way, we dynamically add just the properties we need to our dynamic object.<br \/>\n\u8fd9\u6837\uff0c\u6211\u4eec\u5c31\u53ef\u4ee5\u52a8\u6001\u5730\u5c06\u6240\u9700\u7684\u5c5e\u6027\u6dfb\u52a0\u5230\u52a8\u6001\u5bf9\u8c61\u4e2d\u3002<\/p>\n<p>The FetchData method is just an implementation for multiple objects. It utilizes the FetchDataForEntity method we\u2019ve just implemented:<br \/>\nFetchData \u65b9\u6cd5\u53ea\u662f\u591a\u4e2a\u5bf9\u8c61\u7684\u5b9e\u73b0\u3002\u5b83\u5229\u7528\u4e86\u6211\u4eec\u521a\u521a\u5b9e\u73b0\u7684 FetchDataForEntity \u65b9\u6cd5\uff1a<\/p>\n<pre><code>private IEnumerable&lt;ExpandoObject&gt; FetchData(IEnumerable&lt;T&gt; entities, IEnumerable&lt;PropertyInfo&gt; requiredProperties) { var shapedData = new List&lt;ExpandoObject&gt;(); foreach (var entity in entities) { var shapedObject = FetchDataForEntity(entity, requiredProperties); shapedData.Add(shapedObject); } return shapedData; }<\/code><\/pre>\n<p>To continue, let\u2019s register the DataShaper class in the IServiceCollection in the Program class:<br \/>\n\u82e5\u8981\u7ee7\u7eed\uff0c\u8ba9\u6211\u4eec\u5728 Program \u7c7b\u7684 IServiceCollection \u4e2d\u6ce8\u518c DataShaper \u7c7b\uff1a<\/p>\n<pre><code>builder.Services.AddScoped&lt;IDataShaper&lt;EmployeeDto&gt;, DataShaper&lt;EmployeeDto&gt;&gt;();<\/code><\/pre>\n<p>During the service registration, we provide the type to work with.<br \/>\n\u5728\u670d\u52a1\u6ce8\u518c\u671f\u95f4\uff0c\u6211\u4eec\u4f1a\u63d0\u4f9b\u8981\u4f7f\u7528\u7684\u7c7b\u578b\u3002<\/p>\n<p>Because we want to use the DataShaper class inside the service classes, we have to modify the constructor of the ServiceManager class first:<br \/>\n\u56e0\u4e3a\u6211\u4eec\u60f3\u5728\u670d\u52a1\u7c7b\u4e2d\u4f7f\u7528 DataShaper \u7c7b\uff0c\u6240\u4ee5\u6211\u4eec\u5fc5\u987b\u5148\u4fee\u6539 ServiceManager \u7c7b\u7684\u6784\u9020\u51fd\u6570\uff1a<\/p>\n<pre><code>public ServiceManager(IRepositoryManager repositoryManager, ILoggerManager logger, IMapper mapper, IDataShaper&lt;EmployeeDto&gt; dataShaper) { _companyService = new Lazy&lt;ICompanyService&gt;(() =&gt; new CompanyService(repositoryManager, logger, mapper)); _employeeService = new Lazy&lt;IEmployeeService&gt;(() =&gt; new EmployeeService(repositoryManager, logger, mapper, dataShaper)); }<\/code><\/pre>\n<p>We are going to use it only in the EmployeeService class.<br \/>\n\u6211\u4eec\u53ea\u5728 EmployeeService \u7c7b\u4e2d\u4f7f\u7528\u5b83\u3002<\/p>\n<p>Next, let\u2019s add one more field and modify the constructor in the EmployeeService class:<br \/>\n\u63a5\u4e0b\u6765\uff0c\u8ba9\u6211\u4eec\u518d\u6dfb\u52a0\u4e00\u4e2a\u5b57\u6bb5\u5e76\u4fee\u6539 EmployeeService \u7c7b\u4e2d\u7684\u6784\u9020\u51fd\u6570\uff1a<\/p>\n<pre><code>... \nprivate readonly IDataShaper&lt;EmployeeDto&gt; _dataShaper; public EmployeeService(IRepositoryManager repository, ILoggerManager logger, IMapper mapper, IDataShaper&lt;EmployeeDto&gt; dataShaper) { _repository = repository; _logger = logger; _mapper = mapper; _dataShaper = dataShaper; }<\/code><\/pre>\n<p>Let\u2019s also modify the GetEmployeesAsync method of the same class:<br \/>\n\u6211\u4eec\u8fd8\u4fee\u6539\u540c\u4e00\u7c7b\u7684 GetEmployeesAsync \u65b9\u6cd5\uff1a<\/p>\n<pre><code>public async Task&lt;(IEnumerable&lt;ExpandoObject&gt; employees, MetaData metaData)&gt; GetEmployeesAsync (Guid companyId, EmployeeParameters employeeParameters, bool trackChanges) { if (!employeeParameters.ValidAgeRange) throw new MaxAgeRangeBadRequestException(); await CheckIfCompanyExists(companyId, trackChanges); var employeesWithMetaData = await _repository.Employee .GetEmployeesAsync(companyId, employeeParameters, trackChanges); var employeesDto = _mapper.Map&lt;IEnumerable&lt;EmployeeDto&gt;&gt;(employeesWithMetaData); var shapedData = _dataShaper.ShapeData(employeesDto, employeeParameters.Fields); return (employees: shapedData, metaData: employeesWithMetaData.MetaData); }<\/code><\/pre>\n<p>We have changed the method signature so, we have to modify the interface as well:<br \/>\n\u6211\u4eec\u5df2\u7ecf\u66f4\u6539\u4e86\u65b9\u6cd5\u7b7e\u540d\uff0c\u56e0\u6b64\uff0c\u6211\u4eec\u8fd8\u5fc5\u987b\u4fee\u6539\u63a5\u53e3\uff1a<\/p>\n<pre><code>Task&lt;(IEnumerable&lt;ExpandoObject&gt; employees, MetaData metaData)&gt; GetEmployeesAsync(Guid companyId, EmployeeParameters employeeParameters, bool trackChanges);<\/code><\/pre>\n<p>Now, we can test our solution:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u6d4b\u8bd5\u6211\u4eec\u7684\u89e3\u51b3\u65b9\u6848\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees?fields=name,age\">https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees?fields=name,age<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2001.jpg\" alt=\"alt text\" \/><\/p>\n<p>It works great.<br \/>\n\u5b83\u6548\u679c\u5f88\u597d\u3002<\/p>\n<p>Let\u2019s also test this solution by combining all the functionalities that we\u2019ve implemented in the previous chapters:<br \/>\n\u6211\u4eec\u8fd8\u901a\u8fc7\u7ec4\u5408\u6211\u4eec\u5728\u524d\u51e0\u7ae0\u4e2d\u5b9e\u73b0\u7684\u6240\u6709\u529f\u80fd\u6765\u6d4b\u8bd5\u6b64\u89e3\u51b3\u65b9\u6848\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees?pageNumber=1&amp;pageSize=4&amp;minAge=26&amp;maxAge=32&amp;searchTerm=A&amp;orderBy=name\">https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees?pageNumber=1&pageSize=4&minAge=26&maxAge=32&searchTerm=A&orderBy=name<\/a> desc&amp;fields=name,age<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2002.jpg\" alt=\"alt text\" \/><\/p>\n<p>Excellent. Everything is working like a charm.<br \/>\n\u975e\u5e38\u597d\u3002\u4e00\u5207\u90fd\u50cf\u9b45\u529b\u4e00\u6837\u8fd0\u4f5c\u3002<\/p>\n<h2>20.4 Resolving XML Serialization Problems<\/h2>\n<p>20.4 \u89e3\u51b3 XML \u5e8f\u5217\u5316\u95ee\u9898<\/p>\n<p>Let\u2019s send the same request one more time, but this time with the\u200c different accept header (text\/xml):<br \/>\n\u8ba9\u6211\u4eec\u518d\u53d1\u9001\u4e00\u6b21\u76f8\u540c\u7684\u8bf7\u6c42\uff0c\u4f46\u8fd9\u6b21\u4f7f\u7528\u4e0d\u540c\u7684 accept \u6807\u5934 \uff08text\/xml\uff09\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2003.jpg\" alt=\"alt text\" \/><\/p>\n<p>It works \u2014 but it looks pretty ugly and unreadable. But that\u2019s how the XmlDataContractSerializerOutputFormatter serializes our ExpandoObject by default.<br \/>\n\u5b83\u6709\u6548 \u2014 \u4f46\u5b83\u770b\u8d77\u6765\u975e\u5e38\u4e11\u964b\u4e14\u96be\u4ee5\u9605\u8bfb\u3002\u4f46\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u8fd9\u5c31\u662f XmlDataContractSerializerOutputFormatter \u5e8f\u5217\u5316 ExpandoObject \u7684\u65b9\u5f0f\u3002<\/p>\n<p>We can fix that, but the logic is out of the scope of this book. Of course, we have implemented the solution in our source code. So, if you want, you can use it in your project.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u89e3\u51b3\u8fd9\u4e2a\u95ee\u9898\uff0c\u4f46\u903b\u8f91\u8d85\u51fa\u4e86\u672c\u4e66\u7684\u8303\u56f4\u3002\u5f53\u7136\uff0c\u6211\u4eec\u5df2\u7ecf\u5728\u6e90\u4ee3\u7801\u4e2d\u5b9e\u73b0\u4e86\u89e3\u51b3\u65b9\u6848\u3002\u56e0\u6b64\uff0c\u5982\u679c\u60a8\u613f\u610f\uff0c\u60a8\u53ef\u4ee5\u5728\u60a8\u7684\u9879\u76ee\u4e2d\u4f7f\u7528\u5b83\u3002<\/p>\n<p>All you have to do is to create the Entity class and copy the content from our Entity class that resides in the Entities\/Models folder.<br \/>\n\u60a8\u6240\u8981\u505a\u7684\u5c31\u662f\u521b\u5efa Entity \u7c7b\u5e76\u4ece\u4f4d\u4e8e Entities\/Models \u6587\u4ef6\u5939\u4e2d\u7684 Entity \u7c7b\u4e2d\u590d\u5236\u5185\u5bb9\u3002<\/p>\n<p>After that, just modify the IDataShaper interface and the DataShaper class by using the Entity type instead of the ExpandoObject type. Also, you have to do the same thing for the IEmployeeService interface and the EmployeeService class. Again, you can check our implementation if you have any problems.<br \/>\n\u4e4b\u540e\uff0c\u53ea\u9700\u4f7f\u7528 Entity \u7c7b\u578b\u800c\u4e0d\u662f ExpandoObject \u7c7b\u578b\u6765\u4fee\u6539 IDataShaper \u63a5\u53e3\u548c DataShaper \u7c7b\u3002\u6b64\u5916\uff0c\u8fd8\u5fc5\u987b\u5bf9 IEmployeeService \u63a5\u53e3\u548c EmployeeService \u7c7b\u6267\u884c\u76f8\u540c\u7684\u4f5c\u3002\u540c\u6837\uff0c\u5982\u679c\u60a8\u6709\u4efb\u4f55\u95ee\u9898\uff0c\u53ef\u4ee5\u68c0\u67e5\u6211\u4eec\u7684\u5b9e\u73b0\u3002<\/p>\n<p>After all those changes, once we send the same request, we are going to see a much better result:<br \/>\n\u5728\u6240\u6709\u8fd9\u4e9b\u66f4\u6539\u4e4b\u540e\uff0c\u4e00\u65e6\u6211\u4eec\u53d1\u9001\u76f8\u540c\u7684\u8bf7\u6c42\uff0c\u6211\u4eec\u5c06\u770b\u5230\u66f4\u597d\u7684\u7ed3\u679c\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees?pageNumber=1&amp;pageSize=4&amp;minAge=26&amp;maxAge=32&amp;searchTerm=A&amp;orderBy=name\">https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees?pageNumber=1&pageSize=4&minAge=26&maxAge=32&searchTerm=A&orderBy=name<\/a> desc&amp;fields=name,age<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2004.jpg\" alt=\"alt text\" \/><\/p>\n<p>If XML serialization is not important to you, you can keep using ExpandoObject \u2014 but if you want a nicely formatted XML response, this is the way to go.<br \/>\n\u5982\u679c XML \u5e8f\u5217\u5316\u5bf9\u60a8\u6765\u8bf4\u5e76\u4e0d\u91cd\u8981\uff0c\u60a8\u53ef\u4ee5\u7ee7\u7eed\u4f7f\u7528 ExpandoObject \u2014 \u4f46\u5982\u679c\u60a8\u60f3\u8981\u683c\u5f0f\u826f\u597d\u7684 XML \u54cd\u5e94\uff0c\u8fd9\u5c31\u662f\u8981\u8d70\u7684\u8def\u3002<\/p>\n<p>To sum up, data shaping is an exciting and neat little feature that can make our APIs flexible and reduce our network traffic. If we have a high- volume traffic API, data shaping should work just fine. On the other hand, it\u2019s not a feature that we should use lightly because it utilizes reflection and dynamic typing to get things done.<br \/>\n\u7efc\u4e0a\u6240\u8ff0\uff0c\u6570\u636e\u6574\u5f62\u662f\u4e00\u4e2a\u4ee4\u4eba\u5174\u594b\u4e14\u7b80\u6d01\u7684\u5c0f\u529f\u80fd\uff0c\u5b83\u53ef\u4ee5\u4f7f\u6211\u4eec\u7684 API \u53d8\u5f97\u7075\u6d3b\uff0c\u51cf\u5c11\u6211\u4eec\u7684\u7f51\u7edc\u6d41\u91cf\u3002\u5982\u679c\u6211\u4eec\u6709\u4e00\u4e2a\u5927\u5bb9\u91cf\u6d41\u91cf API\uff0c\u6570\u636e\u6574\u5f62\u5e94\u8be5\u53ef\u4ee5\u6b63\u5e38\u5de5\u4f5c\u3002\u53e6\u4e00\u65b9\u9762\uff0c\u5b83\u4e0d\u662f\u4e00\u4e2a\u6211\u4eec\u5e94\u8be5\u8f7b\u6613\u4f7f\u7528\u7684\u529f\u80fd\uff0c\u56e0\u4e3a\u5b83\u5229\u7528\u53cd\u5c04\u548c\u52a8\u6001\u7c7b\u578b\u6765\u5b8c\u6210\u5de5\u4f5c\u3002<\/p>\n<p>As with all other functionalities, we need to be careful when and if we should implement data shaping. Performance tests might come in handy even if we do implement it.<br \/>\n\u4e0e\u6240\u6709\u5176\u4ed6\u529f\u80fd\u4e00\u6837\uff0c\u6211\u4eec\u9700\u8981\u5c0f\u5fc3\u4f55\u65f6\u4ee5\u53ca\u662f\u5426\u5e94\u8be5\u5b9e\u73b0\u6570\u636e\u6574\u5f62\u3002\u5373\u4f7f\u6211\u4eec\u786e\u5b9e\u5b9e\u65bd\u4e86\u6027\u80fd\u6d4b\u8bd5\uff0c\u5b83\u4e5f\u53ef\u80fd\u4f1a\u6d3e\u4e0a\u7528\u573a\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>20 DATA SHAPING 20 \u6570\u636e\u6574\u5f62 In this chapter, we are going to talk about a neat concept called data shaping and how to implement it in ASP.NET Core Web API. To achieve that, we are going to use similar tools to the previous section. Data shaping is not something that every API needs, but it [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1138","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1138","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1138"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1138\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1138"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1138"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1138"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}