使用 .NET Core 3.x 構建 RESTFUL Api


准備工作:在此之前你需要了解關於.NET .Core的基礎,前面幾篇文章已經介紹:https://www.cnblogs.com/hcyesdo/p/12834345.html

首先需要明確一點的就是REST Api它不是一個標准,而是一種架構風格

什么是WebApi?

WebApi通常是指“使用HTTP協議並通過網絡調用的API”,由於它使用了HTTP協議,所以需要通過URI信息來指定端點。

WebApi就是一個Web系統,通過訪問URI可以與其進行信息交互。

而常用的MVC模式是主要用來構建UI的架構模式。

特點:松耦合,關注點分離、MVC不是一個完整的應用程序框架

MVC映射為API呢?

Model:它賦值處理程序數據的邏輯

View:它是程序里復制展示數據的那部分。構建API的時候,VView就是數據或資源的展示。通常使用JSON格式。

Controller,它復負責View和Model之間的交互。

需要注意的是,在配置服務的時候在core3.0以前可能寫的是AddMvc,但是這個服務涉及了View視圖以及TagHelper的一些功能,所以在做WebApi的時候用不到

public void ConfigureServices(IServiceCollection services)
{
            //services.AddMvc(); core 3.0以前是這樣寫的,這個服務包括了    TageHelper等 WebApi不需要的東西,所有3.0以后可以不這樣寫
            services.AddControllers();
            
}

注意配置中間件的區域管道順序不能隨意改動。

管道就是客戶端通過一些指令指向服務器端,在這個過程中呢,會經過一些手動配置的中間件,比如說路由中間件、靜態資源中間件等,從客戶端出發到服務器端,將數據處理后,再由服務器端原路返回到客戶端這樣的一個過程。但是在請求的過程中也不排除中間件出現短路的情況,這樣也就不會進入到第二個中間件了,而是直接返回到客戶端。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
}

API對外合約:API消費者需要使用到三個概念

  • 資源的標識(URI)
  • HTTP方法(GET、POST)
  • 有效載荷

API對外提供統一資源接口,業界對RESTful資源命名也有規則

關於RESTful API約束

使用名詞而不是動詞

需求:“我想獲得系統里的所有用戶”

常見錯誤:api/getusers

分析:這里的“獲取”就是一個動詞,而我們的目的應該是“用戶”,即用戶是一個名詞

正確做法:GET api/user

要體現資源的結構/關系

通過id獲取單個用戶應該是:api/user/{userId},而不是 api/user/users這樣寫就是讓API具有很好的可讀性和可預測性

需求案例1:

系統存在兩個資源:Company(公司)、Employee(員工),現在需要獲取某個公司下的所有員工

分析:應該使用HTTP GET。API在設計的時候需要體現公司與員工的一個包含關系

常見錯誤做法:api/employees,api/employee/{companyId} 。這兩個URI都沒有體現公司和員工的一個包含關系

建議做法:api/companies/{companyId}/employees

 

需求案例2:

需要獲取某個公司下的某個員工

常見錯誤做法:api/employees/{employeeId}

建議做法:api/companies/{companyId}/employees/{employeeId}

 

自定義查詢怎么命名?

需求:獲取所有用戶信息,並且按年齡從大到小排序

常見錯誤做法:api/user/orderby/age

建議做法:api/user?orderby=age (通過QueryString查詢字符串,多條件使用 & 符號)

 

HTTP狀態碼

請求是否成功?如果請求失敗了,誰來為此負責

2xx 開頭狀態碼

200 - OK,表示請求成功

201 - Created,表示請求成功並創建了資源

204 - No Content,請求成功,但是不應該返回任何對象,例如刪除操作

3xx 開頭狀態碼

用於跳轉。例如告訴瀏覽器搜索引擎,某個頁面的網址已經永久改變,絕大多數的WebApi都不需要這類的狀態碼

4xx 開頭:客戶端錯誤

400 - Bad Request,表示API消費者發送到服務器的請求是有錯誤的

401 - Unauthorized,表示沒有提供授權信息或者提供的授權信息有誤

403 - Forbidden,表示身份認證已經通過,但是已認證的用戶卻無法訪問請求的資源

