1. 程式人生 > >【ASP.NET Core學習】Web API

【ASP.NET Core學習】Web API

 這裡介紹在ASP.NET Core中使用Web API建立 RESTful 服務,本文使用VSCode + NET Core3.0

  1. 建立簡單Rest API
  2. 格式化輸出
  3. JSON Patch請求
  4. Open API(Swagger)整合

建立簡單Rest API

在終端輸入

dotnet new webapi -n WebAPI

1. 建立Order模型,然後初始化資料

public class OrderStore
{
    public List<Order> Orders { get; } = new List<Order>();

    public OrderStore()
    {
        var random = new Random();
        foreach (var item in Enumerable.Range(1, 10))
        {
            Orders.Add(new Order
            {
                Id = item,
                OrderNo = DateTime.Now.AddSeconds(random.Next(100, 200)).AddMilliseconds(random.Next(20, 50)).Ticks.ToString(),
                Quantity = random.Next(1, 10),
                Amount = Math.Round(((decimal)random.Next(100, 500) / random.Next(2, 6)), 2)
            });
        }
    }
}
View Code

2. 簡單REST API介面

/// <summary>
/// 訂單模組
/// </summary>
[ApiController]
[Route("[controller]")]
[FormatFilter]
public class OrderController : ControllerBase
{

    readonly Models.OrderStore _orderStore = null;
    public OrderController(Models.OrderStore orderStore)
    {
        _orderStore = orderStore;
    }

    /// <summary>
    /// 查詢所有訂單
    /// </summary>
    [HttpGet]
    public ActionResult<List<Models.Order>> GetAll() => _orderStore.Orders;

    /// <summary>
    /// 獲取訂單    
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpGet("{id:int}.{format?}")]
    public ActionResult<Models.Order> GetById(int id)
    {
        var order = _orderStore.Orders.FirstOrDefault(m => m.Id == id);

        if (order == null)
        {
            return NotFound();
        }

        return order;
    }

    /// <summary>
    /// 建立訂單
    /// </summary>
    /// <param name="order"></param>
    /// <returns>成功返回訂單Id,失敗返回-1</returns>
    [HttpPost]
    public ActionResult<int> Create(Models.Order order)
    {
        if (_orderStore.Orders.Any(m => m.OrderNo == order.OrderNo))
        {
            return -1;
        }

        order.Id = _orderStore.Orders.Max(m => m.Id) + 1;
        _orderStore.Orders.Add(order);

        return order.Id;
    }

    /// <summary>
    /// 更新訂單
    /// </summary>
    /// <returns></returns>
    [HttpPut]
    public ActionResult<bool> Update(Models.Order model)
    {
        Console.WriteLine($"OrderNo:{model.OrderNo}");
        var order = _orderStore.Orders.FirstOrDefault(m => m.OrderNo == model.OrderNo);

        if (order == null)
        {
            return NotFound();
        }

        order.Amount = model.Amount;
        order.Quantity = model.Quantity;

        return true;
    }

    /// <summary>
    /// 更新訂單指定資訊
    /// </summary>
    /// <remarks>
    /// Sample request:
    /// 
    ///     PATCH  /Order/{orderNo} 
    ///     [
    ///         {
    ///             "op": "test",
    ///             "path": "/quantity",
    ///             "value": "2"
    ///         },
    ///         {
    ///             "op": "test",
    ///             "path": "/amount",
    ///             "value": "38.28"
    ///         },
    ///         {
    ///             "op": "add",
    ///             "path": "/isComplete",
    ///             "value": "true"
    ///         },
    ///     ]
    /// </remarks>
    /// <returns>返回是否成功</returns>
    /// <response code="200">提交成功</response>
    /// <response code="400">提交引數異常</response>    
    /// <response code="404">訂單號不存在</response>
    [HttpPatch("{orderNo:length(18)}")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public ActionResult<bool> Update([FromBody] JsonPatchDocument<Models.Order> patchDoc, [FromRoute] string orderNo)
    {
        var order = _orderStore.Orders.FirstOrDefault(m => m.OrderNo == orderNo);

        if (order == null)
        {
            return NotFound();
        }

        patchDoc.ApplyTo(order, ModelState);

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        return Ok(true);
    }
}
View Code

3. 推薦一個VS Code外掛(REST Client)測試介面,官方介紹

@baseUrl = https://localhost:5001

###
GET {{baseUrl}}/Order HTTP/1.1

### 
# @name order
POST {{baseUrl}}/Order HTTP/1.1
Accept: application/json
Content-Type: application/json

{
    "OrderNo": "637109312996909246",
    "Quantity": 2,
    "Amount": 38.28
}

