By Mike Wasson | January 28, 2012
作者:Mike Wasson | 日期:2012-1-28
本文引自:http://www.asp.net/web-api/overview/creating-web-apis/creating-a-web-api-that-supports-crud-operations
注:本文是ASP.NET Web API系列教程第2章中的第1篇,因此标题采用了“2.1” — 译者注
This tutorial shows how to support CRUD operations in an HTTP service using ASP.NET Web API.
本教程展示如果在使用ASP.NET Web API的HTTP服务中支持CRUD操作。
CRUD stands for "Create, Read, Update, and Delete," which are the four basic database operations. Many HTTP services also model CRUD operations through REST or REST-like APIs.
CRUD是指“创建(C)、读取(R)、更新(U)和删除(D)”,它们是四个基本的数据库操作。许多HTTP服务也会通过REST或类REST的API模拟CRUD操作。
In this tutorial, you will build a very simple web API to manage a list of products. Each product will contain a name, price, and category (such as "toys" or "hardware"), plus a product ID.
在本教程中,你将建立一个十分简单的Web API来管理一列产品。每个产品包含一个name(名称)、price(价格)和category(分类)(如,“toys(玩具)”、“hardware(硬件)”等),还有一个产品的ID。
The products API will expose following methods.
这个产品API将暴露以下方法(见表2-1)。
Action 动作 |
HTTP method HTTP方法 |
Relative URI 相对URI |
---|---|---|
Get a list of all products 获取全部产品列表 |
GET | /api/products |
Get a product by ID 通过ID获取一个产品 |
GET | /api/products/id |
Get a product by category 通过分类获取产品 |
GET | /api/products?category=category |
Create a new product 创建一个新产品 |
POST | /api/products |
Update a product 更新一个产品 |
PUT | /api/products/id |
Delete a product 删除一个产品 |
DELETE | /api/products/id |
Notice that some of the URIs include the product ID in path. For example, to get the product whose ID is 28, the client sends a GET request for http://hostname/api/products/28.
注意,有些URI在路径中包含了产品ID。例如,要得到ID为28的产品,客户端要发送一个http://hostname/api/products/28的GET请求。
The products API defines URIs for two resource types:
这个产品API定义了两种资源类型的URI(见表2-2):
Resource 资源 |
URI |
---|---|
The list of all the products. 所有产品的列表 |
/api/products |
An individual product. 个别产品 |
/api/products/id |
The four main HTTP methods (GET, PUT, POST, and DELTETE) can be mapped to CRUD operations as follows:
四种主要的HTTP方法(GET、PUT、POST和DELETE)可以按如下方式映射到CRUD操作:
Note: The PUT method replaces the entire product entity. That is, the client is expected to send a complete representation of the updated product. If you want to support partial updates, the PATCH method is preferred. This tutorial does not implement PATCH.
注:PUT方法替换整个产品实体。即,期望客户端发送一个更新产品的完整表达式。如果想支持部分更新,最好用PATCH方法。本教程不实现PATCH。
Start by running Visual Studio 2010 and select New Project from the Start page. Or, from the File menu, select New and then Project.
启动VS 2012,并在“开始页”选择“新项目”。或从“文件”菜单选择“新建”,然后选择“项目”。
In the Templates pane, select Installed Templates and expand the Visual C# node. Under Visual C#, select Web. In the list of project templates, select ASP.NET MVC 4 Web Application. Name the project "ProductStore" and click OK.
在“模板”面板中选择“已安装模板”,并展开“Visual C#”节点。选择该节点下的“Web”。在项目模板列表中选择“ASP.NET MVC 4 Web应用程序”。将此项目命名为“ProductStore”,点击“OK”(见图2-1)。
图2-1. 创建ProductStore项目
In the New ASP.NET MVC 4 Project dialog, select Web API and click OK.
在“新的ASP.NET MVC 4项目”对话框中选择“Web API”,点击“OK”(如图2-2)。
图2-2. 选择Web API模板
A model is an object that represents the data in your application. In ASP.NET Web API, you can use strongly typed CLR objects as models, and they will automatically be serialized to XML or JSON for the client.
模型是表示你应用程序中数据的一种对象。在ASP.NET Web API中,你可以使用强类型的CRL(公共语言运行时)对象作为模型,而它们将被自动化地序列化成用于客户端的XML或JSON。
For the ProductStore API, our data consists of products, so we'll create a new class named Product.
对于这个ProductStore API,其数据由产品组成,因此,我们将创建一个名为Product的新类。
If Solution Explorer is not already visible, click the View menu and select Solution Explorer. In Solution Explorer, right-click the Models folder. From the context meny(menu), select Add, then select Class. Name the class "Product".
如果“解决方案资源管理器”此时尚不可见,点击“视图”菜单,并选择“解决方案资源管理器”。在“解决方案资源管理器”中,右击“Models”文件夹。从上下菜单中选择“添加”,然后选择“类”。将这个类命名为“Product”(如图2-3所示)。
图2-3. 创建Product类
Add the following properties to the Product class.
将以下属性添加到这个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; } } }
We need to store a collection of products. It’s a good idea to separate the collection from our service implementation. That way, we can change the backing store without rewriting the service class. This type of design is called the repository pattern. Start by defining a generic interface for the repository.
我们需要存储产品集合。将这个集合与我们的服务实现分开是一种好的思想。这样,我们可以修改后台存储而不必重写服务类。这种设计类型称为存储库模式。首先从定义存储库的泛型接口开始。
In Solution Explorer, right-click the Models folder. Select Add, then select New Item.
在“解决方案资源管理器”中,右击“Models”文件夹。选择“添加”,然后选择“新项”(如图2-4所示)。
图2-4. 创建新项
In the Templates pane, select Installed Templates and expand the C# node. Under C#, select Code. In the list of code templates, select Interface. Name the interface "IProductRepository".
在“模板”面板中,选择“已安装模板”,并展开“C#”节点,选择“代码”。在代码模板列表中选择“接口”。将此接口命名为“IProductRepository”(如图2-5所示)。
图2-5. 创建接口
Add the following implementation:
添加以下实现:
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); } }
Now add another class to the Models folder, named "ProductRepository." This class will implement the IProductRespository interface. Add the following implementation:
现在,把另一个类添加到Models文件夹,名称为“ProductRepository”。这个类将实现这个IProductRespository接口。添加以下实现:
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; } } }
The repository keeps the list in local memory. This is OK for a tutorial, but in a real application, you would store the data externally, either a database or in cloud storage. The repository pattern will make it easier to change the implementation later.
该存储库在本地内存中保持了一个产品列表。对一个教程而言这就行了,但在一个真实应用程序中,你要在外部存储这些数据,可以是一个数据库,或是云存储库。这种存储库模式会使今后对这个实现的修改很容易。
If you have worked with ASP.NET MVC, then you are already familiar with controllers. In ASP.NET Web API, a controller is a class that handles HTTP requests from the client. The New Project wizard created two controllers for you when it created the project. To see them, expand the Controllers folder in Solution Explorer.
如果你曾使用过ASP.NET MVC,对控制器是熟悉的。在ASP.NET Web API中,控制器是一种处理客户端HTTP请求的类。“新项目”向导在创建项目时,为你创建了两个控制器。要看到它们,可以在“解决方案资源管理器”中展开Controllers文件夹。
Go ahead and delete ValuesController, by right-clicking the file in Solution Explorer and selecting Delete. Now add a new controller, as follows:
继续并删除这个ValuesController,在“解决方案资源管理器”中右击这个文件,并选择“删除”。现在,添加一个新控制器,操作如下:
In Solution Explorer, right-click the the Controllers folder. Select Add and then select Controller.
在“解决方案资源管理器”中,右击Controllers文件夹。选择“添加”,然后选择“控制器”(如图2-6所示)。
图2-6. 添加控制器
In the Add Controller wizard, name the controller "ProductsController". In the Template drop-down list, select Empty API Controller. Then click Add.
在“添加控制器”向导中,将此控制器命名为“ProductsController”。在“模板”下拉列表中选择“空的API控制器”。然后点击“添加”(如图2-7所示)。
图2-7. 创建API控制器
It is not necessary to put your contollers into a folder named Controllers. The folder name is not important; it is simply a convenient way to organize your source files.
把控制器放在Controllers文件夹中不是必须的。文件夹名称并不重要,这只是组织你资源文件的一种简单的约定方式。
The Add Controller wizard will create a file named ProductsController.cs in the Controllers folder. If this file is not open already, double-click the file to open it. Add the following using statement:
“添加控制器”向导将在Controllers文件夹中创建一个名为ProductsController.cs的文件。如果这个文件尚未打开,双击此文件打开它。添加以下的using语句:
using ProductStore.Models;
Add a field that holds an IProductRepository instance.
添加一个保存IProductRepository实例的字段:
public class ProductsController : ApiController { static readonly IProductRepository repository = new ProductRepository(); }
Calling new ProductRepository() in the controller is not the best design, because it ties the controller to a particular implementation of IProductRepository. For a better approach, see Using the Web API Dependency Resolver.
在控制器中调用new ProductRepository()不是最好的设计,因为它把控制器绑定到了IProductRepository的一个特定实现上了。更好的办法参见“使用Web API依赖性解析器”。
The ProductStore API will expose several "read" operations as HTTP GET methods:
这个ProductStore API将把几个“读取”操作暴露成HTTP的GET方法(见表2-3):
Action 动作 |
HTTP method HTTP方法 |
Relative URI 相对URI |
---|---|---|
Get a list of all products 获取所有产品列表 |
GET | /api/products |
Get a product by ID 根据ID获得一个产品 |
GET | /api/products/id |
Get a product by category 根据分类获取产品 |
GET | /api/products?category=category |
Here is the method to get the list of all products:
以下是获取所有产品列表的方法:
public IEnumerable<Product> GetAllProducts() { return repository.GetAll(); }
The method name starts with "Get", so by convention it maps to GET requests. Also, because the method has no parameters, it maps to a URI that does not contain an "id" segment in the path.
该方法名以“Get”开头,故根据约定,它用来映射GET请求。另外,因为该方法无参数,它映射到路径中不含“id”片段的URI。
Here is the method to get a product by ID:
以下是根据ID获取产品的方法:
public Product GetProduct(int id) { Product item = repository.Get(id); if (item == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return item; }
This method name also starts with "Get", but the method has a parameter named id. This parameter is mapped to the "id" segment of the URI path. The ASP.NET Web API framework automatically converts the ID to the correct data type (int) for the parameter.
这个方法名也以“Get”开头,但该方法有一个名为id的参数。该参数被映射成URI路径的“id”片段。ASP.NET Web API框架自动地把这个ID转换成用于参数的正确的数据类型(int)。
The GetProduct method throws an exception of type HttpResponseException if id is not valid. This exception will be translated by the framework into a 404 (Not Found) error.
如果id无效,GetProduct会抛出一个HttpResponseException类型的异常。这个异常被框架转换成一个404(未找到)错误。
Finally, here is the method to find products by category:
最后,以下是根据分类查找产品的方法:
public IEnumerable<Product> GetProductsByCategory(string category) { return repository.GetAll().Where( p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)); }
If the request URI has a query string, Web API tries to match the query parameters to parameters on the controller method. Therefore, a URI of the form "api/products?category=category" will map to this method.
如果请求URI有一个查询字符串,Web API会试图把这些查询参数(查询字符串中的参数 — 译者注)匹配成该控制器方法的参数。因此,一个“api/products?category=category”形式的URI会映射给这个方法。
To create a new product, the client sends an HTTP POST request, with the new product in the body of the request message.
为了创建一个新产品,客户端要发送一个HTTP的POST请求,在该请求的消息体中含有这个新产品。
Here is a simple implementation of the method:
以下是此方法的一个简单实现:
// Not the final implementation! // 非最终实现! public Product PostProduct(Product item) { item = repository.Add(item); return item; }
To handle POST requests, we define a method whose name starts with "Post...". The method takes a parameter of type Product. By default, parameters with complex types are deserialized from the request body. Therefore, we expect the client to send us a serialized representation of a product object, using either XML or JSON for the serialization.
为了处理POST请求,我们要定义以“Post…”开头的方法。该方法采用一个Product类型的参数。默认地,复合类型的参数是通过请求体来解序列化的。因此,我们希望客户端给我们发送的是产品对象的一种序列化的表达式,用XML或JSON进行这种序列化。
This implementation will work, but it is missing a couple of things.
这个实现会生效,但它缺少两样东西:
ASP.NET Web API makes it easy to manipulate the HTTP response message. Here is the improved implementation:
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; }
Notice that the method return type is now HttpResponseMessage. By returning an HttpResponseMessage instead of a Product, we can control the details of the HTTP response message, including the status code and the Location header.
注意,方法的返回类型现在是HttpResponseMessage。通过返回一个HttpResponseMessage而不是Product,我们可以控制HTTP响应消息的细节,包括状态码和Location报头。
The CreateResponse method creates an HttpResponseMessage and automatically writes a serialized representation of the Product object into the body fo of the response message.
这个CreateResponse方法创建一个HttpResponseMessage,并自动地把一个序列化的Product对象表达式写入响应消息体。
This example does not validate the Product. For information about model validation, see Model Validation in ASP.NET Web API.
此例不验证Product。关于模型验证的信息,参阅“ASP.NET Web API中的模型验证”。
Updating a product with PUT is straightforward:
用PUT更新一个产品是很直观的:
public void PutProduct(int id, Product product) { product.Id = id; if (!repository.Update(product)) { throw new HttpResponseException(HttpStatusCode.NotFound); } }
The method name starts with "Put...", so Web API matches it to PUT requests. The method takes two parameters, the product ID and the updated product. The id parameter is taken from the URI path, and the product parameter is deserialized from the request body. By default, the ASP.NET Web API framework takes simple parameter types from the route and complex types from the request body.
方法名以“Put…”开头,因此,Web API把它与PUT请求进行匹配。此方法采用两个参数,产品ID和被更新产品。Id参数取自URI路径,而product参数通过请求解序列化。默认地,ASP.NET Web API框架通过路由获取简单参数,而通过请求体获取复合类型。
To delete a resourse, define a "Delete..." method.
为了删除一个资源,要定义一个“Delete…”方法:
public HttpResponseMessage DeleteProduct(int id) { repository.Remove(id); return new HttpResponseMessage(HttpStatusCode.NoContent); }
According to the HTTP specification, the DELETE method must be idempotent, meaning that several DELETE requests to the same URI must have the same effect as a single DELETE request. Therefore, the method should not return an error code if the product was already deleted.
根据HTTP规范,DELETE方法必须是幂等的,意即,对同一URI的几个DELETE请求必须与一个单一的DELETE请求具有同样的效果。因此,如果产品已被删除,该方法不应该返回一个错误代码。
If a DELETE request succeeds, it can return status 200 (OK) with an entity-body that describes the status, or status 202 (Accepted) if the deletion is still pending, or status 204 (No Content) with no entity body. In this example, the method returns status 204.
一个成功的DELETE请求,可以返回200(OK)状态,并带有一个描述该状态的条目体;也可以在删除未决的情况下返回202(Accepted)状态;或者返回无条目体的204(No Content)状态。在本例中,该方法返回204状态。
作者简介:
By Mike Wasson, Mike Wasson is a programmer-writer at Microsoft.
Mike Wasson著,Mike Wasson是微软的一位程序员著作人。