404 - NotFound,表示請求的資源不存在

405 - Method not allowed,當嘗試發送請求到資源的時候,使用了不被支持的HTTP方法

406 - Not acceptable,表示API消費者請求的表述格式並不被WebApi所支持,並且API不會提供默認的表述格式

5xx 開頭狀態碼

500 - Internal serever error,表示服務器出現了錯誤,客戶端無能為力,只能以后再試試

還有就是RESTful API 返回的結果不一定Json格式的

關於如何標注路由屬性 uri ?

先看控制器代碼:

using Microsoft.AspNetCore.Mvc;
using Routine.Api.Service;
using System;
using System.Threading.Tasks;

namespace Routine.Api.Controllers
{
    [ApiController] //好處:ApiController不是強制的
                    //1.會啟用使用屬性路由(Attribute Routing)
                    //2.自動HTTP 400響應
                    //3.推斷參數的綁定源
                    //4.Multipart/form-data 請求推斷
                    //5.錯誤狀態代碼的問題詳細信息
    [Route("api/companies")]    //寫法一
    //[Route("api/[controller]")]   //寫法二:意思是相當於刨除了Controller后綴,獲取前面的 Companies C可以是小寫,如果你改名了那么你路由的uri也跟着變了(不建議這樣寫)
    public class CompaniesController:ControllerBase
    {
        private readonly ICompanyRepository _companyRepository;
        public CompaniesController(ICompanyRepository companyRepository)
        {
            _companyRepository = companyRepository ?? 
                                 throw new ArgumentNullException(nameof(companyRepository));
        }
        [HttpGet]
        //IActionResult定義了一些合約,它可以代表ActionResult返回的結果
        public async Task<IActionResult> GetCompanies()
        {
            var companies =await _companyRepository.GetCompaniesAsync();//讀取出來的是List
            return Ok(companies); 
        }

        [HttpGet("{companyId}")] // Controller標注了ApiController => uri=> api/companies/{companyId}
        public async Task<IActionResult> GetCompany(Guid companyId)
        {
            //判斷該公司是否存在方法一:這種方法在處理並發請求時可能會出現錯誤,原因是查到之后,進行刪除,進入company后也可能是404找不到了
            //var exists =await _companyRepository.CompanyExistsAsync(compamyId);
            //if (!exists)
            //{
            //    //不存在應該返回404
            //    return NotFound();
            //}
            var company = await _companyRepository.GetCompanyAsync(companyId);//讀取出來的是List
            //方法二
            if (company==null)
            {
                return NotFound();
            }
            return Ok(company);
        }
    }
}

為了更好的構建RESTful API 對於 uri 的設計規則也有很嚴格的要求。

在控制器標注 ApiController,它會自動啟用路由屬性

通過 [Route] 設計路由規則

比如:接口一:GetCompanies,請求的方式:GET,通過Route 去設置路由規則 [Router("api/companies")],即查詢所有公司信息

[Router("api/companies")] => api/companies

接口二:GetCompany,請求方式:GET,只不過在 添加了 [HTTPGET("{companyId}")] =>api/companies/{companyId},即查詢某一公司的信息

關於第二種路由寫法請看注釋

通過Postman工具測試一下

測試一:接口一

測試二:接口二

以上兩個接口測試完畢!!!

對於ASP.NET Core 3.x以前對於 404 NotFound請求狀態碼輸出的格式不太友好,而ASP.NET Core 3.x對於404請求狀態碼也做了友好的提示。

現在將接口偽造錯誤信息,提示 404 如圖:

關於構建 RESTful API 存在的內容協商:

所謂內容協商就是這樣一個過程,針對一個響應,當有多種表述格式可用時,選取最佳的一種表述格式,這些表述可以是XML,JSON,甚至是自定義的格式規則

Accept Header:負責指定輸出類型

Media Type(媒體類型)

  • application/json
  • application/xml

404 Not Acceptable

輸出格式:ASP.NET Core 里面對應的就是 Output Formatters 我們稱為:輸出類型的格式化器

也就是說如果一個API消費者,設置了Accept Header的媒體類型為Json,那么這個RESTful API也應該返回的是JSON,

但是呢如果服務器只接收XML的格式,這個時候請求的媒體類型不被服務器所接受,那么就會返回 406 這個狀態碼

總而言之,盡量避免不寫Accept Header,避免客戶端和服務器端接收和返回的類型不一致導致錯誤。

有輸出那么就會有輸入了!!!

Content-Type-Header:負責指定輸入

Media Type(媒體類型)

  • application/json
  • application/xml

輸出格式:ASP.NET Core里面對應的就是 Input Formatters

比如說:對於一個客戶端的POST請求,即添加資源信息,那么就需要輸入參數,這些參數可能是放在Body里面,那么在Body里面的這些參數可能是對象的那種格式。那么我們就需要通過 Content-Type-Header來確定Body里面的參數是什么樣的類型,可能是Json也可能是Xml或者是自定義的格式,指明之后,RESTful API才能更好的對這些參數進行處理。

看Startup類代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Routine.Api.Data;
using Routine.Api.Service;

namespace Routine.Api
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            //services.AddMvc(); core 3.0以前是這樣寫的,這個服務包括了TageHelper等 WebApi不需要的東西,所有3.0以后可以不這樣寫
            services.AddControllers(setup =>
            {
                //setup.ReturnHttpNotAcceptable=false;//如果客戶端默認為xml格式,服務器端為json,false就不會返回406
                setup.ReturnHttpNotAcceptable = true;//如果請求的類型和服務器請求的類型不一致就返回406
               
                //setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
                //setup.OutputFormatters.Insert(0, new XmlDataContractSerializerOutputFormatter());
            }).AddXmlDataContractSerializerFormatters();

            //配置接口服務:涉及到這個服務注冊的生命周期這里采用AddScoped,表示每次的Http請求
            services.AddScoped<ICompanyRepository, CompanyRepository>();

            //獲取配置文件中的數據庫字符串連接
            var sqlConnection = Configuration.GetConnectionString("SqlServerConnection");

            //配置上下文類DbContext,因為它本身也是一套服務
            services.AddDbContext<RoutineDbContext>(options =>
            {
                options.UseSqlServer(sqlConnection);
            });
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

setup.ReturnHttpNotAcceptable就是處理是在客戶端與服務器端數據產生沖突時,是否要即將產生 406 的狀態碼。

  • true:產生
  • false:不產生

setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter())

分析:實際上OutputFormatters 是一個集合 ,通過Add方法添加服務器允許接受XML格式的數據功能。因為集合中默認只有Json

setup.OutputFormatters.Insert(0,new XmlDataContractSerializerOutputFormatter())

分析:實際上剛剛寫的是一種方法。Insert就是指明格式順序,默認是JSON,通過Insert設置 0 ,就是指明XML為默認接受的數據格式

實際上以上兩種寫法都是 ASP.NET Core 3.x以前的寫法。

ASP.NET Core 3.x的實際寫法:就是在AddControllers后面添加XmlDataContractSerializerOutputFormatter方法。這樣不管是輸入輸出都已經設置好了XML的格式數據

postman接口測試:取消setup.OutputFormatters.Insert(0,new XmlDataContractSerializerOutputFormatter())的注釋

默認xml:

最后,關於構建RESTFUL Api的URI規則及原理

動作 HTTP方法 請求的參數(Payload) 參數位置 URI 請求前 請求后 響應內容
查詢 GET 查詢參數 可含查詢字符串(QueryString)

/api/companies/{companyId}

/api/companies

無修改

單個資源

多個資源的集合

創建/添加 POST 要創建的單個資源信息 Body /api/companies 新創建的單個資源
局部修改/更新 PATCH

待修改的資源

JsonPatchDocument

Body /api/companies/{companyId}

a:1

b:2

a:1

b:3

無須返回
 替換 PUT 要替換的單個資源信息 Body  /api/companies/{companyId}

a:1

b:2

a:2

b:3

無須返回
使用預定義的表示進行創建 PUT 要創建的單個資源信息 Body /api/companies/{companyId} 返回新創建的資源
移除/刪除 DELETE 可含查詢字符串(QueryString) /api/companies/{companyId}

a

b

a 無須返回

.NET Core 3.x構建RESTful API 待續!!!


免責聲明!

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



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