### 

@orderId = {{order.response.body.*}}
GET {{baseUrl}}/Order/{{orderId}}.json HTTP/1.1

### 
GET {{baseUrl}}/Order/{{orderId}}.xml HTTP/1.1
###
GET {{baseUrl}}/Order/{{orderId}} HTTP/1.1
###

PUT {{baseUrl}}/Order HTTP/1.1
Content-Type: application/json
Accept: application/json

{
    "Id": 12,
    "OrderNo": "2019112235759329",
    "Quantity": 2,
    "Amount": 38.28
}

###

GET {{baseUrl}}/Order/11

###

PATCH  {{baseUrl}}/Order/637109312996909246 HTTP/1.1
Accept: application/json
Content-Type: application/json

[
  {
    "op": "test",
    "path": "/quantity",
    "value": "2"
  },
  {
    "op": "test",
    "path": "/amount",
    "value": "38.28"
  },
  {
    "op": "add",
    "path": "/isComplete",
    "value": "true"
  },
]




Sample request:

PATCH  /Order/{orderNo} 

[
  {
    "op": "test",
    "path": "/quantity",
    "value": "2"
  },
  {
    "op": "test",
    "path": "/amount",
    "value": "38.28"
  },
  {
    "op": "add",
    "path": "/isComplete",
    "value": "true"
  },
]
View Code

簡單介紹一下,

檔案字尾是http 或 rest

定義全域性變數:@baseUrl = https://localhost:5001   ,注意連結不加引號

### 分割多個請求

POST/PUT 請求緊跟Head請求資訊,換行加上請求內容

Ctrl + Alt + R 快捷鍵 / 點Send Request發起請求  

格式化輸出

Api介面通常會是不同客戶端呼叫,這樣會有可能出現需要不同響應格式,例如常用的Json,XML。 ASPNET Core 預設情況下是忽略 Accept 標頭,JSON格式返回 一、支援XML格式 1. 新增xml格式化
services.AddControllers(options =>
    {
        options.RespectBrowserAcceptHeader = true;  //接受瀏覽器標頭
    })
    .AddXmlSerializerFormatters();                   //新增XMl格式化
}

 2. 請求是新增標頭

@orderId = {{order.response.body.*}}
GET {{baseUrl}}/Order/{{orderId}} HTTP/1.1
Accept: text/xml

 若不新增標頭,預設使用JSON格式輸出

 

二、URL格式對映

1. 新增[FormatFilter]過濾器,它會檢查路由中格式是否存在,並且使用相應的格式化程式輸出

2. 路由規則新增{format?}

[HttpGet("{id:int}.{format?}")]
public ActionResult<Models.Order> GetById(int id)
{
    var order = _orderStore.Orders.FirstOrDefault(m => m.Id == id);

    if (order == null)
    {
        return NotFound();
    }

    return order;
}
View Code

 

Url響應
GET {{baseUrl}}/Order/{{orderId}} HTTP/1.1 JSON(若配置格式化輸出)
GET {{baseUrl}}/Order/{{orderId}}.xml XML(若配置格式化輸出)
GET {{baseUrl}}/Order/{{orderId}}.json JSON(若配置格式化輸出)
  三、新增基於 Newtonsoft.Json 的 JSON 格式支援   在ASPNET Core 3.0開始,不再使用Newtonsoft.Json格式化JSON,而是使用System.Text.Json格式化,我們可以替換成Newtonsoft.Json   1. 新增包
dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson

 2. 配置Newtonsoft.Json

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddNewtonsoftJson(options =>                   //新增基於NewtonsoftJson格式化
        {
            options.SerializerSettings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
            options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
            options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
        });
}
  JSON Patch請求

PUT 和 PATCH 方法用於更新現有資源。 它們之間的區別是,PUT 會替換整個資源,而PATCH 僅指定更改。

什麼是JSON Patch?

JSON Patch官網 裡面有一句介紹的很清楚:JSON Patch is a format for describing changes to a JSON document. (一種描述Json的變化的格式)

什麼時候需要用到JSON Patch

  1. 我們返回的JSON很大,修改可能只是某些欄位
  2. 對效能要求比較大的地方
  3. 一個大的物件,好幾個地方修改,然後統一介面修改

ASPNET Core如何處理JSON Patch 請求

1. 新增包支援

dotnet add package Microsoft.AspNetCore.JsonPatch

2. 使用 HttpPatch 屬性進行批註

3. 接受 JsonPatchDocument<T>,通常帶有 [FromBody]

4. 呼叫 ApplyTo 以應用更改

假設我們現在有一個完成訂單的需求

  1. 檢查金額,數量是否有變更
  2. 更新IsComplete = true

