或許是你應該了解的一些 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"
}

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

"Application": {
    "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.
public void ConfigureServices(IServiceCollection services)
{
    // Config api version
    services.AddApiVersion();
}

  現在我們刪除項目創建時默認生成的 ValuesController,在 Controllers 目錄下建立一個 v1 文件夾,代表此文件夾下都是 v1 版本的控制器。添加一個 UsersController 用來獲取系統的用戶資源,現在項目的文件結構如下圖所示。

  現在我們來改造我們的 UsersController,我們只需要在 Controller 或是 Action 上添加 ApiVersion 特性就可以指定當前 Controller/Action 的版本信息。同時,因為我需要將 API 的版本信息添加到生成的 URL 地址中,所以這里我們需要修改特性路由的模板,將我們的版本以占位符的形式添加到生成的路由 URL 地址中,修改完成后的代碼及實現的效果如下所示。

[ApiVersion("1.0")]
[ApiController]
[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 = "danvic96@hotmail.com",
                    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 文檔。

public void ConfigureServices(IServiceCollection services)
{
    // 添加對於 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 的擴展方法中調用這個過濾方法,從而實現移除版本信息的目的,擴展方法中的添加位置如下所示。

public static void AddSwagger(this IServiceCollection services)
{
    // 配置 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 並沒有定義具體的屬性信息,所以這里顯示的是一個不包含任何屬性的對象數組。

/// <summary>
/// 獲取全部的用戶信息
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<UserListDto>), 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 狀態代碼的缺失特性說明做為一個警告,並提供了修復此問題的選項,我們進行修復后就可以自動添加特性。

/// <summary>
/// 獲取用戶詳細信息
/// </summary>
/// <param name="id">用戶唯一標識</param>
/// <returns></returns>
[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://cloud.tencent.com/developer/support-plan?invite_code=1jarnly4f8ua3


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM