天天看點

ASP.NET Core Web API 使用小技巧

ASP.NET Core Web API 使用小技巧

一、前言#

  在目前的軟體開發的潮流中,不管是前後端分離還是服務化改造,後端更多的是通過建構 API 接口服務進而為 web、app、desktop 等各種用戶端提供業務支援,如何建構一個符合規範、容易了解的 API 接口是我們後端開發人員需要考慮的。在本篇文章中,我将列舉一些我在使用 ASP.NET Core Web API 建構接口服務時使用到的一些小技巧,因才疏學淺,可能會存在不對的地方,歡迎指出。

  代碼倉儲:

https://github.com/Lanesra712/ingos-server

二、Step by Step#

  因為本篇文章中涉及到的一些知識點在之前的文章中也已經有具體的解釋了,是以這裡隻會說明如何在 ASP.NET Core Web API 中如何去使用,不會做過多的詳細介紹。如果你需要詳細了解的話,可以跳轉到文章中給出的外鍊位址去檢視。

  本篇文章中使用的代碼是基于 .NET Core 2.2 + .NET Standard 2.0 進行建構的,如果你采用的版本與我使用的不同,可能最終實作起來的代碼會有所不同,請提前知悉。同時,本篇文章中所有示例代碼都會存在于前言中所列出的 github repo 中,我會嘗試将每個功能點的開發作為一次 commit,并且也會在後續進行不定期的更新完善,最終搭建一個基于領域驅動思想的後端項目模闆,如果對你有幫助的話,歡迎持續關注。

  1、使用小寫路由#

  在我之前的一篇文章中(建構可讀性更高的 ASP.NET Core 路由)有提到過,因為 .NET 預設采用 Pascal 的類命名方式,如果采用預設生成的路由,最終建構出的路由位址會存在大小寫混在一起的情況,雖然在 .NET Core 中大小寫的路由位址最終都會對于到正确的資源上,但是為了更好的符合前端的規範,是以這裡我們首先按照之前的文章中所列出的方法去修改預設生成的路由位址格式。

  因為這裡我們最終想要實作的是符合 Restful 風格的 API 接口,是以這裡我們首先需要将預設生成的 URL 位址改為全小寫模式。

複制代碼

public void ConfigureServices(IServiceCollection services)

{

// 采用小寫的 URL 路由模式
services.AddRouting(options =>
{
    options.LowercaseUrls = true;
});           

}

  如果你有看過建構可讀性更高的 ASP.NET Core 路由這篇文章,你會發現其實我們最終實作的是 hyphen(-) 格式的 Url 位址,那麼這裡我們為什麼不進行後續的修改了呢?

  如果你有檢視 .NET Core 預設模闆中生成的 API Controller,仔細看下,這裡其實是使用的特性路由,是以這裡我們并不能通過 Startup.UseMvc 定義的傳統路由模闆,或是直接在 Startup.Configure 中的 UseMvcWithDefaultRoute 方法去修改我們的生成的路由位址格式。

[Route("api/[controller]")]

[ApiController]

public class ValuesController : ControllerBase

  2、允許跨域請求#

  不管是後端接口的服務化改造,還是隻是單純的前後端分離項目開發,我們的前端項目與後端接口通常不會部署在一起,是以我們需要解決前端通路接口時會涉及到的跨域通路的問題。

  針對跨域請求,我們可以采用 jsonp、或者是通過給 nginx 伺服器配置響應的 header 參數頭資訊、或者是使用 CORS,又或是其它的解決方案。你可以自由選擇,這裡我采用在後端接口中直接配置對于 CORS 的支援。

  在 .NET Core 中,已經在 Microsoft.AspNetCore.Cors 這個類庫中添加了對于 CORS 的支援,因為這個類庫是存在于我們已經安裝的 .NET Core SDK 中,是以這裡我們并不需要通過 Nuget 進行安裝,可以直接使用。

  在 .NET Core 中配置 CORS 規則,我們可以通過在 Startup.ConfigureServices 這個方法中添加不同的授權政策,之後再針對某個 Controller 或是 Action 通過添加 EnableCors 這個 Attribute 的方式進行配置,這裡如果指定了 policy 政策名稱,則會使用指定的政策,如果沒有指定,則适用于系統的預設配置。同樣的,我們也可以隻設定一個政策,直接針對整個項目進行配置,這裡我采用對整個項目采用通用的跨域請求配置方案。

  在配置 CORS 政策時,我們可以設定隻允許來源于某些 URL 位址的請求可以通路,或者是指定接口隻允許某些 HTTP 方法進行通路,或者是在請求的 header 中必須包含某些資訊才可以通路我們的接口。

  在下面的代碼中,我定義了針對整個項目的跨域請求政策,這裡我隻是設定了對于接口請求方 URL 位址的控制,通過讀取配置檔案中的資料,進而達到隻允許某些 IP 可以通路的我們接口的目的。