下面附上程式碼和提交的JSON

控制器程式碼

[HttpPatch("{orderNo:length(18)}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<bool> Update([FromBody] JsonPatchDocument<Models.Order> patchDoc, [FromRoute] string orderNo)
{
    var order = _orderStore.Orders.FirstOrDefault(m => m.OrderNo == orderNo);

    if (order == null)
    {
        return NotFound();
    }

    patchDoc.ApplyTo(order, ModelState);

    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    return Ok(true);
}
View Code

失敗的JSON(金額校驗不過)

PATCH  {{baseUrl}}/Order/637109312996909246 HTTP/1.1
Accept: application/json
Content-Type: application/json

[
  {
    "op": "test",
    "path": "/quantity",
    "value": "2"
  },
  {
    "op": "test",
    "path": "/amount",
    "value": "38.28"
  },
  {
    "op": "add",
    "path": "/isComplete",
    "value": "true"
  },
]
View Code

 

會在ModelState裡面列出校驗不過的資訊

 成功的JSON

PATCH  {{baseUrl}}/Order/637109312996909246 HTTP/1.1
Accept: application/json
Content-Type: application/json

[
  {
    "op": "test",
    "path": "/quantity",
    "value": "2"
  },
  {
    "op": "test",
    "path": "/amount",
    "value": "36.8"
  },
  {
    "op": "add",
    "path": "/isComplete",
    "value": "true"
  },
]
View Code

 

我們用Get請求重新查一下,可以看到IsComplete成功被修改了

這裡只是簡單介紹JSON Patch使用,更多使用方法參考JSON Pan官網 和 微軟文件

 

Open API(Swagger)整合

Api 通常需要跟客戶端,前端進行溝通,需要編寫文件,這需要花費大量時間。

Open Api是專門解決這種問題,它為RESTful api定義了一個標準的、與語言無關的介面,利用工具生成文件,可以做到程式碼即文件(逼著開發者完善註釋)

ASPNET Core 可以使用Swashbuckle.AspNetCore或NSwag 生成Swagger 文件

下面介紹如何使用Swashbuckle.AspNetCore

一、使用Swashbuckle.AspNetCore

  1. 安裝Swashbuckle.AspNetCore包

    dotnet add package Swashbuckle.AspNetCore
  2. 新增並配置 Swagger 中介軟體

    引用名稱空間:using Microsoft.OpenApi.Models;
    services.AddSingleton<Models.OrderStore>();
                            
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "Web Api Doc", Version = "v1" });
    });
    app.UseSwagger();
                                    
    app.UseSwaggerUI(c =>
    {
      c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    });

     經過上面兩步就可以使用SwaggerUI來檢視文件和測試,瀏覽器開啟(http://{url}/swagger)

二、新增XML註釋

上面生成的Swagger文件是不包含XML註釋,下面介紹如何新增XML註釋

  1. 專案檔案(*.csproj)新增以下

    <PropertyGroup>     <GenerateDocumentationFile>true</GenerateDocumentationFile>     <NoWarn>$(NoWarn);1591</NoWarn> </PropertyGroup>

     加上上面生成文件後,未註釋的函式,屬性會發出警告,警告程式碼1591,忽略警告可以新增多個,分號分割

  2. AddSwaggerGen新增下面XML支援

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "Web Api Doc", Version = "v1" });
    
        var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
        var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
        c.IncludeXmlComments(xmlPath);
    });
  3. 方法添加註釋

    /// <summary>
    /// 更新訂單指定資訊
    /// </summary>
    /// <remarks>
    /// Sample request:
    /// 
    ///     PATCH  /Order/{orderNo} 
    ///     [
    ///         {
    ///             "op": "test",
    ///             "path": "/quantity",
    ///             "value": "2"
    ///         },
    ///         {
    ///             "op": "test",
    ///             "path": "/amount",
    ///             "value": "38.28"
    ///         },
    ///         {
    ///             "op": "add",
    ///             "path": "/isComplete",
    ///             "value": "true"
    ///         },
    ///     ]
    /// </remarks>
    /// <returns>返回是否成功</returns>
    /// <response code="200">提交成功</response>
    /// <response code="400">提交引數異常</response>    
    /// <response code="404">訂單號不存在</response>
    View Code

    ProducesResponseType 描述返回型別

    remarks 會生成請求說明

  4. 效果

Web Api 使用就介紹這些,如有錯漏,希望指出。

轉發請標明出處:https://www.cnblogs.com/WilsonPan/p/11945856.html

示例程式碼:https://github.com/WilsonPan/AspNetCoreExamples/tree/master/WebApi