{"id":1146,"date":"2025-05-27T14:47:41","date_gmt":"2025-05-27T06:47:41","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1146"},"modified":"2025-05-27T14:47:41","modified_gmt":"2025-05-27T06:47:41","slug":"ultimate-asp-net-core-web-api-24-versioning-apis","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1146","title":{"rendered":"Ultimate ASP.NET Core Web API 24 VERSIONING APIS"},"content":{"rendered":"<p>24 VERSIONING APIS<br \/>\n24 \u7248\u672c\u63a7\u5236 API<\/p>\n<p>As our project grows, so does our knowledge; therefore, we have a better understanding of how to improve our system. Moreover, requirements change over time \u2014 thus, our API has to change as well.\u200c<br \/>\n\u968f\u7740\u6211\u4eec\u9879\u76ee\u7684\u53d1\u5c55\uff0c\u6211\u4eec\u7684\u77e5\u8bc6\u4e5f\u5728\u589e\u957f;\u56e0\u6b64\uff0c\u6211\u4eec\u5bf9\u5982\u4f55\u6539\u8fdb\u6211\u4eec\u7684\u7cfb\u7edf\u6709\u4e86\u66f4\u597d\u7684\u7406\u89e3\u3002\u6b64\u5916\uff0c\u9700\u6c42\u4f1a\u968f\u7740\u65f6\u95f4\u7684\u63a8\u79fb\u800c\u53d8\u5316\u2014\u2014\u56e0\u6b64\uff0c\u6211\u4eec\u7684 API \u4e5f\u5fc5\u987b\u53d1\u751f\u53d8\u5316\u3002<\/p>\n<p>When we implement some breaking changes, we want to ensure that we don\u2019t do anything that will cause our API consumers to change their code. Those breaking changes could be:<br \/>\n\u5f53\u6211\u4eec\u5b9e\u65bd\u4e00\u4e9b\u91cd\u5927\u66f4\u6539\u65f6\uff0c\u6211\u4eec\u5e0c\u671b\u786e\u4fdd\u6211\u4eec\u4e0d\u4f1a\u6267\u884c\u4efb\u4f55\u4f1a\u5bfc\u81f4 API \u4f7f\u7528\u8005\u66f4\u6539\u5176\u4ee3\u7801\u7684\u4f5c\u3002\u8fd9\u4e9b\u91cd\u5927\u66f4\u6539\u53ef\u80fd\u662f\uff1a<\/p>\n<p>\u2022 Renaming fields, properties, or resource URIs.<br \/>\n\u91cd\u547d\u540d\u5b57\u6bb5\u3001\u5c5e\u6027\u6216\u8d44\u6e90 URI\u3002<\/p>\n<p>\u2022 Changes in the payload structure.<br \/>\n\u6709\u6548\u8d1f\u8f7d\u7ed3\u6784\u7684\u66f4\u6539\u3002<\/p>\n<p>\u2022 Modifying response codes or HTTP Verbs.<br \/>\n\u4fee\u6539\u54cd\u5e94\u4ee3\u7801\u6216 HTTP \u52a8\u8bcd\u3002<\/p>\n<p>\u2022 Redesigning our API endpoints.<br \/>\n\u91cd\u65b0\u8bbe\u8ba1\u6211\u4eec\u7684 API \u7aef\u70b9\u3002<\/p>\n<p>If we have to implement some of these changes in the already working API, the best way is to apply versioning to prevent breaking our API for the existing API consumers.<br \/>\n\u5982\u679c\u6211\u4eec\u5fc5\u987b\u5728\u5df2\u7ecf\u8fd0\u884c\u7684 API \u4e2d\u5b9e\u73b0\u5176\u4e2d\u4e00\u4e9b\u66f4\u6539\uff0c\u6700\u597d\u7684\u65b9\u6cd5\u662f\u5e94\u7528\u7248\u672c\u63a7\u5236\u4ee5\u9632\u6b62\u7834\u574f\u73b0\u6709 API \u4f7f\u7528\u8005\u7684 API\u3002<\/p>\n<p>There are different ways to achieve API versioning and there is no guidance that favors one way over another. So, we are going to show you different ways to version an API, and you can choose which one suits you best.<br \/>\n\u6709\u591a\u79cd\u65b9\u6cd5\u53ef\u4ee5\u5b9e\u73b0 API \u7248\u672c\u63a7\u5236\uff0c\u5e76\u4e14\u6ca1\u6709\u652f\u6301\u4e00\u79cd\u65b9\u6cd5\u7684\u6307\u5bfc\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u5c06\u5411\u60a8\u5c55\u793a\u5bf9 API \u8fdb\u884c\u7248\u672c\u63a7\u5236\u7684\u4e0d\u540c\u65b9\u6cd5\uff0c\u60a8\u53ef\u4ee5\u9009\u62e9\u6700\u9002\u5408\u60a8\u7684\u65b9\u6cd5\u3002<\/p>\n<h2>24.1 Required Package Installation and Configuration<\/h2>\n<p>24.1 \u6240\u9700\u7684\u8f6f\u4ef6\u5305\u5b89\u88c5\u548c\u914d\u7f6e<\/p>\n<p>In order to start, we have to install the Microsoft.AspNetCore.Mvc.Versioning library in the Presentation project:\u200c<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u5fc5\u987b\u5728 Presentation \u9879\u76ee\u4e2d\u5b89\u88c5 Microsoft.AspNetCore.Mvc.Versioning \u5e93\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2401.jpg\" alt=\"alt text\" \/><\/p>\n<p>This library is going to help us a lot in versioning our API.<br \/>\n\u8fd9\u4e2a\u5e93\u5c06\u5bf9\u6211\u4eec\u7684 API \u7248\u672c\u63a7\u5236\u6709\u5f88\u5927\u5e2e\u52a9\u3002<\/p>\n<p>After the installation, we have to add the versioning service in the service collection and configure it. So, let\u2019s create a new extension method in the ServiceExtensions class:<br \/>\n\u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u5728\u670d\u52a1\u96c6\u5408\u4e2d\u6dfb\u52a0\u7248\u672c\u63a7\u5236\u670d\u52a1\u5e76\u5bf9\u5176\u8fdb\u884c\u914d\u7f6e\u3002\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u5728 ServiceExtensions \u7c7b\u4e2d\u521b\u5efa\u65b0\u7684\u6269\u5c55\u65b9\u6cd5\uff1a<\/p>\n<pre><code>public static void ConfigureVersioning(this IServiceCollection services) { services.AddApiVersioning(opt =&gt; { opt.ReportApiVersions = true; opt.AssumeDefaultVersionWhenUnspecified = true; opt.DefaultApiVersion = new ApiVersion(1, 0); }); }<\/code><\/pre>\n<p>With the AddApiVersioning method, we are adding service API versioning to the service collection. We are also using a couple of properties to initially configure versioning:<br \/>\n\u4f7f\u7528 AddApiVersioning \u65b9\u6cd5\uff0c\u6211\u4eec\u5c06\u670d\u52a1 API \u7248\u672c\u63a7\u5236\u6dfb\u52a0\u5230\u670d\u52a1\u96c6\u5408\u4e2d\u3002\u6211\u4eec\u8fd8\u4f7f\u7528\u51e0\u4e2a\u5c5e\u6027\u6765\u521d\u59cb\u914d\u7f6e\u7248\u672c\u63a7\u5236\uff1a<\/p>\n<p>\u2022 ReportApiVersions adds the API version to the response header.<br \/>\nReportApiVersions \u5c06 API \u7248\u672c\u6dfb\u52a0\u5230\u54cd\u5e94\u6807\u5934\u4e2d\u3002<\/p>\n<p>\u2022 AssumeDefaultVersionWhenUnspecified does exactly that. It specifies the default API version if the client doesn\u2019t send one.<br \/>\nAssumeDefaultVersionWhenUnspecified \u6b63\u662f\u8fd9\u6837\u505a\u7684\u3002\u5982\u679c\u5ba2\u6237\u7aef\u672a\u53d1\u9001\u9ed8\u8ba4 API \u7248\u672c\uff0c\u5219\u5b83\u6307\u5b9a\u9ed8\u8ba4 API \u7248\u672c\u3002<\/p>\n<p>\u2022 DefaultApiVersion sets the default version count.<br \/>\nDefaultApiVersion \u8bbe\u7f6e\u9ed8\u8ba4\u7248\u672c\u8ba1\u6570\u3002<\/p>\n<p>After that, we are going to use this extension in the Program class:<br \/>\n\u4e4b\u540e\uff0c\u6211\u4eec\u5c06\u5728 Program \u7c7b\u4e2d\u4f7f\u7528\u6b64\u6269\u5c55\uff1a<\/p>\n<pre><code>builder.Services.ConfigureVersioning();<\/code><\/pre>\n<p>API versioning is installed and configured, and we can move on.<br \/>\nAPI \u7248\u672c\u63a7\u5236\u5df2\u5b89\u88c5\u5e76\u914d\u7f6e\u5b8c\u6bd5\uff0c\u6211\u4eec\u53ef\u4ee5\u7ee7\u7eed\u524d\u8fdb\u3002<\/p>\n<h2>24.2 Versioning Examples<\/h2>\n<p>24.2 \u7248\u672c\u63a7\u5236\u793a\u4f8b<\/p>\n<p>Before we continue, let\u2019s create another controller: CompaniesV2Controller (for example\u2019s sake), which will represent a new version of our existing one. It is going to have just one Get action:\u200c<br \/>\n\u5728\u6211\u4eec\u7ee7\u7eed\u4e4b\u524d\uff0c\u8ba9\u6211\u4eec\u521b\u5efa\u53e6\u4e00\u4e2a\u63a7\u5236\u5668\uff1aCompaniesV2Controller\uff08\u4f8b\u5982\uff0c\u4e3a\u4e86\u8d77\u89c1\uff09\uff0c\u5b83\u5c06\u4ee3\u8868\u6211\u4eec\u73b0\u6709\u63a7\u5236\u5668\u7684\u65b0\u7248\u672c\u3002\u5b83\u5c06\u53ea\u6709\u4e00\u4e2a Get\u4f5c\uff1a<\/p>\n<pre><code>[ApiVersion(&quot;2.0&quot;)] [Route(&quot;api\/companies&quot;)] [ApiController] public class CompaniesV2Controller : ControllerBase { private readonly IServiceManager _service; public CompaniesV2Controller(IServiceManager service) =&gt; _service = service; [HttpGet]public async Task&lt;IActionResult&gt; GetCompanies() { var companies = await _service.CompanyService .GetAllCompaniesAsync(trackChanges: false); return Ok(companies); } }<\/code><\/pre>\n<p>By using the [ApiVersion(\u201c2.0\u201d)] attribute, we are stating that this controller is version 2.0.<br \/>\n\u901a\u8fc7\u4f7f\u7528 [ApiVersion\uff08\u201c2.0\u201d\uff09] \u5c5e\u6027\uff0c\u6211\u4eec\u58f0\u660e\u6b64\u63a7\u5236\u5668\u662f 2.0 \u7248\u3002<\/p>\n<p>After that, let\u2019s version our original controller as well:<br \/>\n\u4e4b\u540e\uff0c\u8ba9\u6211\u4eec\u4e5f\u5bf9\u539f\u59cb\u63a7\u5236\u5668\u8fdb\u884c\u7248\u672c\u63a7\u5236\uff1a<\/p>\n<pre><code>[ApiVersion(&quot;1.0&quot;)] [Route(&quot;api\/companies&quot;)] [ApiController] public class CompaniesController : ControllerBase<\/code><\/pre>\n<p>If you remember, we configured versioning to use 1.0 as a default API version (opt.AssumeDefaultVersionWhenUnspecified = true;). Therefore, if a client doesn\u2019t state the required version, our API will use this one:<br \/>\n\u5982\u679c\u60a8\u8fd8\u8bb0\u5f97\uff0c\u6211\u4eec\u5c06\u7248\u672c\u63a7\u5236\u914d\u7f6e\u4e3a\u4f7f\u7528 1.0 \u4f5c\u4e3a\u9ed8\u8ba4 API \u7248\u672c\uff08opt.AssumeDefaultVersionWhenUnspecified = true;\u56e0\u6b64\uff0c\u5982\u679c\u5ba2\u6237\u7aef\u6ca1\u6709\u8bf4\u660e\u6240\u9700\u7684\u7248\u672c\uff0c\u6211\u4eec\u7684 API \u5c06\u4f7f\u7528\u4ee5\u4e0b\u7248\u672c\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2402.jpg\" alt=\"alt text\" \/><\/p>\n<p>If we inspect the Headers tab of the response, we are going to find that the controller V1 was assigned for this request:<br \/>\n\u5982\u679c\u6211\u4eec\u68c0\u67e5\u54cd\u5e94\u7684 Headers \u9009\u9879\u5361\uff0c\u6211\u4eec\u5c06\u53d1\u73b0\u4e3a\u6b64\u8bf7\u6c42\u5206\u914d\u4e86\u63a7\u5236\u5668 V1\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2403.jpg\" alt=\"alt text\" \/><\/p>\n<p>Of course, you can place a breakpoint in GetCompanies actions in both controllers and confirm which endpoint was hit.<br \/>\n\u5f53\u7136\uff0c\u60a8\u53ef\u4ee5\u5728\u4e24\u4e2a\u63a7\u5236\u5668\u7684 GetCompanies\u4f5c\u4e2d\u653e\u7f6e\u4e00\u4e2a\u65ad\u70b9\uff0c\u5e76\u786e\u8ba4\u547d\u4e2d\u4e86\u54ea\u4e2a\u7ec8\u7aef\u8282\u70b9\u3002<\/p>\n<p>Now, let\u2019s see how we can provide a version inside the request.<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u5728\u8bf7\u6c42\u4e2d\u63d0\u4f9b\u7248\u672c\u3002<\/p>\n<h3>24.2.1 Using Query String\u200c<\/h3>\n<p>24.2.1 \u4f7f\u7528\u67e5\u8be2\u5b57\u7b26\u4e32<\/p>\n<p>We can provide a version within the request by using a query string in the URI. Let\u2019s test this with an example:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u5728 URI \u4e2d\u4f7f\u7528\u67e5\u8be2\u5b57\u7b26\u4e32\u5728\u8bf7\u6c42\u4e2d\u63d0\u4f9b\u7248\u672c\u3002\u8ba9\u6211\u4eec\u7528\u4e00\u4e2a\u4f8b\u5b50\u6765\u6d4b\u8bd5\u4e00\u4e0b\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies?api-version=2.0\">https:\/\/localhost:5001\/api\/companies?api-version=2.0<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2404.jpg\" alt=\"alt text\" \/><\/p>\n<p>So, we get the same response body.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u5f97\u5230\u76f8\u540c\u7684\u54cd\u5e94\u6b63\u6587\u3002<\/p>\n<p>But, we can inspect the response headers to make sure that version 2.0 is used:<br \/>\n\u4f46\u662f\uff0c\u6211\u4eec\u53ef\u4ee5\u68c0\u67e5\u54cd\u5e94\u6807\u5934\u4ee5\u786e\u4fdd\u4f7f\u7528\u7248\u672c 2.0\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2405.jpg\" alt=\"alt text\" \/><\/p>\n<h3>24.2.2 Using URL Versioning\u200c<\/h3>\n<p>24.2.2 \u4f7f\u7528 URL \u7248\u672c\u63a7\u5236<\/p>\n<p>For URL versioning to work, we have to modify the route in our controller:<br \/>\n\u8981\u4f7f URL \u7248\u672c\u63a7\u5236\u6b63\u5e38\u5de5\u4f5c\uff0c\u6211\u4eec\u5fc5\u987b\u5728\u63a7\u5236\u5668\u4e2d\u4fee\u6539\u8def\u7531\uff1a<\/p>\n<pre><code>[ApiVersion(&quot;2.0&quot;)] [Route(&quot;api\/{v:apiversion}\/companies&quot;)] [ApiController] public class CompaniesV2Controller : ControllerBase<\/code><\/pre>\n<p>Also, let\u2019s just slightly modify the GetCompanies action in this controller, so we could see the difference in Postman by just inspecting the response body:<br \/>\n\u6b64\u5916\uff0c\u6211\u4eec\u53ea\u9700\u7a0d\u5fae\u4fee\u6539\u6b64\u63a7\u5236\u5668\u4e2d\u7684 GetCompanies\u4f5c\uff0c\u4ee5\u4fbf\u6211\u4eec\u53ea\u9700\u68c0\u67e5\u54cd\u5e94\u6b63\u6587\u5373\u53ef\u770b\u5230 Postman \u4e2d\u7684\u5dee\u5f02\uff1a<\/p>\n<pre><code>[HttpGet] public async Task&lt;IActionResult&gt; GetCompanies() { var companies = await _service.CompanyService .GetAllCompaniesAsync(trackChanges: false); var companiesV2 = companies.Select(x =&gt; $&quot;{x.Name} V2&quot;); return Ok(companiesV2); }<\/code><\/pre>\n<p>We are creating a projection from our companies collection by iterating through each element, modifying the Name property to contain the V2 suffix, and extracting it to a new collection companiesV2.<br \/>\n\u6211\u4eec\u5c06\u901a\u8fc7\u5faa\u73af\u8bbf\u95ee\u6bcf\u4e2a\u5143\u7d20\uff0c\u4fee\u6539 Name \u5c5e\u6027\u4ee5\u5305\u542b V2 \u540e\u7f00\uff0c\u5e76\u5c06\u5176\u63d0\u53d6\u5230\u65b0\u7684\u96c6\u5408 companiesV2 \u4e2d\uff0c\u4ece companies \u96c6\u5408\u521b\u5efa\u6295\u5f71\u3002<\/p>\n<p>Now, we can test it:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u6d4b\u8bd5\u5b83\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/2.0\/companies\">https:\/\/localhost:5001\/api\/2.0\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2406.jpg\" alt=\"alt text\" \/><\/p>\n<p>One thing to mention, we can\u2019t use the query string pattern to call the companies v2 controller anymore. We can use it for version 1.0, though.<br \/>\n\u503c\u5f97\u4e00\u63d0\u7684\u662f\uff0c\u6211\u4eec\u4e0d\u80fd\u518d\u4f7f\u7528\u67e5\u8be2\u5b57\u7b26\u4e32\u6a21\u5f0f\u6765\u8c03\u7528\u516c\u53f8 v2 \u63a7\u5236\u5668\u3002\u4e0d\u8fc7\uff0c\u6211\u4eec\u53ef\u4ee5\u5728 1.0 \u7248\u672c\u4e2d\u4f7f\u7528\u5b83\u3002<\/p>\n<h3>24.2.3 HTTP Header Versioning\u200c<\/h3>\n<p>24.2.3 HTTP \u6807\u5934\u7248\u672c\u63a7\u5236<\/p>\n<p>If we don\u2019t want to change the URI of the API, we can send the version in the HTTP Header. To enable this, we have to modify our configuration:<br \/>\n\u5982\u679c\u6211\u4eec\u4e0d\u60f3\u66f4\u6539 API \u7684 URI\uff0c\u6211\u4eec\u53ef\u4ee5\u5728 HTTP Header \u4e2d\u53d1\u9001\u7248\u672c\u3002\u8981\u542f\u7528\u6b64\u529f\u80fd\uff0c\u6211\u4eec\u5fc5\u987b\u4fee\u6539\u6211\u4eec\u7684\u914d\u7f6e\uff1a<\/p>\n<pre><code>public static void ConfigureVersioning(this IServiceCollection services) { services.AddApiVersioning(opt =&gt; { opt.ReportApiVersions = true; opt.AssumeDefaultVersionWhenUnspecified = true; opt.DefaultApiVersion = new ApiVersion(1, 0); opt.ApiVersionReader = new HeaderApiVersionReader(&quot;api-version&quot;); }); }<\/code><\/pre>\n<p>And to revert the Route change in our controller:<br \/>\n\u8981\u5728\u6211\u4eec\u7684\u63a7\u5236\u5668\u4e2d\u6062\u590d Route \u66f4\u6539\uff1a<\/p>\n<pre><code>[Route(&quot;api\/companies&quot;)]\n[ApiVersion(&quot;2.0&quot;)]<\/code><\/pre>\n<p>Let\u2019s test these changes:<br \/>\n\u8ba9\u6211\u4eec\u6d4b\u8bd5\u4e00\u4e0b\u8fd9\u4e9b\u53d8\u5316\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2407.jpg\" alt=\"alt text\" \/><\/p>\n<p>If we want to support query string versioning, we should use a new QueryStringApiVersionReader class instead:<br \/>\n\u5982\u679c\u6211\u4eec\u60f3\u652f\u6301\u67e5\u8be2\u5b57\u7b26\u4e32\u7248\u672c\u63a7\u5236\uff0c\u6211\u4eec\u5e94\u8be5\u6539\u7528\u65b0\u7684 QueryStringApiVersionReader \u7c7b\uff1a<\/p>\n<pre><code>opt.ApiVersionReader = new QueryStringApiVersionReader(&quot;api-version&quot;);<\/code><\/pre>\n<h3>24.2.4 Deprecating Versions\u200c<\/h3>\n<p>24.2.4 \u5f03\u7528\u7248\u672c<\/p>\n<p>If we want to deprecate version of an API, but don\u2019t want to remove it completely, we can use the Deprecated property for that purpose:<br \/>\n\u5982\u679c\u6211\u4eec\u60f3\u5f03\u7528 API \u7684\u7248\u672c\uff0c\u4f46\u53c8\u4e0d\u60f3\u5b8c\u5168\u5220\u9664\u5b83\uff0c\u6211\u4eec\u53ef\u4ee5\u4e3a\u6b64\u76ee\u7684\u4f7f\u7528 Deprecated \u5c5e\u6027\uff1a<\/p>\n<pre><code>[ApiVersion(&quot;2.0&quot;, Deprecated = true)]<\/code><\/pre>\n<p>We will be able to work with that API, but we will be notified that this version is deprecated:<br \/>\n\u6211\u4eec\u5c06\u80fd\u591f\u4f7f\u7528\u8be5 API\uff0c\u4f46\u4f1a\u6536\u5230\u6b64\u7248\u672c\u5df2\u5f03\u7528\u7684\u901a\u77e5\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2408.jpg\" alt=\"alt text\" \/><\/p>\n<h3>24.2.5 Using Conventions<\/h3>\n<p>24.2.5 \u4f7f\u7528\u7ea6\u5b9a<\/p>\n<p>If we have a lot of versions of a single controller, we can assign these versions in the configuration instead:<br \/>\n\u5982\u679c\u6211\u4eec\u6709\u5f88\u591a\u5355\u4e2a\u63a7\u5236\u5668\u7684\u7248\u672c\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u914d\u7f6e\u4e2d\u5206\u914d\u8fd9\u4e9b\u7248\u672c\uff1a<\/p>\n<pre><code>services.AddApiVersioning(opt =&gt; { opt.ReportApiVersions = true; opt.AssumeDefaultVersionWhenUnspecified = true; opt.DefaultApiVersion = new ApiVersion(1, 0); opt.ApiVersionReader = new HeaderApiVersionReader(&quot;api-version&quot;); opt.Conventions.Controller&lt;CompaniesController&gt;() .HasApiVersion(new ApiVersion(1, 0)); opt.Conventions.Controller&lt;CompaniesV2Controller&gt;() .HasDeprecatedApiVersion(new ApiVersion(2, 0)); });<\/code><\/pre>\n<p>Now, we can remove the [ApiVersion] attribute from the controllers.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u4ece\u63a7\u5236\u5668\u4e2d\u5220\u9664 [ApiVersion] \u5c5e\u6027\u3002<\/p>\n<p>Of course, there are a lot more features that the installed library provides for us \u2014 but with the mentioned ones, we have covered quite enough to version our APIs.<br \/>\n\u5f53\u7136\uff0c\u5df2\u5b89\u88c5\u7684\u5e93\u4e3a\u6211\u4eec\u63d0\u4f9b\u4e86\u66f4\u591a\u529f\u80fd \u2014 \u4f46\u5bf9\u4e8e\u4e0a\u8ff0\u529f\u80fd\uff0c\u6211\u4eec\u5df2\u7ecf\u6db5\u76d6\u4e86\u8db3\u591f\u591a\u7684\u529f\u80fd\u6765\u5bf9\u6211\u4eec\u7684 API \u8fdb\u884c\u7248\u672c\u63a7\u5236\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>24 VERSIONING APIS 24 \u7248\u672c\u63a7\u5236 API As our project grows, so does our knowledge; therefore, we have a better understanding of how to improve our system. Moreover, requirements change over time \u2014 thus, our API has to change as well.\u200c \u968f\u7740\u6211\u4eec\u9879\u76ee\u7684\u53d1\u5c55\uff0c\u6211\u4eec\u7684\u77e5\u8bc6\u4e5f\u5728\u589e\u957f;\u56e0\u6b64\uff0c\u6211\u4eec\u5bf9\u5982\u4f55\u6539\u8fdb\u6211\u4eec\u7684\u7cfb\u7edf\u6709\u4e86\u66f4\u597d\u7684\u7406\u89e3\u3002\u6b64\u5916\uff0c\u9700\u6c42\u4f1a\u968f\u7740\u65f6\u95f4\u7684\u63a8\u79fb\u800c\u53d8\u5316\u2014\u2014\u56e0\u6b64\uff0c\u6211\u4eec\u7684 API \u4e5f\u5fc5\u987b\u53d1\u751f\u53d8\u5316\u3002 When we implement some breaking changes, we want to ensure that we [&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-1146","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1146","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=1146"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1146\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1146"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1146"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1146"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}