在 ASP.NET Web API 1 中启用 CRUD 操作
作者:Mike Wasson
本教程介绍如何使用适用于 ASP.NET 4.x 的 ASP.NET Web API 在 HTTP 服务中支持 CRUD 操作。
本教程中使用的软件版本
- Visual Studio 2012
- Web API 1 (也适用于 Web API 2)
CRUD 代表“创建、读取、更新和删除”,这是四个基本数据库操作。 许多 HTTP 服务还通过 REST 或类似于 REST 的 API 为 CRUD 操作建模。
在本教程中,你将构建一个非常简单的 Web API 来管理产品列表。 每个产品将包含名称、价格和类别 (,例如“玩具”或“硬件”) ,以及产品 ID。
产品 API 将公开以下方法。
操作 | HTTP 方法 | 相对 URI |
---|---|---|
获取所有产品的列表 | GET | /api/products |
根据 ID 获取产品 | GET | /api/products/id |
按类别获取产品 | GET | /api/products?category=category |
创建新产品 | POST | /api/products |
更新产品 | PUT | /api/products/id |
删除产品 | DELETE | /api/products/id |
请注意,某些 URI 在路径中包含产品 ID。 例如,若要获取 ID 为 28 的产品,客户端会发送针对 的 http://hostname/api/products/28
GET 请求。
资源
产品 API 定义了两种资源类型的 URI:
资源 | URI |
---|---|
所有产品的列表。 | /api/products |
单个产品。 | /api/products/id |
方法
(GET、PUT、POST 和 DELETE) 的四main HTTP 方法可以映射到 CRUD 操作,如下所示:
- GET 检索指定 URI 处的资源表示形式。 GET 应该不会对服务器产生任何副作用。
- PUT 更新指定 URI 处的资源。 如果服务器允许客户端指定新的 URI,则 PUT 还可用于在指定的 URI 处创建新资源。 在本教程中,API 不支持通过 PUT 创建。
- POST 创建新资源。 服务器为新对象分配 URI,并将此 URI 作为响应消息的一部分返回。
- DELETE 删除指定 URI 处的资源。
注意:PUT 方法替换整个产品实体。 也就是说,客户端应发送更新产品的完整表示形式。 如果要支持部分更新,首选 PATCH 方法。 本教程不实现 PATCH。
创建新的 Web API 项目
首先运行 Visual Studio,然后从“开始”页中选择“新建项目”。 或者,从“ 文件 ”菜单中选择“ 新建 ”,然后选择“ 项目”。
在 “模板 ”窗格中,选择“ 已安装的模板 ”,然后展开 “Visual C# ”节点。 在 “Visual C#”下,选择“ Web”。 在项目模板列表中,选择“ ASP.NET MVC 4 Web 应用程序”。 将项目命名为“ProductStore”,然后单击“ 确定”。
在 “新建 ASP.NET MVC 4 项目 ”对话框中,选择“ Web API ”并单击“ 确定”。
添加模型
模型是表示应用程序中的数据的对象。 在 ASP.NET Web API 中,可以使用强类型 CLR 对象作为模型,它们将自动序列化为客户端的 XML 或 JSON。
对于 ProductStore API,我们的数据由产品组成,因此我们将创建一个名为 Product
的新类。
如果解决方案资源管理器尚未出现,请单击“视图”菜单,并选择“解决方案资源管理器”。 在“解决方案资源管理器”中,右键单击“模型”文件夹。 在上下文菜单中选择“ 添加”,然后选择“ 类”。 将类命名为“Product”。
将以下属性添加到 Product
类。
namespace ProductStore.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
}
添加存储库
我们需要存储一系列产品。 最好将集合与服务实现分开。 这样,我们可以更改后备存储,而无需重写服务类。 这种类型的设计称为 存储库 模式。 首先定义存储库的泛型接口。
在“解决方案资源管理器”中,右键单击“模型”文件夹。 选择 “添加”,然后选择“ 新建项”。
在 “模板 ”窗格中,选择“ 已安装的模板 ”,然后展开“C#”节点。 在“C#”下,选择“ 代码”。 在代码模板列表中,选择“ 接口”。 将接口命名为“IProductRepository”。
添加以下实现:
namespace ProductStore.Models
{
public interface IProductRepository
{
IEnumerable<Product> GetAll();
Product Get(int id);
Product Add(Product item);
void Remove(int id);
bool Update(Product item);
}
}
现在,将另一个名为“ProductRepository”的类添加到 Models 文件夹。此类将实现 IProductRepository
接口。 添加以下实现:
namespace ProductStore.Models
{
public class ProductRepository : IProductRepository
{
private List<Product> products = new List<Product>();
private int _nextId = 1;
public ProductRepository()
{
Add(new Product { Name = "Tomato soup", Category = "Groceries", Price = 1.39M });
Add(new Product { Name = "Yo-yo", Category = "Toys", Price = 3.75M });
Add(new Product { Name = "Hammer", Category = "Hardware", Price = 16.99M });
}
public IEnumerable<Product> GetAll()
{
return products;
}
public Product Get(int id)
{
return products.Find(p => p.Id == id);
}
public Product Add(Product item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
item.Id = _nextId++;
products.Add(item);
return item;
}
public void Remove(int id)
{
products.RemoveAll(p => p.Id == id);
}
public bool Update(Product item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
int index = products.FindIndex(p => p.Id == item.Id);
if (index == -1)
{
return false;
}
products.RemoveAt(index);
products.Add(item);
return true;
}
}
}
存储库将列表保存在本地内存中。 这在教程中是可以的,但在实际应用程序中,可以将数据存储在外部,无论是数据库还是云存储中。 存储库模式将使稍后更改实现变得更容易。
添加 Web API 控制器
如果曾使用过 ASP.NET MVC,则已熟悉控制器。 在 ASP.NET Web API 中,控制器是处理来自客户端的 HTTP 请求的类。 新建项目向导在创建项目时为你创建了两个控制器。 若要查看它们,请展开 解决方案资源管理器 中的 Controllers 文件夹。
- HomeController 是传统的 ASP.NET MVC 控制器。 它负责为网站提供 HTML 页面,与我们的 Web API 不直接相关。
- ValuesController 是一个示例 WebAPI 控制器。
继续并删除 ValuesController,方法是右键单击解决方案资源管理器中的文件,然后选择“删除”。现在添加新控制器,如下所示:
在“解决方案资源管理器”中,右键单击“控制器”文件夹。 依次选择“添加”、“控制器”。
在 “添加控制器 ”向导中,将控制器命名为“ProductsController”。 在 “模板” 下拉列表中,选择“ 空 API 控制器”。 然后单击“添加” 。
注意
无需将控制器放入名为 Controllers 的文件夹中。 文件夹名称并不重要;它只是组织源文件的便捷方法。
添加控制器向导将在 Controllers 文件夹中创建名为 ProductsController.cs 的文件。 如果此文件尚未打开,请双击文件将其打开。 添加以下 using 语句:
using ProductStore.Models;
添加一个包含 IProductRepository 实例的 字段。
public class ProductsController : ApiController
{
static readonly IProductRepository repository = new ProductRepository();
}
注意
在控制器中调用 new ProductRepository()
不是最佳设计,因为它将控制器与 的特定实现 IProductRepository
联系起来。 有关更好的方法,请参阅 使用 Web API 依赖项解析程序。
获取资源
ProductStore API 会将多个“读取”操作作为 HTTP GET 方法公开。 每个操作都将对应于 类中的 ProductsController
一个方法。
操作 | HTTP 方法 | 相对 URI |
---|---|---|
获取所有产品的列表 | GET | /api/products |
根据 ID 获取产品 | GET | /api/products/id |
按类别获取产品 | GET | /api/products?category=category |
若要获取所有产品的列表,请将此方法添加到 ProductsController
类:
public class ProductsController : ApiController
{
public IEnumerable<Product> GetAllProducts()
{
return repository.GetAll();
}
// ....
}
方法名称以“Get”开头,因此按照约定会映射到 GET 请求。 此外,由于 方法没有参数,因此它映射到路径中不包含 “id” 段的 URI。
若要按 ID 获取产品,请将此方法添加到 ProductsController
类:
public Product GetProduct(int id)
{
Product item = repository.Get(id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return item;
}
此方法名称也以“Get”开头,但该方法具有名为 id 的参数。此参数映射到 URI 路径的“id”段。 ASP.NET Web API框架会自动将 ID 转换为参数的正确数据类型 (int) 。
如果 id 无效,GetProduct 方法将引发 HttpResponseException 类型的异常。 此异常将由框架转换为 404 (找不到) 错误。
最后,添加方法以按类别查找产品:
public IEnumerable<Product> GetProductsByCategory(string category)
{
return repository.GetAll().Where(
p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}
如果请求 URI 具有查询字符串,Web API 会尝试将查询参数与控制器方法上的参数匹配。 因此,格式为“api/products?category=category”的 URI 将映射到此方法。
创建资源
接下来,我们将向 类添加一个方法来 ProductsController
创建新产品。 下面是 方法的简单实现:
// Not the final implementation!
public Product PostProduct(Product item)
{
item = repository.Add(item);
return item;
}
请注意此方法的两点:
- 方法名称以“Post...”开头。 若要创建新产品,客户端会发送 HTTP POST 请求。
- 方法采用 Product 类型的参数。 在 Web API 中,具有复杂类型的参数将从请求正文反序列化。 因此,我们希望客户端以 XML 或 JSON 格式发送产品对象的序列化表示形式。
此实现将起作用,但并不十分完整。 理想情况下,我们希望 HTTP 响应包括以下内容:
- 响应代码: 默认情况下,Web API 框架将响应状态代码设置为 200 (正常) 。 但根据 HTTP/1.1 协议,当 POST 请求导致创建资源时,服务器应回复状态为 201 (创建) 。
- 位置: 当服务器创建资源时,它应在响应的 Location 标头中包含新资源的 URI。
ASP.NET Web API可以轻松操作 HTTP 响应消息。 下面是改进的实现:
public HttpResponseMessage PostProduct(Product item)
{
item = repository.Add(item);
var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);
string uri = Url.Link("DefaultApi", new { id = item.Id });
response.Headers.Location = new Uri(uri);
return response;
}
请注意,方法返回类型现在是 HttpResponseMessage。 通过返回 HttpResponseMessage 而不是 Product,我们可以控制 HTTP 响应消息的详细信息,包括状态代码和 Location 标头。
CreateResponse 方法创建 HttpResponseMessage,并自动将 Product 对象的序列化表示形式写入响应消息正文中。
注意
此示例不验证 Product
。 有关模型验证的信息,请参阅 ASP.NET Web API 中的模型验证。
更新资源
使用 PUT 更新产品非常简单:
public void PutProduct(int id, Product product)
{
product.Id = id;
if (!repository.Update(product))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
方法名称以“Put...”开头,因此 Web API 将其与 PUT 请求匹配。 方法采用两个参数:产品 ID 和更新的产品。 id 参数取自 URI 路径,product 参数从请求正文反序列化。 默认情况下,ASP.NET Web API框架采用路由中的简单参数类型和请求正文中的复杂类型。
删除资源
若要删除资源,请定义“删除...”方法。
public void DeleteProduct(int id)
{
Product item = repository.Get(id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
repository.Remove(id);
}
如果 DELETE 请求成功,可以使用描述状态的实体正文返回状态 200 (正常) ;状态 202 (如果删除仍处于挂起状态,则) 已接受;或状态 204 (无实体正文的无内容) 。 在这种情况下, DeleteProduct
方法具有void
返回类型,因此 ASP.NET Web API自动将其转换为状态代码 204 (无内容) 。