这里介绍在asp.net core中使用web api创建 restful 服务,本文使用vscode + net core3.0

  1. 创建简单rest api
  2. json patch请求
  3. 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)
            });
        }
    }
}

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);
    }
}

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"
  },
]

简单介绍一下,

文件后缀是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;
}

 

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);
}

失败的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"
  },
]

 

会在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"
  },
]

 

我们用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>

    producesresponsetype 描述返回类型

    remarks 会生成请求说明

  4. 效果

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

转发请标明出处:https://www.cnblogs.com/wilsonpan/p/11945856.html

示例代码:https://github.com/wilsonpan/aspnetcoreexamples/tree/master/webapi