WebApi 中的 REST





5.00/5 (10投票s)
在“REST - 概述”一文中,我们介绍了 RESTful 的基本规则。您能告诉我如何在 WebApi 中实现吗?好的,在本文中,我们将学习如何使用 C# 在 WebApi 中实现这些规则。我在哪里可以找到本文使用的源代码?请查看 https://github.com 上的代码。
在“REST - 概述”一文中,我们介绍了 RESTful 的基本规则。您能告诉我如何在 WebApi 中实现吗?
好的,在本文中,我们将学习如何使用 C# 在 WebApi 中实现这些规则。
我在哪里可以找到本文使用的源代码?
请查看 https://github.com/techcoaching/webapi 上的代码。
我应该从哪里开始?
首先,我们需要在您的 Visual Studio 中创建一个新项目。
打开您的 Visual Studio,创建新项目,选择“ASP.Net Web Application ...”并单击“确定”。
选择“Web API”并单击“确定”(或双击“Web API”)
“Web API”项目默认文件已创建,其中有许多未使用的文件。请暂时忽略
按“F5”运行网站,浏览器中的默认页面看起来像这样
现在,您可以开始编写第一个 REST 操作了。
好的,您可以先从 GET 用户列表开始吗?
好的,为了与“REST - 概述”匹配,我将定义一个具有相同属性的 User 类。它具有以下属性
- 名
- 姓氏
- 性别
- 年龄
我们创建一个新的“UsersController”并定义 GetUsers 方法如下
namespace REST.Controllers { [RoutePrefix("api/users")] public class UsersController : ApiController { [Route("")] [HttpGet] public IList<User> GetUsers() { return UsersController.Users; } } }
在此 UsersController 类中,有几点需要注意
- RoutePrefix,这声明了您的控制器可以访问哪个 URI。在这种情况下,它是“<根 URI>/api/users”。
- Route:声明了将调用哪个函数。这是控制器 URI 的相对路径。此时为空。因此,访问“<根 URI>/api/users”将触发此函数。
- HttpGet:声明当客户端/调用者使用 GET HTTP Verb 向“<根 URI>/api/users”发送请求时,将触发 GetUsers。
- GetUsers 将返回 IList
对象。
再次回顾,如果我们向“<根 URI>/api/users”发送 GET HTTP 请求,将调用此函数。客户端/调用者将收到用户对象列表,如下图所示
UsersController 的完整代码如下
namespace REST.Controllers { [RoutePrefix("api/users")] public class UsersController : ApiController { private static List<User> Users { get; set; } static UsersController() { UsersController.CreateTestUsers(); } private static void CreateTestUsers() { UsersController.Users = new List<User>() { new User("TU","Tran", 20,"Male"), new User("TU 1","Tran", 20,"Male") }; } [Route("")] [HttpGet] public IList<User> GetUsers() { return UsersController.Users; } } }
访问 UsersController 的 URI 是“<根 URI>/api/users”,我可以将其更改为“<根 URI>/api/user”吗?
在 WebApi 中,我们可以更改,API 仍然可以正常工作。但这会违反 REST 规则。
“User”被视为您系统中的资源,因此用户在 REST 中的 URI 是“<根 URI>/users”(在这种情况下,<根 URI>="https://:11092/api")。
规定资源必须是名词且为复数形式。所以“.../user”不正确。应该是“users”。
我可以移除控制器中的“RoutePrefix”和“Route”吗?这会违反 REST 规则吗?
是的,我们可以移除这些。
REST 关注调用方和被调用方之间的接口。这意味着调用方需要向服务器发送哪些信息,以及服务器如何响应来自调用方/客户端的请求。
"RoutePrefix" 和 "Route" 是 .Net 的属性。
移除 "RoutePrefix" 属性后,应用程序仍然可以正常运行。那么 .Net Framework 如何理解“<...>/api/users”映射到我的 UsersController 呢?
请打开“<项目文件夹>/app_start/WebApiConfig.cs”,我们有这个路由配置
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
默认情况下,.Net 将使用此路由配置来解析您的 UsersController(在本例中为 controller = Users)。
应用程序可以没有 RoutePrefix 运行,为什么我需要使用这个属性?
我们使用它来显式映射 URI(../api/users)和相应的控制器(UsersController)。
这有助于代码在未来更容易维护。特别是,在拥有许多控制器的应用程序中,这些控制器可能名称相似且位于不同的子文件夹中。
我如何更改接收数据的格式?
在请求中,请指定“accept”标头,有效值可以是:application/json, application/xml...有关 accept 标头的更多信息,请参阅 HTTP 请求字段。
当向“..../api/users”发送请求并带有“accept=application/xml”时,响应如下
我在本地遇到了这个错误,我该怎么办?
我们遇到此错误是因为 .Net Framework 不知道如何序列化(从 .Net 对象转换为字符串)。
有两种选择可以解决这个问题
[Serializable()] public class User{}
响应将是
这样,响应消息就很难被调用方理解了。
使用 DataContract 和 DataMember 来控制响应格式
将 User 类更改为如下
[DataContract(Name = "user")] public class User { [DataMember(Name = "firstName")] public string FirstName { get; set; } [DataMember(Name = "lastName")] public string LastName { get; set; } [DataMember(Name = "age")] public int Age { get; set; } [DataMember(Name = "gender")] public string Gender { get; set; } }
响应将是
我们可以使用 Serializable 或 DataContract 来控制返回给调用方的响应。我的类应该使用哪一个?
我更喜欢使用 DataContract,因为返回给调用方的字段名称更具体。
好的,获取指定用户怎么样?
与“获取用户列表”类似,我们向服务器发送以下信息
- URI:<根 URI>/users/
。例如:“https:///api/users/3”将返回 #3 用户的详细信息。 - HTTP 方法:GET
- Accept:调用方希望接收的响应数据的格式。有关 Accept 标头的更多信息,请参阅 Accept。
结果如下照片所示
在 UsersController 中,添加新的 GetUser 方法如下
namespace REST.Controllers { [RoutePrefix("api/users")] public class UsersController : ApiController { /*..... other declarations */ [HttpGet] [Route("{userId}")] public User GetUser(Guid userId) { return UsersController.Users.Find(user => user.Id == userId); } } }
在此方法中,我们有带有“{userId}”的 Route。这是用户在获取用户详细信息时将传递的参数。
GetUser 还有一个与 Route 中声明的参数同名的参数(我的意思是 userId)。.Net Framework 会在运行时为我们映射声明的参数和请求的参数。
该函数返回与该 userId 对应的用户对象。
好的,创建新用户怎么样?
要创建新用户,我们需要将以下信息发送到服务器
- URI:
/users。例如,“https:///api/users” - HTTP 方法:POST
- Content-Type(标头字段):指定我们要发送到服务器的数据格式。这让服务器知道如何反序列化为服务器上的适当对象。有关 content-type 标头字段的更多信息,请参阅 Content-Type 标头字段。
- 正文:要发送到服务器的数据的序列化字符串。例如,如果“content-type”是“application/json”,则这是一个 json 字符串;如果“content-type”是“application/xml”,则是一个 xml 字符串。
- Accept:服务器可以向调用方返回新创建的数据。在这种情况下,我们使用“accept”标头字段来指定调用方想要接收的内容格式。
在下面的照片中,我使用以下信息向服务器发送请求
- URI:https://:11092/api/users
- HTTP 方法:POST
- Content-Type:application/json
- 正文:用户对象的 Json 字符串
- Accept:application/json
在 UsersController 中,我们需要添加新的 CreateUser 函数如下
namespace REST.Controllers { [RoutePrefix("api/users")] public class UsersController : ApiController { /*... Other declarations goes here ...*/ [HttpPost] [Route("")] public User CreateUser(User user) { user.Id = Guid.NewGuid(); UsersController.Users.Add(user); return user; } } }
我们注意到
- CreateUser 被标记为 HttpPost 属性。
- CreateUser 的参数类型为 User,该对象将包含来自调用方的反序列化数据。
如果我们尝试向“https://:11092/api/users" 发送 GET,列表中将有一个新对象。
更新用户怎么样?
更新用户与创建新用户类似,但有一些区别如下
- URI:<根 URI>/users/
- HTTP 方法:PUT
- 响应:无响应
再次获取用户列表,我们可以看到更改已应用
可以将 userId 放在我们发送到服务器的 json 字符串中吗?
是的,我们可以发送。实际上,我们在服务器上没有使用这个属性。我们应该获取 URI 中传递的值。
我应该将更新后的用户返回给调用方吗?
我认为不应该。因为大多数调用方不需要这些信息,因为他们已经将这些信息发送到服务器,所以他们本地已经有了这些信息。
我没有看到您提到 HTTP 状态码。我们需要注意吗?
是的,我们应该返回正确的状态码,以便客户端/调用方可以根据 HTTP 状态码知道请求是否成功执行。
这里有一些常用的 HTTP 状态码
否 | 状态码 | 注释 |
1 | 200 | 这表明请求已成功执行。此状态码用于 GET、UPDATE、DELETE。 |
2 | 201 | 这表明新资源已被创建。这用于 POST(创建新资源)。 |
3 | 204 | 这表明请求没有附加内容。这用于 PUT(更新资源)。 |
4 | 404 | 这表明请求的资源(按 id 获取用户等)未找到。 |
5 | 500 | 这表明服务器上存在错误。 |
我可以使用 POST 更新用户吗?
理论上,我们应该使用 POST 来创建新资源(例如:用户,....),而使用 PUT 来更新现有资源(例如:用户,...)。
实际上,我们通常同时使用 POST 来创建新资源和更新现有资源。
如何在 WebAPI 中做到这一点?
我们需要修改 UpdateUser 方法如下
[HttpPost] [Route("{userId}")] public void UpdateUser(Guid userId, User user) { Models.User currentUser = UsersController.Users.Find(item => item.Id==userId); currentUser.FirstName = user.FirstName; currentUser.LastName = user.LastName; currentUser.Age = user.Age; currentUser.Gender = user.Gender; }
我们看到代码没有任何变化。除了 HttpPut 到 HttpPost。
CodeProject感谢阅读。