ASP.NET WebAPI:MVC4 和 WebAPI 入门






4.86/5 (150投票s)
ASP.NET Web API 是一个用于构建和使用 HTTP 服务的框架,这些服务可以触及广泛的客户端,包括浏览器、手机和平板电脑。
引言
我花了一年的时间来开发 ASP.NET MVC3,今年又在 MVC4 上感觉良好。升级到 MVC4 后,我获得了一些令人兴奋的新功能,Web API 就是其中之一。我阅读了很多关于这个功能的资料,并在网上看了很多优秀的文章。但没有一篇文章能将所有概念都集中在一起。所以我试图为初学者将它们整合到一个地方。请不要认为这篇文章是我自己的发明,所有内容都来自多篇文章。请参阅“历史”部分中的链接以获取更多详细信息。
必备组件
- ASP.NET MVC 4
- 您也可以在 MVC3 中使用 Web API。只需使用 Nuget 程序包管理器对话框安装 WebAPI 组件即可。
- 或者使用程序包管理器控制台并键入:Install-Package AspNetWebApi。
什么是 ASP.NET Web API?
ASP.NET Web API 是一个用于构建和使用 HTTP 服务的框架,这些服务可以触及广泛的客户端,包括浏览器、手机和平板电脑。您可以使用 XML、JSON 或其他格式来处理您的 API。例如,JSON 对连接速度慢的移动应用很有用。您可以从 jQuery 调用 API,并更好地利用客户端的机器和浏览器。
在本文中,我们将展示使用 ASP.NET Web API 在 HTTP 服务中进行基本数据库操作 (CRUD)。许多 HTTP 服务还通过 REST 或类 REST API 来模拟 CRUD 操作。
为什么使用 ASP.NET Web API?
ASP.NET Web API 是为您的网站或服务需要支持的所有其他非人工交互而构建的。考虑一下进行 Ajax 请求的 jQuery 代码,或者支持移动客户端的服务接口。在这些情况下,请求来自代码,并期望某种结构化数据和特定的 HTTP 状态码。这两者是互补的,但又足够不同,以至于使用 ASP.NET MVC 构建 HTTP 服务需要大量工作才能正确实现。将 ASP.NET Web API 包含在 ASP.NET MVC 中(并在 ASP.NET Web Pages 等其他地方可用)意味着您可以在 ASP.NET MVC 应用程序中构建一流的 HTTP 服务,利用通用基础并使用相同的底层范例。
ASP.NET Web API 支持以下功能
- 现代 HTTP 编程模型:通过新的强类型 HTTP 对象模型,在 Web API 中直接访问和操作 HTTP 请求和响应。客户端通过新的
HttpClient
类型,以对称的方式访问相同的编程模型和 HTTP 管道。 - 完全支持路由:Web API 现在支持 Web 堆栈一直以来拥有的全部路由功能,包括路由参数和约束。此外,映射到操作完全支持约定,因此您不再需要将
[HttpPost]
等属性应用于类和方法。 - 内容协商:客户端和服务器可以协同工作,以确定 API 返回数据的正确格式。我们为 XML、JSON 和 Form URL 编码格式提供了默认支持,您可以通过添加自己的格式化程序来扩展此支持,甚至替换默认的内容协商策略。模型绑定和验证:模型绑定器提供了一种简便的方法,可以从 HTTP 请求的各个部分提取数据,并将这些消息部分转换为 .NET 对象,Web API 操作可以使用这些对象。
- 过滤器:Web API 现在支持过滤器,包括 [Authorize] 属性等常用过滤器。您可以为操作、授权和异常处理编写并插入自己的过滤器。
- 查询组合:只需返回
IQueryable<t>
,您的 Web API 即可支持通过 OData URL 约定进行查询。 - 改进的 HTTP 详细信息可测试性:Web API 操作现在可以与
HttpRequestMessage
和HttpResponseMessage
的实例一起工作,而不是在静态上下文对象中设置 HTTP 详细信息。这些对象的泛型版本也存在,允许您除了 HTTP 类型之外,还可以使用自定义类型。 - 通过 DependencyResolver 改进的控制反转 (IoC):Web API 现在使用 MVC 的依赖解析器实现的工厂模式来获取各种设施的实例。
- 基于代码的配置:Web API 配置完全通过代码完成,使您的配置文件保持整洁。
- 自托管:除了 IIS 之外,Web API 还可以托管在您自己的进程中,同时仍然可以使用路由和其他 Web API 功能的全部功能。
如何创建新的 Web API 项目
启动 Visual Studio 2010 并按照以下步骤操作
- 从“开始页/文件菜单”中选择“新建项目”,然后选择“新建项目”。
- 在项目模板列表中:选择“ASP.NET MVC 4”Web 应用程序。选择您偏好的位置,然后键入所需的项目名称,然后单击“确定”。
- 在“新建 ASP.NET MVC 4 项目”对话框中,选择“Web API”。视图引擎默认是 Razor,然后单击“确定”。
添加模型
模型是代表应用程序数据的对象。ASP.NET Web API 可以自动将您的模型序列化为 JSON、XML 或其他格式。然后序列化并将这些数据写入 HTTP 响应消息的正文。只要客户端能够读取序列化格式,它就可以反序列化对象。大多数客户端都能解析 XML 或 JSON。通过设置 HTTP 请求消息中的 Accept 标头,客户端可以指示它想要的格式。
我们将一步一步地证明上述概念。让我们从创建一个简单的模型开始。
在解决方案资源管理器中,右键单击Models文件夹,然后选择Add,然后选择Class。
将类命名为Book
。接下来,向Book
类添加以下属性。
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
添加存储库
为了满足本文的目的,让我们将列表存储在内存中,并且 HTTP 服务需要存储一个图书列表。
让我们将图书对象与服务实现分开。这是因为我们可以更改后端存储而不必重写服务类。这种设计类型称为存储库模式。为此,我们需要一个通用接口。让我们通过以下步骤了解如何为图书存储库定义通用接口。
在解决方案资源管理器中,右键单击Models文件夹。选择 Add,然后选择 New Item。
然后向Models文件夹添加另一个类,命名为“BookRepository
”,它将实现派生自IBookRepository
的IBookRespository
接口。
public interface IBookRepository
{
IEnumerable<book> GetAll();
Book Get(int id);
Book Add(Book item);
void Remove(int id);
bool Update(Book item);
}
public class BookRepository : IBookRepository
{
private BookStore db = new BookStore();
public BookRepository()
{
}
public IEnumerable<Book> GetAll()
{
return db.Books;
}
public Book Get(int id)
{
return db.Books.Find(id);
}
public Book Add(Book item)
{
db.Books.Add(item);
db.SaveChanges();
return item;
}
public void Remove(int id)
{
Book book = db.Books.Find(id);
db.Books.Remove(book);
db.SaveChanges();
}
public bool Update(Book item)
{
db.Entry(item).State = EntityState.Modified;
db.SaveChanges();
return true;
}
}
存储库将在本地内存中保留图书。我们已经提到过,为了文章的目的,我们可以接受这一点,但在实际应用中请不要这样做。因为您需要将数据存储在数据库或云存储中。存储库模式将使其以后更容易更改实现。
添加 Web API 控制器
如果您使用过 ASP.NET MVC,那么您已经熟悉控制器了。在 ASP.NET Web API 中,控制器是处理来自客户端的 HTTP 请求的类。新建项目向导在创建项目时为您创建了两个控制器。要查看它们,请在解决方案资源管理器中展开“Controllers”文件夹。
HomeController 是传统的 ASP.NET MVC 控制器。它负责为网站提供 HTML 页面,与我们的 Web API 服务没有直接关系。ValuesController 是一个 WebAPI 控制器的示例。
由于我们要从头开始,请继续删除ValuesController。我们需要说明如何删除吗?好的,在解决方案资源管理器中右键单击文件并选择 Delete。
现在添加一个新的控制器,如下所示
在解决方案资源管理器中,右键单击Controllers文件夹。选择Add,然后选择Controller。
在“Add Controller”向导中,将控制器命名为 BooksController。在“Template”下拉列表中,选择“Empty API Controller”。然后单击“Add”。
Add Controller向导将在Controllers文件夹中创建一个名为BooksController.cs的文件。如果此文件尚未打开,请双击该文件以打开它。
添加以下using
语句,并添加一个字段用于保存IBookRepository
实例。
using WebAPI.Models;
using System.Net;
public class BooksController : ApiController
{
static readonly IBookRepository _repository = new BookRepository();
}
使用 IoC 容器进行依赖注入
依赖项是另一个对象需要的对象或接口。例如,在本文中,我们定义了一个BooksController
类,它需要一个IBookRepository
实例。上面是实现的样子。p;p;
这不是最佳设计,因为对BookRepository
的new
调用被硬编码在控制器类中。稍后,我们可能希望切换到IBookRespository
的另一个实现,然后我们需要更改BooksController
的实现。最好是让BooksController
与任何具体的IBookRespoitory
实例松散解耦。
依赖注入解决了这个问题。通过依赖注入,对象不负责创建自己的依赖项。相反,创建对象的代码会注入依赖项,通常通过构造函数参数或设置器方法。
这是BooksController
的修订实现
public class BooksController : ApiController
{
static IBookRepository _repository;
public BooksController(IBookRepository repository)
{
if (repository == null)
{
throw new ArgumentNullException("repository");
}
_repository = repository;
}
}
IoC容器是负责创建依赖项的软件组件。IoC 容器提供了依赖注入的通用框架。如果您使用 IoC 容器,那么您就不需要在代码中直接连接对象。有几种开源 .NET IoC 容器可用。以下示例使用了Unity,这是Microsoft Patterns & Practices开发的IoC容器。
在解决方案资源管理器中,双击Global.asax。Visual Studio 将打开名为Global.asax.cs的文件,这是Global.asax的代码隐藏文件。此文件包含处理 ASP.NET 中应用程序级别和会话级别事件的代码。
向WebApiApplication
类添加一个名为ConfigureApi
的静态方法。
添加以下using
语句
using Microsoft.Practices.Unity;
using WebAPI.Models;
添加以下实现
void ConfigureApi(HttpConfiguration config)
{
var unity = new UnityContainer();
unity.RegisterType<BooksController>();
unity.RegisterType<IBookRepository, BookRepository>(
new HierarchicalLifetimeManager());
config.DependencyResolver = new IoCContainer(unity);
}
现在修改Application_Start
方法以调用RegisterDependencies
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ConfigureApi(GlobalConfiguration.Configuration);
Database.SetInitializer(new BookInitializer());
}
获取图书
图书服务将公开两个“读取”方法:一个返回所有图书的列表,另一个按 ID 查找图书。方法名以“Get”开头,因此根据约定,它映射到 GET 请求。此外,该方法没有参数,因此它映射到 URI 中没有“id”段的路径。第二个方法名也以“Get”开头,但该方法有一个名为id
的参数。此参数映射到 URI 路径的“id”段。ASP.NET Web API 框架会自动将 ID 转换为正确的数据类型 (int
) 以用于参数。
请注意,如果 ID 无效,GetBook
会引发HttpResponseException
类型的异常。框架会将此异常转换为404 (Not Found)错误。
public IEnumerable<book> GetAllBooks()
{
return _repository.GetAll();
}
public Book GetBook(int id)
{
Book book = _repository.Get(id);
if (book == null)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
return book;
}
创建图书
要创建新图书,客户端会发送一个HTTP POST 请求,并在请求消息的正文中包含新图书。
以下是方法的简单实现
public Book PostBook(Book book)
{
book = _repository.Add(book);
return book;
}
要处理 POST 请求,我们定义一个名称以“Post...”开头的方法。该方法采用Book
类型的参数。默认情况下,具有复杂类型的参数会从请求正文中反序列化。因此,我们期望客户端向我们发送Book
对象的序列化表示,使用XML 或 JSON 进行序列化。
此实现有效,但缺少一些要完成的内容。
- 响应代码:默认情况下,Web API 框架会将响应状态码设置为 200 (OK)。但根据 HTTP/1.1 协议,当 POST 请求导致资源创建时,服务器应回复 201 (Created)。
- Location:当服务器创建资源时,它应在响应的 Location 标头中包含新资源的 URI。
ASP.NET Web API 可以轻松操作 HTTP 响应消息。以下是改进的实现
public HttpResponseMessage PostBook(Book book)
{
book = _repository.Add(book);
var response = Request.CreateResponse<Book>(HttpStatusCode.Created, book);
string uri = Url.Route(null, new { id = book.Id });
response.Headers.Location = new Uri(Request.RequestUri, uri);
return response;
}
请注意,方法返回类型现在是HttpResponseMessage<book>
。HttpResponseMessage<t>
类是 HTTP 响应消息的强类型表示。泛型参数T
给出了将序列化为消息正文的CLR类型。这是 Beta 版本中的。您将遇到编译错误。处理此问题的新方法是通过控制器中的Request
属性:您需要将任何返回类型从HttpResponseMessage<t>
更改为HttpResponseMessage
。
在构造函数中,我们指定要序列化的Book
实例和要返回的HTTP 状态码。
var response = Request.CreateResponse<Book>(HttpStatusCode.Created, book);
更新图书
使用 PUT 更新图书很简单。只需定义一个名称以“Put...”开头的方法即可。
public void PutBook(int id, Book book)
{
book.Id = id;
if (!_repository.Update(book))
{
//throw new HttpResponseException(HttpStatusCode.NotFound);
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
此方法采用两个参数,图书 ID 和更新后的图书。ID 参数从 URI 路径获取,图书参数从请求正文反序列化。默认情况下,ASP.NET Web API 框架从路由获取简单的参数类型,从请求正文获取复杂类型。
删除图书
要删除图书,请定义一个“Delete...”方法。
public HttpResponseMessage DeleteBook(int id)
{
_repository.Remove(id);
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
根据 HTTP 规范,DELETE 方法必须是幂等的,这意味着对同一 URI 的多个 DELETE 请求应与单个 DELETE 请求具有相同的效果。因此,如果图书已被删除,该方法不应返回错误代码。
如果 DELETE 请求成功,它可以返回状态 200 (OK)并附带描述状态的实体正文,或者在删除尚待处理时返回状态 202 (Accepted),或者返回状态 204 (No Content)且没有实体正文。在此示例中,该方法返回状态 204。
使用 JavaScript、jQuery 和 jQuery 模板使用 HTTP 服务
在解决方案资源管理器中,展开Views文件夹,然后展开其中的Home文件夹。您应该会看到一个名为Index.cshtml的文件。双击此文件将其打开。
添加以下代码
<script type="text/javascript">
$(function() {
$.getJSON(
"api/books",
function(data) {
$.each(data,
function(index, value) {
$("#bookTemplate").tmpl(value).appendTo("#books");
}
);
$("#loader").hide();
$("#addBook").show();
}
);
$("#addBook").submit(function() {
$.post(
"api/books",
$("#addBook").serialize(),
function(value) {
$("#bookTemplate").tmpl(value).appendTo("#books");
$("#name").val("");
$("#price").val("");
},
"json"
);
return false;
});
$(".removeBook").live("click", function() {
$.ajax({
type: "DELETE",
url: $(this).attr("href"),
context: this,
success: function() {
$(this).closest("li").remove();
}
});
return false;
});
$("input[type=\"submit\"], .removeBook, .viewImage").button();
});
function find() {
var id = $('#bookId').val();
$.getJSON("api/books/" + id,
function(data) {
var str = data.Name + ': $' + data.Price;
$('#book').html(str);
})
.fail(
function(jqXHR, textStatus, err) {
$('#book').html('Error: ' + err);
});
}
</script>
视图中数据的呈现如下所示
上面的示例演示了 Get All Books、Add Book 和 Remove a Book from the list。find 函数帮助我们按 ID获取图书。为了绑定所有图书列表,我们在这里使用了jQuery 模板。以下部分也需要此功能
<script id="bookTemplate" type="text/html">
<li> Book Name: ${ Name }
Price:${ Price }
<a class="button small red removeBook" href="$%7B%20Self%20%7D">
Remove</a>
</li>
</script>
HTML 应如下所示
<div class="grid_16 body-container">
<div class="margin grid_6 alpha">
<label for="Name"> Name</label>
<input type="text" class="text grid_4" name="Name" id="name" />
<label for="Price">Price</label>
<input type="text" class="text grid_4" name="Price" id="price" />
<input type="submit" class="button small green" value="Add" />
<label for="bookId">Serach By ID</label>
<input type="text" class="text grid_4" size="20" id="bookId" />
<input type="button" class="button small gray"
önclick="find();" value="Search" />
<label id="book">
</label>
</div>
<div class="grid_8 omega">
<ul class="books" id="books">
</ul>
</div>
</div>
我们使用 jQuery 模板来制作正确的图书列表。请下载源代码以获取更多详细信息。
关注点
- 完全支持路由
- 模型绑定
- 过滤器。
- 内容协商
- 默认捆绑
- oData 风格的查询支持
- Razor 增强
- URL 解析 - 支持 ~/ 语法
- 条件属性渲染
- 基于 NuGet 的项目安装
还有更多...
历史
我从以下内容中获取了所有概念/一些内容
希望大家喜欢我描述的内容。感谢阅读文章。玩得开心!