Web API 项目是 Windows 通信接口(Windows Communication Foundation,WCF)团队及其用户激情下的产物,他们想与 HTTP 深度整合。WCF 进行 Web 服务编程的迭代是一个抽象事务,主要为了隐藏像传输细节一样的内容。Web API 试图彻底颠覆这一过程,去掉 WCF 中的大部分层,而允许开发人员直接访问 HTTP 编程模型的所有方面。
ASP.NET MVC 在接收表单数据生成 HTML 方面功能非常强大;ASP.NET Web API 在接收和生成像 JSON 和 XML 等结构化数据方面功能非常强大。
由于 ASP.NET Web API 是另一个全新的框架,本文主要介绍 MVC 和 Web API 之间的异同,以帮助大家决定是否在 MVC 项目中使用 Web API。
编写 API 控制器
使用 Web API 模板添加一个项目,这个模板是唯一一个包含示例 API 控制器的模板。
Web API 和 MVC 都利用了控制器,它们都拥有将 HTTP 请求映射成控制器操作的概念,但不是使用输出模板和视图引擎渲染结果的 MVC 模式,Web API 直接把结果模型对象作为响应来渲染。
查看先前模板生成的 Web API 类:
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
// POST api/values
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
这个控制器类和 MVC 模式下的控制器类有以下区别:
- 它继承自 ApiController 类。
- 控制器中的方法返回的是原始对象,而不是视图,也不是其他辅助操作对象。
- MVC 控制器总是根据名称调度操作,Web API 控制器默认根据 HTTP 动词调度操作。
异步设计:IHttpController
查看 ApiController,如果与 MVC 的Controller 对比,会发现其中一些概念是相同的,比如控制器上下文、ModelState、Url 辅助方法和 User。但一些概念相似却存在差异,比如 Request 是来自 System.Net.Http 的 HttpRequestMessage 而不是来自 System.Web 的 HttpRequestBase,一些概念是缺失的,比如最显著的 Response 和 ActionResult 的生成方法。
ApiController 中的 ExecuteAsync 方法是接口 IHttpController 中的方法,顾名思义,它意味着所有 Web API 控制器都是异步设计。这里的管道不同于 ASP.NET,因此也不能访问 Response 对象。
API 控制器期望返回一个 HttpRequestMessage 类型的对象。HttpRequestMessage 和 HttpResponseMessage 类构成了 System.Net.Http 中 HTTP 支持的基础。这些类的设计不同于 ASP.NET 的核心运行时类,在这个栈的处理方法中,给定一个请求消息,期望返回一个响应消息。System.Net.Http 没有静态方法访问持续请求的信息,这也意味着,不必直接写入响应流,开发人员可以返回一个描述响应的对象,然后在需要时渲染。
传入的操作参数
为从请求中接收传入的值,可在操作上放置参数,就像 MVC 一样,Web API 框架会自动为这些操作方法提供参数值。不同的是,从 HTTP 主体获取的值和从其他地方(比如 URI)获取的值之间有一条强线(strong line)。
默认情况下,Web API 会假设简单类型(如字符串、日期、时间)的参数是非主体值,而复合类型从主体获取。此外,还有一个额外的限制:只有一个值可以来自主体,并且这个值必须代表整个主体。
如果传入参数不是主体的一部分,就会由模型绑定系统处理,这里的模型绑定系统与 MVC 的相似。另一方面,传入和输出的主体会被一个“格式器”处理。
操作返回值、错误和异步
Web API 控制器以操作返回值的方式把值发回客户端,然而,返回响应对象是一个相当低级的操作,所以 Web API 控制器几乎总是返回一个原始对象值或值序列。当操作返回一个原始对象时,Web API 使用称为内容协商(Content Negotiation)的功能把它自动转换成一个符合要求的结构化响应,比如 JSON 和 XML。
配置 Web API
可能极想知道控制器上的 Configuration 属性。在传统 ASP.NET 应用程序中,应用程序配置在 Global.asax 中完成,应用程序使用全局状态(包括静态的和线程局部变量)访问请求和应用程序配置。
Web API 被设计为不具有任何这样的静态全局值,而把它的配置放在 HttpConfiguration 类中。这对应用程序有两方面影响:第一,可在一个应用程序中运行多个 Web API 服务器,因为每个服务器有它自身的非全局配置;第二,可在 Web API 中更方便的运行单元测试和端到端测试。
配置类可以访问下列项:
- 路由
- 为所有请求运行的过滤器
- 参数绑定规则
- 读写主体内容使用的默认格式器
- Web API 使用的默认服务
- 用户提供的依赖解析器(针对服务和控制器上的 DI)
- HTTP 消息处理程序
- 标记是否包含像堆栈跟踪这样的错误细节
- 可以存放用户定义值的 Properties 袋
创建和访问这些配置的方式取决于我们如何托管应用程序:在 ASP.NET 内,还是在 WCF 自托管内。
Web 托管 Web API 的配置
Web API 的配置代码在文件 WebApiConfig.cs 中,开发人员可以修改这个文件以满足自己应用程序的要求,默认代码仅包含一个路由示例:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
如果查看 Global.asax 文件,会发现这个函数通过传进 GlobalConfiguration.Configuration 对象来调用,Web 托管的 Web API 仅支持单一服务器和单一配置文件:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
GlobalConfiguration 类在程序集 System.Web.Http.WebHost.dll 中,它是基础设施的其余部分,用来支持 Web 托管的 Web API。
自托管的 Web API
与 Web API 一起发布的其他托管是基于 WCF 的自托管。自托管的代码包含在程序集 System.Web.Http.SelfHost.dll 中。
没有针对自托管的内置项目模板,因为这样没有项目类型限制,当需要使用自托管时,我们就可以使用。我们可能正托管在一个控制台应用程序中,或者在一个 GUI 应用程序内部,甚至在一个 Windows 服务中。
当使用自托管时,需要正确的创建配置,启动和停止 Web API 服务器。我们需要实例化的配置类是 HttpSelfHostConfiguration,因为它通过要求一个监听的 URL 扩展了基类 HttpConfiguration,正确配置好之后,就会创建一个 HttpSelfHostServer 实例,然后告诉它开始监听。
下面是自托管启动代码的一个示例片段:
var config = new HttpSelfHostConfiguration("http://localhost:8080/");
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
完成之后,应该关闭服务器:
server.CloseAsync().Wait();
如果在控制台应用程序中是自托管,我们应该在 Main 函数中运行这些代码,对于其他应用程序类型的自托管,只需要找到合适位置以运行应用程序的启动和关闭代码即可。
Web API 的托管系统是可插拔的,这些托管配置独立于托管系统。
向 Web API 添加路由
Web API 的主要路由注册是 MapHttpRoute 扩展方法,与所有 Web API 配置任务一样,为应用程序的路由配置了 HttpConfiguration 对象。Routes 属性指向 HttpRouteCollection 类的一个实例,而不是 ASP.NET 的 RouteCollection 类实例。Web API 提供了一些直接依赖 ASP.NET 中 RouteCollection 类的 MapHttpRoute 版本,但这些路由只有在自托管时才能使用。为避免 Web API 拥有 ASP.NET 的硬依赖,开发团队采用了 ASP.NET 路由代码的副本并加以修改,把它移植到 Web API,当在自托管环境中运行,Web API 会使用它自己的私有路由代码副本;当应用程序是 Web 托管时,Web API 会使用 ASP.NET 的内置路由引擎,因为它已经连接到了 ASP.NET 请求管道。此时,系统就不只会注册 HttpRoute 对象,也会自动创建封装 Route 对象,并在 ASP.NET 路由引擎中注册这些对象。
自托管和 Web 托管之间的主要区别在于路由的运转时刻:对于 Web 托管,ASP.NET 运行路由非常早;但在自托管中,Web API 运行路由的时刻就非常晚,如果编写消息处理程序,知道可能无法访问路由信息是很重要的,因为此时路由可能尚未运行。
绑定参数
当编写操作方法签名时,来自主体的复杂类型由格式器负责生成;来自非主体的简单类型由模型绑定器负责生成。
这里提出一个 Web API 的新概念:参数绑定。Web API 使用参数绑定器来决定如何为各个参数提供值。特性可以用来影响这个决定,比如 MVC 中的特性 [ModelBinder],但当没有重写方法影响绑定决定时,默认的逻辑使用简单类型和复合类型。参数绑定系统通过查看操作参数,寻找 ParameterBindingAttribute 派生的属性。Web API 中创建有一些这样的特性,如下表,此外,还可以通过在配置文件中注册或者通过编写基于 ParameterBindingAttribute 的特性,来注册不使用模型绑定器和格式器的自定义参数绑定器。
特 性 |
描 述 |
ModelBindingAttribute | 该特性告诉参数绑定系统使用模型绑定,也就是通过使用注册模型绑定器和值提供器创建值。
这就是通过简单类型参数的默认绑定逻辑表示的内容。 |
FromUriAttribute | 该特性是一个专门的 ModelBindingAttribute,它告诉系统只能使用实现了 IUriValueProviderFactory 的值提供器,从而限制值的范围,确保它们只能从 URI 获取。
开箱即用,路由数据和 Web API 中的查询字符串值提供器实现了这个接口。 |
FromBodyAttribute | 该特性告诉参数绑定系统使用格式器,也就是通过查找 MediaTypeFormatter 的实现创建值,这里的 MediaTypeFormatter 可以解码主体,从解码主体数据中创建给定类型。
这就是通过任何复合类型的默认绑定逻辑表示的内容。 |
在 MVC 中,所有参数都是通过模型绑定创建,而 Web API 模型绑定的工作方式与 MVC(模型绑定器和提供器,值提供器和工厂)几乎一样,尽管它稍微重构了基于 MVC Futures 中的备用模型绑定系统。针对数组、集合、字典、简单类型甚至复合类型,我们会发现有对应的内置模型绑定器,尽管需要使用特性 [ModelBinder] 来运行这些绑定器。虽然接口有轻微改动,但如果知道如何在 MVC 中编写模型绑定器和值提供器,那么也能为 Web API 做同样的事。
格式器是 Web API 的新概念。格式器主要负责使用和生成主体内容。可以把它想象成 .NET 中的序列化器(serializers):负责编码和解码,可以把一个对象精确编码到主体,也可以从主体中精确解码一个对象,对象可以包含嵌套对象。
Web API 内部有三种格式器:
- 使用 Json.NET 编码和解码 JSON
- 使用 DataContractSerializer 编码和解码 XML
- 另一种解码表单 URL,编码主体数据