public class Startup

// 預設的跨域請求政策名稱
private const string _defaultCorsPolicyName = "Ingos.Api.Cors";

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(
        // 添加 CORS 授權過濾器
        options => options.Filters.Add(new CorsAuthorizationFilterFactory(_defaultCorsPolicyName))
    ).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    // 配置 CORS 授權政策
    services.AddCors(options => options.AddPolicy(_defaultCorsPolicyName,
        builder => builder.WithOrigins(
                Configuration["Application:CorsOrigins"]
                .Split(",", StringSplitOptions.RemoveEmptyEntries).ToArray()
            )
        .AllowAnyHeader()
        .AllowAnyMethod()
        .AllowCredentials()));
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // 允許跨域請求通路
    app.UseCors(_defaultCorsPolicyName);
}           

  例如在下面的設定中,我隻允許這一個位址可以通路我們的接口,如果需要指定多個的話,則可以通過英文的 , 進行分隔。

"Application": {

"CorsOrigins": "http://127.0.0.1:5050"           

  某些情況下,如果我們不想進行限制的話,隻需要将值改為 * 即可。

"CorsOrigins": "*"           

  3、添加接口版本控制#

  在一些涉及到接口功能更新的場景下,當我們需要修改接口邏輯而舊版本的接口無法停用的情況時,為了減少對于原有接口的影響,我們可以采取為接口添加版本資訊的形式,進而降低因采用不同版本而造成的影響。如果你想要詳細了解的話,可以檢視這篇文章,電梯直達 =》ASP.NET Core 實戰:建構帶有版本控制的 API 接口。

   在實作具有版本控制的接口前,首先我們需要通過 Nuget 添加下面的兩個 dll,因為我是在 Ingos.Api.Core 這個類庫中進行配置的,是以我安裝到了這個類庫下,你需要根據你自己的情況選擇最終是安裝到 Api 接口項目中還是在别的類庫下。

Install-Package Microsoft.AspNetCore.Mvc.Versioning

Install-Package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer

  在安裝完成之後,我們就可以在 Startup.ConfigureServices 方法中,為項目中的接口配置版本資訊,這裡我采用的方案是将版本号添加到接口的 URL 位址中。

  因為對于所有中間件的配置都會在 Startup.ConfigureServices 方法中,為了保持該方法的純淨性,這裡我寫了一個擴充方法用于配置我們的 api 的版本,之後直接調用即可。

public static class ApiVersionExtension

/// <summary>
/// 添加 API 版本控制擴充方法
/// </summary>
/// <param name="services">生命周期中注入的服務集合 <see cref="IServiceCollection"/></param>
public static void AddApiVersion(this IServiceCollection services)
{
    // 添加 API 版本支援
    services.AddApiVersioning(o =>
    {
        // 是否在響應的 header 資訊中傳回 API 版本資訊
        o.ReportApiVersions = true;

        // 預設的 API 版本
        o.DefaultApiVersion = new ApiVersion(1, 0);

        // 未指定 API 版本時,設定 API 版本為預設的版本
        o.AssumeDefaultVersionWhenUnspecified = true;
    });

    // 配置 API 版本資訊
    services.AddVersionedApiExplorer(option =>
    {
        // api 版本分組名稱
        option.GroupNameFormat = "'v'VVVV";

        // 未指定 API 版本時,設定 API 版本為預設的版本
        option.AssumeDefaultVersionWhenUnspecified = true;
    });
}           

  擴充方法最終實作方式如上面的代碼所示,之後我們就可以直接在 ConfigureServices 方法中直接進行調用這個擴充方法就可以了。

// This method gets called by the runtime. Use this method to add services to the container.

// Config api version
services.AddApiVersion();           

  現在我們删除項目建立時預設生成的 ValuesController,在 Controllers 目錄下建立一個 v1 檔案夾,代表此檔案夾下都是 v1 版本的控制器。添加一個 UsersController 用來擷取系統的使用者資源,現在項目的檔案結構如下圖所示。

  現在我們來改造我們的 UsersController,我們隻需要在 Controller 或是 Action 上添加 ApiVersion 特性就可以指定目前 Controller/Action 的版本資訊。同時,因為我需要将 API 的版本資訊添加到生成的 URL 位址中,是以這裡我們需要修改特性路由的模闆,将我們的版本以占位符的形式添加到生成的路由 URL 位址中,修改完成後的代碼及實作的效果如下所示。

[ApiVersion("1.0")]

[Route("api/v{version:apiVersion}/[controller]")]

public class UsersController : ControllerBase

  4、添加對于 Swagger 接口文檔的支援#

  在前後端分離開發的情況下,我們需要提供給前端開發人員一個接口文檔,進而讓前端開發人員知道以什麼樣的 HTTP 方法或是傳遞什麼樣的參數給後端接口,進而擷取到正确的資料,而 Swagger 則提供了一種自動生成接口文檔的方式,同時也提供類似于 Postman 的功能,可以實作對于接口的實時調用測試。

  首先,我們需要通過 Nuget 添加 Swashbuckle.AspNetCore 這個 dll 檔案,之後我們就可以在此基礎上實作對于 Swagger 的配置。

Install-Package Swashbuckle.AspNetCore

  與上面配置 API 接口的版本資訊相似,這裡我依舊采用建構擴充方法的方式來實作對于 Swagger 中間件的配置。具體的配置過程可以檢視我之前寫的文章(ASP.NET Core 實戰:建構帶有版本控制的 API 接口),這裡隻列出最終配置完成的代碼。

public static void AddSwagger(this IServiceCollection services)

// 配置 Swagger 文檔資訊
services.AddSwaggerGen(s =>
{
    // 根據 API 版本資訊生成 API 文檔
    //
    var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();

    foreach (var description in provider.ApiVersionDescriptions)
    {
        s.SwaggerDoc(description.GroupName, new Info
        {
            Contact = new Contact
            {
                Name = "Danvic Wang",
                Email = "[email protected]",
                Url = "https://yuiter.com"
            },
            Description = "Ingos.API 接口文檔",
            Title = "Ingos.API",
            Version = description.ApiVersion.ToString()
        });
    }

    // 在 Swagger 文檔顯示的 API 位址中将版本資訊參數替換為實際的版本号
    s.DocInclusionPredicate((version, apiDescription) =>
    {
        if (!version.Equals(apiDescription.GroupName))
            return false;

        var values = apiDescription.RelativePath
            .Split('/')
            .Select(v => v.Replace("v{version}", apiDescription.GroupName)); apiDescription.RelativePath = string.Join("/", values);
        return true;
    });

    // 參數使用駝峰命名方式
    s.DescribeAllParametersInCamelCase();

    // 取消 API 文檔需要輸入版本資訊
    s.OperationFilter<RemoveVersionFromParameter>();

    // 擷取接口文檔描述資訊
    var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);
    var apiPath = Path.Combine(basePath, "Ingos.Api.xml");
    s.IncludeXmlComments(apiPath, true);
});           

  當我們配置完成後就可以在 Startup 類中去啟用 Swagger 文檔。

