{"id":216,"date":"2023-10-08T00:28:14","date_gmt":"2023-10-07T16:28:14","guid":{"rendered":"https:\/\/miie.net\/?p=216"},"modified":"2023-10-08T00:28:14","modified_gmt":"2023-10-07T16:28:14","slug":"ultimate-asp-net-core-web-api-chapter7-content-negotiation","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=216","title":{"rendered":"Chapter7:CONTENT NEGOTIATION"},"content":{"rendered":"<h1>7 CONTENT NEGOTIATION<\/h1>\n<p>Content negotiation is one of the quality-of-life improvements we can add to our REST API to make it more user friendly and flexible. And when we design an API, isn\u2019t that what we want to achieve in the first place?<br \/>\n\u5185\u5bb9\u534f\u5546\u662f\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u5230 REST API \u4e2d\u7684\u751f\u6d3b\u8d28\u91cf\u6539\u8fdb\u4e4b\u4e00\uff0c\u4ee5\u4f7f\u5176\u66f4\u52a0\u7528\u6237\u53cb\u597d\u548c\u7075\u6d3b\u3002\u5f53\u6211\u4eec\u8bbe\u8ba1\u4e00\u4e2aAPI\u65f6\uff0c\u8fd9\u4e0d\u5c31\u662f\u6211\u4eec\u9996\u5148\u60f3\u8981\u5b9e\u73b0\u7684\u76ee\u6807\u5417\uff1f<\/p>\n<p>Content negotiation is an HTTP feature that has been around for a while, but for one reason or another, it is often a bit underused.<br \/>\n\u5185\u5bb9\u534f\u5546\u662f\u4e00\u9879\u5df2\u7ecf\u5b58\u5728\u4e86\u4e00\u6bb5\u65f6\u95f4\u7684HTTP\u529f\u80fd\uff0c\u4f46\u7531\u4e8e\u67d0\u79cd\u539f\u56e0\uff0c\u5b83\u901a\u5e38\u6ca1\u6709\u5f97\u5230\u5145\u5206\u5229\u7528\u3002<\/p>\n<p>In short, content negotiation lets you choose or rather \u201cnegotiate\u201d the content you want in to get in response to the REST API request.<br \/>\n\u7b80\u800c\u8a00\u4e4b\uff0c\u5185\u5bb9\u534f\u5546\u5141\u8bb8\u60a8\u9009\u62e9\u6216\u66f4\u786e\u5207\u5730\u8bf4\u201c\u534f\u5546\u201d\u60a8\u60f3\u8981\u83b7\u53d6\u7684\u5185\u5bb9\u4ee5\u54cd\u5e94 REST API \u8bf7\u6c42\u3002<\/p>\n<h2>7.1 What Do We Get Out of the Box ?<\/h2>\n<p>By default, ASP.NET Core Web API returns a JSON formatted result.<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cASP.NET \u6838\u5fc3 Web API \u8fd4\u56de JSON \u683c\u5f0f\u7684\u7ed3\u679c\u3002<\/p>\n<p>We can confirm that by looking at the response from the GetCompanies action:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u67e5\u770bGetCompanies\u7684\u56de\u590d\u6765\u786e\u8ba4\u8fd9\u4e00\u70b9\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\"><a href=\"https:\/\/localhost:5001\/api\/companies\"><a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_0701.jpg\" alt=\"Alt text\" \/><\/p>\n<p>We can clearly see that the default result when calling GET on \/api\/companies returns the JSON result. We have also used the Accept header (as you can see in the picture above) to try forcing the server to return other media types like plain text and XML.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u6e05\u695a\u5730\u770b\u5230\uff0c\u8c03\u7528 GET \u65f6\u7684\u9ed8\u8ba4\u7ed3\u679c\/api\/companies \u8fd4\u56de JSON \u7ed3\u679c\u3002\u6211\u4eec\u8fd8\u4f7f\u7528Accept \u6807\u5934\uff08\u5982\u4e0a\u56fe\u6240\u793a\uff09\u5c1d\u8bd5\u5f3a\u5236\u670d\u52a1\u5668\u8fd4\u56de\u5176\u4ed6\u5a92\u4f53\u7c7b\u578b\uff0c\u5982\u7eaf\u6587\u672c\u548c XML\u3002<\/p>\n<p>But that doesn\u2019t work. Why?<br \/>\n\u4f46\u8fd9\u884c\u4e0d\u901a\u3002\u4e3a\u4ec0\u4e48\uff1f<\/p>\n<p>Because we need to configure server formatters to format a response the way we want it.<br \/>\n\u56e0\u4e3a\u6211\u4eec\u9700\u8981\u914d\u7f6e\u670d\u52a1\u5668\u683c\u5f0f\u5316\u7a0b\u5e8f\u4ee5\u6309\u7167\u6211\u4eec\u60f3\u8981\u7684\u65b9\u5f0f\u683c\u5f0f\u5316\u54cd\u5e94\u3002<\/p>\n<p>Let\u2019s see how to do that.<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u505a\u5230\u8fd9\u4e00\u70b9\u3002<\/p>\n<h2>7.2 Changing the Default Configuration of Our Project<\/h2>\n<p>A server does not explicitly specify where it formats a response to JSON. But you can override it by changing configuration options through the AddControllers method.<br \/>\n\u670d\u52a1\u5668\u4e0d\u4f1a\u663e\u5f0f\u6307\u5b9a\u5b83\u683c\u5f0f\u5316 JSON \u54cd\u5e94\u7684\u4f4d\u7f6e\u3002\u4f46\u662f\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7 AddControllers \u65b9\u6cd5\u66f4\u6539\u914d\u7f6e\u9009\u9879\u6765\u8986\u76d6\u5b83\u3002<\/p>\n<p>We can add the following options to enable the server to format the XML response when the client tries negotiating for it:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u4ee5\u4e0b\u9009\u9879\uff0c\u4f7f\u670d\u52a1\u5668\u80fd\u591f\u5728\u5ba2\u6237\u7aef\u5c1d\u8bd5\u534f\u5546 XML \u54cd\u5e94\u65f6\u683c\u5f0f\u5316\u5b83\uff1a<\/p>\n<p><pre class=\"brush: csharp; highlight: [11,12,13,14]; title: ; notranslate\" title=\"\">\n        public void ConfigureServices(IServiceCollection services)\n        {\n\n            services.ConfigureCors();\n            services.ConfigureIISIntegration();\n            services.ConfigureLoggerService();\n            services.ConfigureSqlContext(Configuration);\n            services.ConfigureRepositoryManager();\n            services.AddAutoMapper(typeof(Startup));\n\n            services.AddControllers(config =&gt;\n            {\n                config.RespectBrowserAcceptHeader = true;\n            }).AddXmlDataContractSerializerFormatters();\n\n            services.AddControllers();\n        }\n<\/pre>\n<\/p>\n<p>First things first, we must tell a server to respect the Accept header. After that, we just add the AddXmlDataContractSerializerFormatters method to support XML formatters.<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u5fc5\u987b\u544a\u8bc9\u670d\u52a1\u5668\u63a5\u53d7 Accept \u6807\u5934\u3002\u4e4b\u540e\uff0c\u6211\u4eec\u53ea\u9700\u6dfb\u52a0 AddXmlDataContractSerializerFormatters \u65b9\u6cd5\u6765\u652f\u6301 XML \u683c\u5f0f\u5316\u7a0b\u5e8f\u3002<\/p>\n<p>Now that we have our server configured, let\u2019s test the content negotiation once more.<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u914d\u7f6e\u4e86\u670d\u52a1\u5668\uff0c\u8ba9\u6211\u4eec\u518d\u6b21\u6d4b\u8bd5\u5185\u5bb9\u534f\u5546\u3002<\/p>\n<h2>7.3 Testing Content Negotiation<\/h2>\n<p>Let\u2019s see what happens now if we fire the same request through Postman:<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u5982\u679c\u6211\u4eec\u901a\u8fc7 Postman \u89e6\u53d1\u76f8\u540c\u7684\u8bf7\u6c42\uff0c\u73b0\u5728\u4f1a\u53d1\u751f\u4ec0\u4e48\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\"><a href=\"https:\/\/localhost:5001\/api\/companies\"><a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_0702.jpg\" alt=\"Alt text\" \/><\/p>\n<p>There is our XML response.<br \/>\n\u6709\u6211\u4eec\u7684 XML \u54cd\u5e94\u3002<\/p>\n<p>Now by changing the Accept header from text\/xml to text\/json, we can get differently formatted responses \u2014 and that is quite awesome, wouldn\u2019t you agree?<br \/>\n\u73b0\u5728\uff0c\u901a\u8fc7\u5c06 Accept \u6807\u5934\u4ece text\/xml \u66f4\u6539\u4e3a text\/json\uff0c\u6211\u4eec\u53ef\u4ee5\u83b7\u5f97\u4e0d\u540c\u683c\u5f0f\u7684\u54cd\u5e94\u2014\u2014\u8fd9\u975e\u5e38\u68d2\uff0c\u4f60\u4e0d\u540c\u610f\u5417\uff1f<\/p>\n<p>Okay, that was nice and easy.<br \/>\n\u597d\u7684\uff0c\u8fd9\u5f88\u597d\uff0c\u5f88\u5bb9\u6613\u3002<\/p>\n<p>But what if despite all this flexibility a client requests a media type that a server doesn\u2019t know how to format?<br \/>\n\u4f46\u662f\uff0c\u5c3d\u7ba1\u6709\u6240\u6709\u8fd9\u4e9b\u7075\u6d3b\u6027\uff0c\u4f46\u5ba2\u6237\u7aef\u8bf7\u6c42\u670d\u52a1\u5668\u4e0d\u77e5\u9053\u5982\u4f55\u683c\u5f0f\u5316\u7684\u5a92\u4f53\u7c7b\u578b\u600e\u4e48\u529e\uff1f<\/p>\n<h2>7.4 Restrictin Media Types<\/h2>\n<p>Currently, it \u2013 the server - will default to a JSON type.<br \/>\n\u76ee\u524d\uff0c\u5b83\uff08\u670d\u52a1\u5668\uff09\u5c06\u9ed8\u8ba4\u4e3a JSON \u7c7b\u578b\u3002<\/p>\n<p>But we can restrict this behavior by adding one line to the configuration:<br \/>\n\u4f46\u662f\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u5728\u914d\u7f6e\u4e2d\u6dfb\u52a0\u4e00\u884c\u6765\u9650\u5236\u6b64\u884c\u4e3a\uff1a<\/p>\n<p><pre class=\"brush: csharp; highlight: [4]; title: ; notranslate\" title=\"\">\nservices.AddControllers(config =&gt;\n{\n    config.RespectBrowserAcceptHeader = true;\n    config.ReturnHttpNotAcceptable = true;\n}).AddXmlDataContractSerializerFormatters();\n<\/pre>\n<\/p>\n<p>We added the ReturnHttpNotAcceptable = true option, which tells the server that if the client tries to negotiate for the media type the server doesn\u2019t support, it should return the 406 Not Acceptable status code.<br \/>\n\u6211\u4eec\u6dfb\u52a0\u4e86 ReturnHttpNotAcceptable = true \u9009\u9879\uff0c\u8be5\u9009\u9879\u544a\u8bc9\u670d\u52a1\u5668\uff0c\u5982\u679c\u5ba2\u6237\u7aef\u5c1d\u8bd5\u534f\u5546\u670d\u52a1\u5668\u4e0d\u652f\u6301\u7684\u5a92\u4f53\u7c7b\u578b\uff0c\u5219\u5e94\u8fd4\u56de 406 \u4e0d\u53ef\u63a5\u53d7\u72b6\u6001\u4ee3\u7801\u3002<\/p>\n<p>This will make our application more restrictive and force the API consumer to request only the types the server supports. The 406 status code is created for this purpose.<br \/>\n\u8fd9\u5c06\u4f7f\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u66f4\u5177\u9650\u5236\u6027\uff0c\u5e76\u5f3a\u5236 API \u4f7f\u7528\u8005\u4ec5\u8bf7\u6c42\u670d\u52a1\u5668\u652f\u6301\u7684\u7c7b\u578b\u3002406 \u72b6\u6001\u4ee3\u7801\u5c31\u662f\u4e3a\u6b64\u76ee\u7684\u800c\u521b\u5efa\u7684\u3002<\/p>\n<p>Now, let\u2019s try fetching the text\/css media type using Postman to see what happens:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u5c1d\u8bd5\u4f7f\u7528 Postman \u83b7\u53d6\u6587\u672c\/css \u5a92\u4f53\u7c7b\u578b\uff0c\u770b\u770b\u4f1a\u53d1\u751f\u4ec0\u4e48\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\"><a href=\"https:\/\/localhost:5001\/api\/companies\"><a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_0703.jpg\" alt=\"Alt text\" \/><\/p>\n<p>And as expected, there is no response body and all we get is a nice 406 Not Acceptable status code.<br \/>\n\u6b63\u5982\u9884\u671f\u7684\u90a3\u6837\uff0c\u6ca1\u6709\u54cd\u5e94\u6b63\u6587\uff0c\u6211\u4eec\u5f97\u5230\u7684\u53ea\u662f\u4e00\u4e2a\u4e0d\u9519\u7684 406 \u4e0d\u53ef\u63a5\u53d7\u72b6\u6001\u4ee3\u7801\u3002<\/p>\n<p>So far so good.<br \/>\n\u76ee\u524d\u4e3a\u6b62\uff0c\u4e00\u5207\u90fd\u597d\u3002<\/p>\n<h2>7.5 More About Formatters<\/h2>\n<p>If we want our API to support content negotiation for a type that is not \u201cin the box,\u201d we need to have a mechanism to do this.<br \/>\n\u5982\u679c\u6211\u4eec\u5e0c\u671b\u6211\u4eec\u7684 API \u652f\u6301\u975e\u201c\u76d2\u5b50\u91cc\u201d\u7c7b\u578b\u7684\u5185\u5bb9\u534f\u5546\uff0c\u6211\u4eec\u9700\u8981\u6709\u4e00\u79cd\u673a\u5236\u6765\u505a\u5230\u8fd9\u4e00\u70b9\u3002<\/p>\n<p>So, how can we do that?<br \/>\n\u90a3\u4e48\uff0c\u6211\u4eec\u8be5\u600e\u4e48\u505a\u5462\uff1f<\/p>\n<p>ASP.NET Core supports the creation of custom formatters. Their purpose is to give us the flexibility to create our formatter for any media types we need to support.<br \/>\nASP.NET \u6838\u5fc3\u652f\u6301\u521b\u5efa\u81ea\u5b9a\u4e49\u683c\u5f0f\u5316\u7a0b\u5e8f\u3002\u5b83\u4eec\u7684\u76ee\u7684\u662f\u4f7f\u6211\u4eec\u80fd\u591f\u7075\u6d3b\u5730\u4e3a\u6211\u4eec\u9700\u8981\u652f\u6301\u7684\u4efb\u4f55\u5a92\u4f53\u7c7b\u578b\u521b\u5efa\u683c\u5f0f\u5316\u7a0b\u5e8f\u3002<\/p>\n<p>We can make the custom formatter by using the following method:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u65b9\u6cd5\u5236\u4f5c\u81ea\u5b9a\u4e49\u683c\u5f0f\u5316\u7a0b\u5e8f\uff1a<\/p>\n<ul>\n<li>\nCreate an output formatter class that inherits the <strong>TextOutputFormatter<\/strong> class.<br \/>\n\u521b\u5efa\u7ee7\u627f\u6587\u672c\u8f93\u51fa\u683c\u5f0f\u5316\u7a0b\u5e8f\u7c7b\u3002\n<\/li>\n<li>\nCreate an input formatter class that inherits the <strong>TextInputformatter<\/strong> class.<br \/>\n\u521b\u5efa\u7ee7\u627fTextInput\u683c\u5f0f\u5316\u7a0b\u5e8f\u7c7b\u3002\n<\/li>\n<li>\nAdd input and output classes to the InputFormatters and OutputFormatters collections the same way we did for the XML formatter.<br \/>\n\u5c06\u8f93\u5165\u548c\u8f93\u51fa\u7c7b\u6dfb\u52a0\u5230 InputFormatters \u548c OutputFormatters \u96c6\u5408\uff0c\u65b9\u6cd5\u4e0e XML \u683c\u5f0f\u5316\u7a0b\u5e8f\u76f8\u540c\u3002\n<\/li>\n<\/ul>\n<p>Now let\u2019s have some fun and implement a custom CSV formatter for our example.<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u73a9\u5f97\u5f00\u5fc3\uff0c\u5e76\u4e3a\u6211\u4eec\u7684\u793a\u4f8b\u5b9e\u73b0\u81ea\u5b9a\u4e49 CSV \u683c\u5f0f\u5316\u7a0b\u5e8f\u3002<\/p>\n<h2>7.6 Implementing a Custom Formatter<\/h2>\n<p>Since we are only interested in formatting responses, we need to implement only an output formatter. We would need an input formatter only if a request body contained a corresponding type.<br \/>\n\u7531\u4e8e\u6211\u4eec\u53ea\u5bf9\u683c\u5f0f\u5316\u54cd\u5e94\u611f\u5174\u8da3\uff0c\u56e0\u6b64\u6211\u4eec\u53ea\u9700\u8981\u5b9e\u73b0\u4e00\u4e2a\u8f93\u51fa\u683c\u5f0f\u5316\u7a0b\u5e8f\u3002\u4ec5\u5f53\u8bf7\u6c42\u6b63\u6587\u5305\u542b\u76f8\u5e94\u7684\u7c7b\u578b\u65f6\uff0c\u6211\u4eec\u624d\u9700\u8981\u8f93\u5165\u683c\u5f0f\u5316\u7a0b\u5e8f\u3002<\/p>\n<p>The idea is to format a response to return the list of companies in a CSV format.<br \/>\n\u8fd9\u4e2a\u60f3\u6cd5\u662f\u683c\u5f0f\u5316\u54cd\u5e94\u4ee5 CSV \u683c\u5f0f\u8fd4\u56de\u516c\u53f8\u5217\u8868\u3002<\/p>\n<p>Let\u2019s add a CsvOutputFormatter class to our main project:<br \/>\n\u8ba9\u6211\u4eec\u5c06\u4e00\u4e2a CsvOutputFormatter \u7c7b\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u4e3b\u9879\u76ee\u4e2d\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/CsvOutputFormatter.cs\n\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Entities.DataTransferObjects;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc.Formatters;\nusing Microsoft.Net.Http.Headers;\n\nnamespace CompanyEmployees\n{\n    public class CsvOutputFormatter : TextOutputFormatter\n    {\n        public CsvOutputFormatter() { \n            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(&quot;text\/csv&quot;)); \n            SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); \n        }\n        protected override bool CanWriteType(Type type)\n        {\n            if (typeof(CompanyDto).IsAssignableFrom(type) || typeof(IEnumerable&lt;CompanyDto&gt;).IsAssignableFrom(type)) { return base.CanWriteType(type); }\n            return false;\n        }\n        public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)\n        {\n            var response = context.HttpContext.Response;\n            var buffer = new StringBuilder();\n            if (context.Object is IEnumerable&lt;CompanyDto&gt;)\n            {\n                foreach (var company in (IEnumerable&lt;CompanyDto&gt;)context.Object)\n                { FormatCsv(buffer, company); }\n            }\n            else\n            {\n                FormatCsv(buffer, (CompanyDto)context.Object);\n            }\n            await response.WriteAsync(buffer.ToString());\n        }\n        private static void FormatCsv(StringBuilder buffer, CompanyDto company)\n        {\n            buffer.AppendLine($&quot;{company.Id},\\&quot;{company.Name},\\&quot;{company.FullAddress}\\&quot;&quot;);\n        }\n    }\n}\n\n<\/pre>\n<\/p>\n<p>There are a few things to note here:<br \/>\n\u8fd9\u91cc\u6709\u51e0\u70b9\u9700\u8981\u6ce8\u610f\uff1a<\/p>\n<ul>\n<li>\nIn the constructor, we define which media type this formatter should parse as well as encodings.<br \/>\n\u5728\u6784\u9020\u51fd\u6570\u4e2d\uff0c\u6211\u4eec\u5b9a\u4e49\u6b64\u683c\u5f0f\u5316\u7a0b\u5e8f\u5e94\u89e3\u6790\u7684\u5a92\u4f53\u7c7b\u578b\u4ee5\u53ca\u7f16\u7801\u3002\n<\/li>\n<li>\nThe <strong>CanWriteType<\/strong> method is overridden, and it indicates whether or not the CompanyDto type can be written by this serializer.<br \/>\n\u65b9\u6cd5\u88ab\u91cd\u5199\uff0c\u5b83\u6307\u793a\u6b64\u5e8f\u5217\u5316\u7a0b\u5e8f\u662f\u5426\u53ef\u4ee5\u5199\u5165 CompanyDto \u7c7b\u578b\u3002WriteResponseBodyAsync \u65b9\u6cd5\u6784\u9020\u54cd\u5e94\u3002\n<\/li>\n<li>\nThe <strong>WriteResponseBodyAsync<\/strong> method constructs the response.<br \/>\nWriteResponseBodyAsync \u65b9\u6cd5\u6784\u9020\u54cd\u5e94\u3002\n<\/li>\n<li>\nAnd finally, we have the <strong>FormatCsv<\/strong> method that formats a response the way we want it.<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u6709 FormatCsv \u65b9\u6cd5\uff0c\u5b83\u53ef\u4ee5\u6309\u7167\u6211\u4eec\u60f3\u8981\u7684\u65b9\u5f0f\u683c\u5f0f\u5316\u54cd\u5e94\u3002\n<\/li>\n<\/ul>\n<p>The class is pretty straightforward to implement, and the main thing that you should focus on is the FormatCsvmethod logic.<br \/>\n\u8be5\u7c7b\u7684\u5b9e\u73b0\u975e\u5e38\u7b80\u5355\uff0c\u60a8\u5e94\u8be5\u5173\u6ce8\u7684\u4e3b\u8981\u5185\u5bb9\u662f FormatCsvmethod \u903b\u8f91\u3002<\/p>\n<p>Now we just need to add the newly made formatter to the list of OutputFormatters in the ServicesExtensions class:<br \/>\n\u73b0\u5728\u6211\u4eec\u53ea\u9700\u8981\u5c06\u65b0\u5236\u4f5c\u7684\u683c\u5f0f\u5316\u7a0b\u5e8f\u6dfb\u52a0\u5230 ServicesExtensions \u7c7b\u4e2d\u7684 OutputFormatters \u5217\u8868\u4e2d\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic static IMvcBuilder AddCustomCSVFormatter(this IMvcBuilder builder) =&gt; \n    builder.AddMvcOptions(config =&gt; config.OutputFormatters.Add(new CsvOutputFormatter()));\n<\/pre>\n<\/p>\n<p>And to call it in the AddControllers:<br \/>\n\u5e76\u5728AddControllers\u4e2d\u8c03\u7528\u5b83\uff1a<\/p>\n<p><pre class=\"brush: csharp; highlight: [6]; title: ; notranslate\" title=\"\">\nservices.AddControllers(config =&gt;\n{\n    config.RespectBrowserAcceptHeader = true;\n    config.ReturnHttpNotAcceptable = true;\n}).AddXmlDataContractSerializerFormatters()\n.AddCustomCSVFormatter();\n\n<\/pre>\n<\/p>\n<p>Let\u2019s run this and see if it actually works. This time we will put text\/csv as the value for the Accept header:<br \/>\n\u8ba9\u6211\u4eec\u8fd0\u884c\u5b83\uff0c\u770b\u770b\u5b83\u662f\u5426\u771f\u7684\u6709\u6548\u3002\u8fd9\u6b21\u6211\u4eec\u5c06\u628atext\/csv \u4f5c\u4e3a\u63a5\u53d7\u6807\u5934\u7684\u503c\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\"><a href=\"https:\/\/localhost:5001\/api\/companies\"><a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_0704.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Well, what do you know, it works!<br \/>\n\u597d\u5427\uff0c\u4f60\u77e5\u9053\u4ec0\u4e48\uff0c\u5b83\u6709\u6548\uff01<\/p>\n<p>In this chapter, we finished working with GET requests in our project and we are ready to move on to the POST PUT and DELETE requests. We have a lot more ground to cover, so let\u2019s get down to business.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5b8c\u6210\u4e86\u9879\u76ee\u4e2d\u7684GET\u8bf7\u6c42\uff0c\u6211\u4eec\u51c6\u5907\u7ee7\u7eed\u5904\u7406POST PUT\u548cDELETE\u8bf7\u6c42\u3002\u6211\u4eec\u8fd8\u6709\u5f88\u591a\u4e8b\u60c5\u8981\u505a\uff0c\u6240\u4ee5\u8ba9\u6211\u4eec\u5f00\u59cb\u505a\u6b63\u9898\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>7 CONTENT NEGOTIATION Content negotiation is one of the quality-of-life improvements we can add to our REST API to make it more user friendly and flexible. And when we design an API, isn\u2019t that what we want to achieve in the first place? \u5185\u5bb9\u534f\u5546\u662f\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u5230 REST API \u4e2d\u7684\u751f\u6d3b\u8d28\u91cf\u6539\u8fdb\u4e4b\u4e00\uff0c\u4ee5\u4f7f\u5176\u66f4\u52a0\u7528\u6237\u53cb\u597d\u548c\u7075\u6d3b\u3002\u5f53\u6211\u4eec\u8bbe\u8ba1\u4e00\u4e2aAPI\u65f6\uff0c\u8fd9\u4e0d\u5c31\u662f\u6211\u4eec\u9996\u5148\u60f3\u8981\u5b9e\u73b0\u7684\u76ee\u6807\u5417\uff1f Content negotiation is an HTTP feature that has [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[23],"class_list":["post-216","post","type-post","status-publish","format-standard","hentry","category-csharp","tag-ultimate-asp-net-core-web-api"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/216","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=216"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/216\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=216"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=216"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=216"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}