【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模型,然后初始化数据

【ASP.NET Core学习】Web API
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接口

【ASP.NET Core学习】Web 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)测试接口,官方介绍

【ASP.NET Core学习】Web API
@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?}

【ASP.NET Core学习】Web API
[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

控制器代码

【ASP.NET Core学习】Web API
[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(金额校验不过)

【ASP.NET Core学习】Web API
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

 【ASP.NET Core学习】Web API

会在ModelState里面列出校验不过的信息

 成功的JSON

【ASP.NET Core学习】Web API
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成功被修改了

【ASP.NET Core学习】Web API

这里只是简单介绍JSON Patch使用,更多使用方法参考JSON Pan官网微软文档

 

Open API(Swagger)集成

Api 通常需要跟客户端,前端进行沟通,需要编写文档,这需要花费大量时间。

Open Api是专门解决这种问题,它为RESTful api定义了一个标准的、与语言无关的接口,利用工具生成文档,可以做到代码即文档(逼着开发者完善注释)

ASPNET Core 可以使用Swashbuckle.AspNetCoreNSwag 生成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. 方法添加注释

    【ASP.NET Core学习】Web API
    /// <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. 效果

    【ASP.NET Core学习】Web API
    【ASP.NET Core学习】Web API

Web Api 使用就介绍这些,如有错漏,希望指出。

【ASP.NET Core学习】Web API

上一篇:API之Scanner,Random,ArrayList基础运用。重点是ArrayList


下一篇:C#基础提升系列——C# LINQ