// 添加對于 swagger 文檔的支援
services.AddSwagger();           

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)

// 啟用 Swagger 文檔
app.UseSwagger();
app.UseSwaggerUI(s =>
{
    // 預設加載最新版本的 API 文檔
    foreach (var description in provider.ApiVersionDescriptions.Reverse())
    {
        s.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
            $"Sample API {description.GroupName.ToUpperInvariant()}");
    }
});           

  因為我們在之前設定建構的 API 路由時包含了版本資訊,是以在最終生成的 Swagger 文檔中進行測試時,我們都需要在參數清單中添加 API 版本這個參數。這無疑是有些不友善,是以這裡我們可以通過繼承 IOperationFilter 接口,控制在生成 API 文檔時移除 API 版本參數,接口的實作方法如下所示。

public class RemoveVersionFromParameter : IOperationFilter

public void Apply(Operation operation, OperationFilterContext context)
{
    var versionParameter = operation.Parameters.Single(p => p.Name == "version");
    operation.Parameters.Remove(versionParameter);
}           

  當我們實作自定義的接口後就可以在之前針對 Swagger 的擴充方法中調用這個過濾方法,進而實作移除版本資訊的目的,擴充方法中的添加位置如下所示。

// 配置 Swagger 文檔資訊
services.AddSwaggerGen(s =>
{
    // 取消 API 文檔需要輸入版本資訊
    s.OperationFilter<RemoveVersionFromParameter>();
});           

  最終的實作效果如下圖所示,可以看到,參數清單中已經沒有版本資訊這個參數,但是我們在進行接口測試時會自動幫我們添加上版本參數資訊。

  這裡需要注意,因為我們需要在最終生成的 Swagger 文檔中顯示出我們對于 Controller 或是 Action 添加的注釋資訊,是以這裡我們需要在 Web Api 項目的屬性選項中勾選上輸出 XML 文檔檔案。同時如果你不想 VS 一直提示你有方法沒有添加參數資訊,這裡我們可以在取消顯示警告這裡添加上 1591 這個參數。

  5、建構符合 Restful 風格的接口#

  在沒有采用 Restful 風格來建構接口傳回值時,我們可能會習慣于在接口傳回的資訊中添加一個接口是否請求成功的辨別,就像下面代碼中示例的這種傳回形式。

