ASP.NET Web API基础(01)---初识 ASP.NET Web API

1.1 什么是Web API

WebApi是一个很广泛的概念,ASP.NET Web API是一个在.NET框架上构建Web API的框架。用于轻松构建可以由多种客户端(包括浏览器和移动设备)访问的 HTTP 服务。它是一种RestFul风格的开发接口的技术,它比WebService更省流量,比WCF更简单。

Web APIASP.NET完整框架中地位如下图,与SignalR一起同为构建Service的框架。Web API负责构建http常规服务,而SingalR主要负责的是构建实时服务,例如股票,聊天室,在线游戏等实时性要求比较高的服务。

   

1.1.1. WebApi的功能特点

1.1.2. WebApiMVC的区别

1.2 第一个Web Api 程序

VS中创建 Asp.net Web Application 项目,项目模板中选择Web API。、

新生成的WebAPI项目和典型的MVC项目一样,包含主要的ModelsViewsControllers等文件夹和Global.asax文件

Controllers里面有一个ValuesController,是自动生成的一个最简单的Web API Controller

正如我们前面所说,里面引用的是System.Web.Http命名空间。

   

默认情况下,模板自带了两个路由规则,分别对应于WebAPI和普通MVCWeb请求,默认的WebAPI路由规则如下 

routes.MapHttpRoute(

name: "DefaultApi",

routeTemplate: "api/{controller}/{id}",

defaults: new { id = RouteParameter.Optional }

);

可以看到,默认路由使用的固定的api作为Uri的先导,按照微软官方的说法,用于区分普通Web请求和WebService的请求路径: 

默认的路由规则只指向了Controller,没有指向具体的Action,因为默认情况下,对于Controller中的Action的匹配是和Action的方法名相关联的:具体来说,如果使用上面的路由规则,对应下面的Controller

public class ContactController : ApiController

{

static List<Contact> contacts = new List<Contact>()

{

new Contact{Name="张三", Id="1", Address="邯郸", Email="", Phone="43665434"},

new Contact{Name="李四", Id="102", Address="邯郸", Email="", Phone="13454565"}

};

   

public IEnumerable<Contact> Get()

{

return contacts;

}

   

public Contact Get(string id)

{

return contacts.FirstOrDefault(n => n.Id == id);

}

   

public bool Post(Contact contact)

{

contacts.Add(contact);

return true;

}

   

public bool Put(Contact contact)

{

var ct = contacts.FirstOrDefault(n => n.Id == contact.Id);

if (ct != null)

{

ct.Name = contact.Name;

ct.Email = contact.Email;

ct.Phone = contact.Phone;

ct.Address = contact.Address;

return true;

}

return false;

}

   

public bool Delete(string id)

{

return contacts.Remove(contacts.FirstOrDefault(n => n.Id == id));

}

}

   

则,会有下面的对应关系:

URL

HttpMethod

对应的Action名

/api/Contact

GET

Get()

/apiContact/1

GET

Get(string id)

/api/Contact

POST

Post()

/api/Contact/1

DELETE

Delete(string id)

/api/Contact

DELETE

Delete()

/api/Contact

PUT

Put()

   

客户端代码:

<script src="Scripts/jquery-3.3.1.min.js"></script>

<script>

//查询全部

function getAll() {

$.ajax({

url: "api/contact/",

type: "get",

success: function (result) {

$('tbody').html("");

$(result).each(function (index, ele) {

var li = "<tr><td>" + ele.Id + "</td><td>" + ele.Name + "</td><td>" + ele.Phone + "</td><td>" + ele.Address +

"</td><td><button onclick='getItem(" + ele.Id + ")'>修改</button><button onclick='deleteItem(" + ele.Id+")'>删除</button></td></tr>"

$('tbody').append($(li));

});

}

})

}

//删除

function deleteItem(id) {

$.ajax({

url: "api/contact/" + id,

type: "delete",

success: function () {

getAll();

}

})

}

//查询单个

function getItem(id) {

$.ajax({

url: "api/contact/" + id,

type: "get",

success: function (data) {

$('#Id').val(data.Id);

$('#Name').val(data.Name);

$('#Phone').val(data.Phone);

$('#Address').val(data.Address);

},

fail: function (xhr, textStatus, err) {

alert('Error: ' + err);

}

})

}

   

$(function () {

getAll();

//添加

$('#addItem').click(function () {

var id = $('#Id').val();

var name = $('#Name').val();

var phone = $('#Phone').val();

var address = $('#Address').val();

$.ajax({

url: "api/Contact/",

type: "post",

dataType: "json",

data: { "Id": id, "Name": name, "Phone": phone, "Address": address },

success: function () {

getAll();

}

})

})

//修改

$('#EditItem').click(function () {

var id = $('#Id').val();

var name = $('#Name').val();

var phone = $('#Phone').val();

var address = $('#Address').val();

$.ajax({

url: "api/Contact/",

type: "put",

dataType: "json",

data: { "Id": id, "Name": name, "Phone": phone, "Address": address },

success: function () {

getAll();

}

})

})

})

</script>

这样就实现了最基本的CRUD操作。

问题1:我想按照用户名称(UserName)进行查询,怎么办? 

答:第一步:在UserController类中加一个方法名称叫:GetUserByName,如下所示: 

public UserModel GetUserByName(string userName)

{

return contacts.Find((m) => { return m.UserName.Equals(userName); });

}

第二步:在客户端index.cshtml中调用 

如果URL是: url: "api/User/zhang",将会报错:Bad Request 

原因是他会自动调用我们的Get(string id) 这个方法,类型转换出错 

解决办法: 

改变URL为: url: "api/User/?userName=zhang", 

   

1.3 Restful 简介

网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备......),因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现"API First"的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。在此,我们将介绍RESTful API的设计细节,探讨如何设计一套合理、好用的API

1.3.1 什么是REST

REST全称是Representational State Transfer,中文意思是表述(通常译为表征)性状态转移。 它首次出现在2000Roy Fielding的博士论文中,Roy FieldingHTTP规范的主要编写者之一。 他在论文中提到:"我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。" 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST

1.3.2 Restful API 设计

RESTful架构应该遵循统一接口原则,统一接口包含了一组受限的预定义的操作,不论什么样的资源,都是通过使用相同的接口进行资源的访问。接口应该使用标准的HTTP方法如GETPUTPOST,并遵循这些方法的语义。

1、协议

API与用户的通信协议,总是使用HTTPs协议。

2、域名

应该尽量将API部署在专用域名之下。

例如:https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

https://example.org/api/