sueecss: true
msg: '',
data: [{
    id: '20190720214402',
    name: 'zhangsan'
}]           

  但是,當我們想要建構符合 Restful 風格的接口時,我們就不能再這樣進行設計了,我們應該通過傳回的 HTTP 響應狀态碼來辨別這次通路是否成功。一些比較常用的 HTTP 狀态碼如下表所示。

HTTP 狀态碼 涵義 解釋說明

200 OK 用于一般性的成功傳回,不可用于請求錯誤傳回

201 Created 資源被建立

202 Accepted 用于資源異步處理的傳回,僅表示請求已經收到。對于耗時比較久的處理,一般用異步處理來完成

204 No Content 此狀态可能會出現在 PUT、POST、DELETE 的請求中,一般表示資源存在,但消息體中不會傳回任何資源相關的狀态或資訊

400 Bad Request 用于用戶端一般性錯誤資訊傳回, 在其它 4xx 錯誤以外的錯誤,也可以使用,錯誤資訊一般置于 body 中

401 Unauthorized 接口需要授權通路,為通過授權驗證

403 Forbidden 目前的資源被禁止通路

404 Not Found 找不到對應的資訊

500 Internal Server Error 伺服器内部錯誤

  我們知道 HTTP 共有四個謂詞方法,分别為 Get、Post、Put 和 Delete,在之前我們可能更多的是使用 Get 和 Post,對于 Put 和 Delete 方法可能并不會使用。同樣的,如果我們需要建立符合 Restful 風格的接口,我們則需要根據這四個 HTTP 方法謂詞一些約定俗成的功能定義去定義對應接口的 HTTP 方法。

HTTP 謂詞方法 解釋說明

GET 擷取資源資訊

POST 送出新的資源資訊

PUT 更新已有的資源資訊

DELETE 删除資源

  例如,對于一個擷取所有資源的方法,我們可能會定義接口的預設傳回 HTTP 狀态碼為 200 或是 400,當狀态碼為 200 時,代表資料擷取成功,接口可以正常傳回資料,當狀态碼為 400 時,則代表接口通路出現問題,此時則傳回錯誤資訊對象。

  在 ASP.NET Core Web API 中,我們可以通過在 Action 上添加 ProducesResponseType 特性來定義接口的傳回狀态碼。通過 F12 按鍵我們可以進入 ProducesResponseType 這個特性,可以看到這個特性存在兩個構造方法,我們可以隻定義接口傳回 HTTP 狀态碼或者是在定義接口傳回的狀态碼時同時傳回的具體對象資訊。

  上面給出的接口案例的示例代碼如下所示,從下圖中可以看到,Swagger 會自動根據我們的 ProducesResponseType 特性來列出我們接口可能傳回的 HTTP 狀态碼和對象資訊。這裡因為是示例程式,UserListDto 并沒有定義具體的屬性資訊,是以這裡顯示的是一個不包含任何屬性的對象數組。

///

/// 擷取全部的使用者資訊

[HttpGet]

[ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]

[ProducesResponseType(StatusCodes.Status400BadRequest)]

public IActionResult Get()

// 1、擷取資源資料