3、版本(Versioning

应该将API的版本号放入URL

例如:https://api.example.com/v1/

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

4、路径(Endpoint

路径又称"终点"endpoint),表示API的具体网址。

RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"collection),所以API中的名词也应该使用复数。

举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

https://api.example.com/v1/zoos

https://api.example.com/v1/animals

https://api.example.com/v1/employees

5HTTP动词

对于资源的具体操作类型,由HTTP动词表示。常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

  • GETSELECT):从服务器取出资源(一项或多项)。
  • POSTCREATE):在服务器新建一个资源。
  • PUTUPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCHUPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETEDELETE):从服务器删除资源。

    还有两个不常用的HTTP动词。

  • HEAD:获取资源的元数据。
  • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

    下面是一些例子。

    GET /zoos:列出所有动物园

    POST /zoos:新建一个动物园

    GET /zoos/ID:获取某个指定动物园的信息

    PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)

    PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)

    DELETE /zoos/ID:删除某个动物园

    GET /zoos/ID/animals:列出某个指定动物园的所有动物

    DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

    6、过滤信息(Filtering

    如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

    下面是一些常见的参数。

    ?limit=10:指定返回记录的数量

    ?offset=10:指定返回记录的开始位置。

    ?page=2&per_page=100:指定第几页,以及每页的记录数。

    ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。

    ?animal_type_id=1:指定筛选条件

    参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals GET /animals?zoo_id=ID 的含义是相同的。

    7、状态码(Status Codes

    服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。

    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。

    202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)

    204 NO CONTENT - [DELETE]:用户删除数据成功。

    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。

    401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。

    403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。

    404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。

    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。

    410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。

    422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。

    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

    8、错误处理(Error handling

    如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

    {

    error: "Invalid API key"

    }

    9、返回结果

    针对不同操作,服务器向用户返回的结果应该符合以下规范。

    GET /collection:返回资源对象的列表(数组)

    GET /collection/resource:返回单个资源对象

    POST /collection:返回新生成的资源对象

    PUT /collection/resource:返回完整的资源对象

    PATCH /collection/resource:返回完整的资源对象

    DELETE /collection/resource:返回一个空文档

    10Hypermedia API

    RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

    比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

    {"link": {

    "rel": "collection https://www.example.com/zoos",

    "href": "https://api.example.com/zoos",

    "title": "List of zoos",

    "type": "application/vnd.yourformat+json"

    }}

    上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

    Hypermedia API的设计被称为HATEOASGithubAPI就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

    {

    "current_user_url": "https://api.github.com/user",

    "authorizations_url": "https://api.github.com/authorizations",

    // ...

    }

    从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

    {

    "message": "Requires authentication",

    "documentation_url": "https://developer.github.com/v3"

    }

    上面代码表示,服务器给出了提示信息,以及文档的网址。

    11、其他

    1API的身份认证应该使用OAuth 2.0框架。

    2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML

问题1:什么是RestFul风格的接口?

(1).调用者不再根据方法名称区分请求方法,而是通过请求方式进行区分,将所有的操作都抽象成对资源的增删改查。

即:新增用Post请求,查询用get请求,修改用put请求,删除用delete请求。

(2).请求的路径中将不在出现方法名称

(3).用Http的返回状态码表示服务器端的处理结果,eg:找不到用404、没有权限返回201,不在需要自己约定状态码。

(4).有利于系统优化,浏览器可以自动缓存Get请求。

问题2:RestFul风格的弊端

理论性太强,很容易把初学者搞晕,比如 如何通过状态码区分是"账号错误"还是"密码错误"? 比如"登录"属于什么操作?再比如 一个控制器里有两个方法 GetM1() 和 GetM2(), 两个方法中都没有参数值,这种情况如何通过请求方式来区分呢?再比如 下面的GetStudets(string str) 和 GetTeachers(string str) 方法,参数名都是str,所以无法区分(可以把其中一个参数名改为str2,就不冲突了,或者通过别的路由规则声明一下,或者给标注个非Get请求的标记,比如[HttpPost]总之很麻烦)

1.4 生成API 帮助文档

1.4.1 生成API文档

通过观察,发现WebApi项目中Area文件夹下有一个HelpPage文件夹,如下图,该文件夹就是WebApi自带的生成Api的方式,如果该文件夹没了,可以通过Nuget安装:Microsoft.AspNet.WebApi.HelpPage ,你就会发现下图这一坨代码又回来了。

   

使用:http://localhost:2131/Help/Index , 即可访问生成的Api目录,如下图:

   

你会发现方法名的注释和参数的注释均不显示,这对使用者而言,相当不放方便了。

改进支持参数的注释

 (1). 选中项目,右键属性,填写生成xml文件的路径,如下图: bin\api.xml

(2). 找到 Areas/HelpPage/App_Start  目录下的HelpPageConfig.cs 文件,Register 方法,添加一行代码:

config.SetDocumentationProvider(new XmlDocumentationProvider(AppDomain.CurrentDomain.BaseDirectory + "bin\\api.xml"));

(3).再次访问 http://localhost:2131/Help/Index 发现无论是方法名还是参数名,均有描述了

   

   

   

上述通过改进,已经生成比较完善的Api文档了,但美中不足的是不能直接测试,当然可以整合别的控件使其支持,但比较麻烦,不如使用下面的SwashBuckle生成SwaggerUI形式的Api文档。  

   

1.4.2 借助SwaggerUI生成API文档

1. 通过Nuget安装 Swashbuckle (版本:5.6.0)程序集,会发现在 App_Start 文件夹下生成一个 SwaggerConfig.cs 配置文件,用于配置  SwaggerUI 相关展示行为的,如下图:

 2. 选中项目,右键属性,勾上xml文档文件,注意:这里默认是什么就保留什么,不要在自己改了 。如下图: bin\05-WebApiExtend.xml

   

3. SwaggerConfig.cs文件中 搜索 c.IncludeXmlComments(GetXmlCommentsPath()); ,在这句话的下面新增一句代码:

c.IncludeXmlComments(AppDomain.CurrentDomain.BaseDirectory + "bin\\05-WebApiExtend.xml");

4. 访问:http://localhost:2182/swagger/ui/index  ,即可查看生成Api文档。

   

   

posted @ 2020-10-27 18:08  高原&秃鹫  阅读(563)  评论(0编辑  收藏  举报