// 2、判斷資料擷取是否成功
if (true)
    return Ok(new List<UserListDto>());
else
    return BadRequest(new
    {
        statusCode = StatusCodes.Status400BadRequest,
        description = "錯誤描述",
        msg = "錯誤資訊"
    });           

  可能這裡你可能會有疑問,當接口傳回的 HTTP 狀态碼為 400 時,傳回的資訊是什麼鬼,與我們定義的錯誤資訊對象字段不同啊?原來,在 ASP.NET Core 2.1 之後的版本中,對于 API 接口傳回 400 的 HTPP 狀态碼會預設傳回 ProblemDetails 對象,因為這裡我們并沒有将接口中的傳回 BadRequest 中的錯誤資訊對象作為 ProducesResponseType 特性的構造函數的參數,是以這裡就采用了預設的錯誤資訊對象。

  當然,當接口的 HTTP 傳回狀态碼為 400 時,最終還是會傳回我們自定義的錯誤資訊對象,是以這裡為了不造成前後端對接上的歧義,我們最好将傳回的對象資訊也作為參數添加到 ProducesResponseType 特性中。

  同時,除了上面示例的接口中通過傳回 OK 方法和 BadRequest 方法來表明接口的傳回 HTTP 狀态碼,在 ASP.NET Core Web API 中還有下列繼承于 ObjectResult 的方法來表明接口傳回的狀态碼,對應資訊如下。

HTTP 狀态碼 方法名稱

200 OK()

201 Created()

202 Accepted()

204 NoContent()

400 BadRequest()

401 Unauthorized()

403 Forbid()

404 NotFound()

  6、使用 Web API 分析器#

  在上面的示例中,因為我們需要指定接口需要傳回的 HTTP 狀态碼,是以我們需要提前添加好 ProducesResponseType 特性,在某些時候我們可能在代碼中添加了一種 HTTP 狀态碼的傳回結果,可是卻忘了添加特性描述,那麼有沒有一種便捷的方式提示我們呢?

  在 ASP.NET Core 2.2 及以後更新的 ASP.NET Core 版本中,我們可以通過 Nuget 去添加 Microsoft.AspNetCore.Mvc.Api.Analyze 這個包,進而實作對我們的 API 進行分析,首先我們需要将這個包添加到我們的 API 項目中。

Install-Package Microsoft.AspNetCore.Mvc.Api.Analyzers

   例如在下面的接口代碼中,我們根據使用者的唯一辨別去尋找使用者資料,當擷取不到資料的時候,傳回的 HTTP 狀态碼為 400,而我們隻添加了 HTTP 狀态碼為 200 的特性說明。此時,分析器将 HTTP 404 狀态代碼的缺失特性說明做為一個警告,并提供了修複此問題的選項,我們進行修複後就可以自動添加特性。

/// 擷取使用者詳細資訊

/// 使用者唯一辨別

[HttpGet("{id}")]

[ProducesResponseType(typeof(UserEditDto), StatusCodes.Status200OK)]

public IActionResult Get(string id)

// 1、根據 Id 擷取使用者資訊
UserEditDto user = null;

if (user == null)
    return NotFound();
else
    return Ok(user);           

  但是,在自動完成文檔補全後其實還是需要我們進行一些操作的,例如,如果我們需要指定傳回值的 Type 類型,還是需要我們自己手動添加到 ProducesResponseType 特性上的。

  在進行特性補齊的時候,分析器也幫我們填加了一個 ProducesDefaultResponseType 特性。通過在微軟的文檔中指向的 Swagger 文檔(Swagger Default Response)中可以了解到,如果我們接口不管是什麼狀态,最終傳回的 response 響應結構都是相同的,我們就可以直接使用 ProducesDefaultResponseType 特性來指定 response 的響應結構,而不需要每個 HTTP 狀态都添加一個特性。

三、總結#

   在本篇文章中,主要介紹了一些我在使用 ASP.NET Core Web API 的過程中使用到的一些小技巧,以及在以前踩過坑後的一些解決方案,如果對你能有一點的幫助的話,不勝榮幸。同時,如果你有更好的解決方案,或者是針對一些你之前踩過的 Web API 坑的解決方案,也歡迎你在評論區中提出。

作者:墨墨墨墨小宇

出處:

https://www.cnblogs.com/danvic712/p/11255423.html

繼續閱讀