来源:Dino Esposito
msdn.microsoft.com/zh-cn/magazine/mt845654
我从不热衷于使用 ASP.NET Web API 作为独立框架,也几乎想不起曾用它开发过什么项目。并不是因为框架本身过时或多余。我只是发现,在大多数情况下,它带来的实际商业价值微不足道。然而,有一些明显迹象表明,Microsoft 正在暗暗努力,以复兴 ASP.NET 运行时管道。总的来说,我愿意将 ASP.NET Web API 视作是,如今形成的 ASP.NET Core(具体而言是,新 ASP.NET Core 运行时环境)的概念证明。
最初引入 Web API 主要是为了,能够在 ASP.NET 中轻松自如地生成 RESTful API。本文旨在实现相同的目标,即在 ASP.NET Core 中生成 RESTful API。
经典 ASP.NET 中 Web API 的额外费用
ASP.NET Web API 依据的是,遵循 Open Web Interface for .NET (OWIN) 规范的原则,旨在将 Web 服务器与托管 Web 应用程序分离开来。
在 .NET 空间内,引入 OWIN 标志着一个转折点,导致 IIS 和 ASP.NET 的紧密集成受到质疑。ASP.NET Core 完全放弃了这种紧密耦合。
任何使用 ASP.NET Web API 框架生成的 Web 外观都依赖完全重写的管道,即使用标准 OWIN 接口与基础主机 Web 服务器对话。不过,ASP.NET Web API 不是独立应用程序。为了便于调用方调用,它需要有主机环境来负责侦听某配置端口、捕获传入请求,以及将它们延 Web API 管道向下分派。
Web API 应用程序可以托管在 Windows 服务中,也可以托管在实现相应 OWIN 接口的自定义控制台应用程序中。它还可以由经典 ASP.NET 应用程序托管,无论它定目标到 Web 窗体还是 ASP.NET MVC。在过去几年里,在经典 ASP.NET MVC 应用程序中托管 Web API 被证明是十分常见的方案,但也是原始性能和内存占用方面效率最低的方案之一。
如图 1 所示,只要在 ASP.NET MVC 应用程序中安排 Web API 外观,三个框架最终会并列共存,共同处理每个 Web API 请求。主机 ASP.NET MVC 应用程序被封装在 HTTP 处理程序中。
此处理程序是在 system.web(原始 ASP.NET 运行时环境)基础之上构建而成。除此之外,还有基于 OWIN 的 Web API 管道(占用额外内存)。
图 1:经典 ASP.NET Web API 应用程序中涉及的框架
在这种情况下,由于受到要一直与现有 ASP.NET 管道兼容的约束,因此引入不依赖服务器的 Web 框架的理念被极大削弱。
所以,鉴于有旧版 system.web 程序集,Web API 这种简洁且适合 REST 的设计并未充分发挥潜力。
纯粹从性能角度来看,只有一些边缘用例真正合理使用了 Web API。
Web API 的有效用例
Web API 是实际应用 OWIN 原则的最备受关注的示例。Web API 库在捕获和转发传入请求的服务器应用程序后面运行。此主机可以是 Microsoft 堆栈上的经典 Web 应用程序(Web 窗体、ASP.NET MVC),也可以是控制台应用程序或 Windows 服务。
无论如何,应用程序都必须拥有薄薄一层代码,能够与 Web API 侦听器进行对话。
在 Web 环境外部托管 Web API 从根源上消除了对 system.web 程序集的任何依赖,因此巧妙地让请求管道如预期那样高效。
这是启发 ASP.NET Core 团队生成 ASP.NET Core 管道的关键要点。Web API 的理想托管条件已重写为,任何 ASP.NET Core 应用程序的理想托管条件。这就生成了一个全新管道,不仅不再依赖 system.web 程序集,还可托管在公开精简接口(IServer 接口)的嵌入式 HTTP 服务器后面。
OWIN 规范和 Katana(对 IIS/ASP.NET 环境实现它)在 ASP.NET Core 中不起任何作用。不过,这些平台的使用经验让技术理念(尤其是 Web API 边缘用例)变得更加成熟,从而照亮了生成全新 ASP.NET Core 管道之路。
有趣的是,(深受 Web API 理想托管环境的启发)重新设计整个 ASP.NET 管道后,作为独立框架的相同 Web API 不再相关。
在全新的 ASP.NET Core 管道中,只需一种应用程序模型(即基于控制器的 MVC 应用程序模型),且控制器类比在经典 ASP.NET MVC 中更丰富一点,因此融合了旧 ASP.NET 控制器和 Web API 控制器的功能。
扩展的 ASP.NET Core 控制器
在 ASP.NET Core 中,无论是打算使用 HTML,还是其他任何类型(如 JSON 或 PDF)的响应,都会用到控制器类。为了让 RESTful 接口生成变得轻松便捷,已添加一系列新操作结果类型。
所有控制器类全面支持内容协商,并向操作调用程序基础结构添加了格式设置帮助程序。若要生成公开 HTTP 终结点的 Web API,只需生成简单的控制器类,如下所示:
public class ApiController : Controller
{
// Your methods here
}
可任意命名控制器类。虽然在 URL 中添加 /api 是出于明确目的,但绝不是一项强制性要求。
可以在调用条件如下的 URL 中添加 /api:使用传统路由(ApiController 类)将 URL 映射到操作方法,或使用属性路由。我个人认为,属性路由可能更可取,因为可以通过 URL 中的同一 /api 项公开多个终结点(尽管是在任意命名的不同控制器类中定义)。
与经典 ASP.NET MVC 中的类相比,ASP.NET Core 中的控制器类功能更多,且大多数扩展都与生成 RESTful Web API 有关。首先,所有 ASP.NET Core 控制器都支持内容协商。内容协商是指,在调用方和 API 之间就返回数据的实际格式展开的静默协商。
内容协商并不是一直都会对每个请求触发。只有当传入请求包含“接受 HTTP”头且头公布调用方能够理解的 MIME 类型时,才会触发内容协商。
在这种情况下,ASP.NET Core 基础结构会遍历头内容中列出的类型,直到找到在当前应用程序配置中有格式化程序的类型为止。
如果在类型列表中找不到匹配的格式化程序,就会使用默认 JSON 格式化程序,如下所示:
[HttpGet]
public ObjectResult Get(Guid id)
{
// Do something here to retrieve the resource data
var data = FindResourceDataInSomeWay(id);
return Ok(data);
}
内容协商的另一非凡之处在于,在没有“接受 HTTP”头的情况下,它不会对序列化过程进行任何更改;只有当控制器要发送回的响应是 ObjectResult 类型时,它才会进行技术层面上的触发。
返回 ObjectResult 操作结果类型的最常见方式是,通过 Ok 方法序列化响应。请务必注意,如果通过 Json 等方法序列化控制器响应,无论发送的头是什么,都不会触发协商。可以通过 AddMvc 方法的选项,以编程方式添加对输出格式化程序的支持。例如:
services.AddMvc(options =>
{
options.OutputFormatters.Add(new PdfFormatter());
});
在此示例中,演示类 PdfFormatter 在内部包含自己能够处理的支持 MIME 类型列表。
请注意,使用 Produces 属性,可以重写内容协商,如下所示:
[Produces("application/json")]
public class ApiController : Controller
{
// Action methods here
}
可以在控制器或方法一级应用 Produces 属性,强制将类型 ObjectResult 的输出始终序列化为属性指定的格式,而不考虑“接受 HTTP”头的内容。
若要详细了解如何设置控制器方法响应的格式,建议参阅 bit.ly/2klDgdY 中的内容。
面向 REST 的操作结果类型
具有 REST 设计的 Web API 是否更好,存在极大争议。通常,可以足够安全地说,REST 方法是基于一组已知规则;从这一点来看,它更为标准。
因此,一般建议对属于企业业务的公共 API 使用此方法。如果 API 只为数量有限的客户端(大多受控于相同的 API 创建者)提供服务,使用 REST 设计路由或更松散的远程过程调用 (RPC) 方法实际并无业务上的区别。
在 ASP.NET Core 中,没有什么框架比 Web API 框架更独特、更专用。只有包含一组操作结果和帮助程序方法的控制器。
若要生成任何 Web API,只需返回 JSON、XML 或其他类似内容即可。若要生成 RESTful API,只需熟悉另一组操作结果和帮助程序方法即可。
图 2 展示了 ASP.NET Core 控制器可以返回的新操作结果类型。在 ASP.NET Core 中,操作结果类型是实现 IActionResult 接口的类型。
图 2:Web API 相关操作结果类型
请注意,图 2 中的一些类型随附搭配类型,虽然核心功能相同,但也有一些细微差别。
例如,除了 AcceptedResult 和 CreatedResult 以外,还有 xxxAtActionResult 和 xxxAtRouteResult 类型。区别在于,类型是如何表达 URI,以监视接受的操作的状态,以及刚刚创建的资源位置的。xxxAtActionResult 类型将 URI 表达为控制器和操作字符串对,而 xxxAtRouteResult 类型则使用路由名称。
相反,xxxObjectResult 变体包括 OkObjectResult 和 BadRequestObjectResult。不同之处在于,对象结果类型还支持将对象追加到响应中。
因此,OkResult 只设置 200 状态代码,而 OkObjectResult 则设置 200 状态代码,并追加选定对象。此功能常用于在处理错误请求时返回更新了检测到的错误的 ModelState 字典。
NoContentResult 和 EmptyResult 之间还存在另一个有趣区别。虽然两者都返回空响应,但 NoContentResult 设置 204 状态代码,而 EmptyResult 则设置 200 状态代码。
所有这一切都表明,生成 RESTful API 实际上是定义要对其执行操作的资源,并安排一组使用 Http 谓词执行常见控制操作的调用。使用 GET 可以读取资源,使用 PUT 可以更新资源,使用 POST 可以新建资源,使用 DELETE 可以删除现有资源。
图 3 展示了以 ASP.NET Core 类的示例资源结果类型为依据的 RESTful 接口框架。
图 3:常用 RESTful 代码框架
[HttpGet]
public ObjectResult Get(Guid id)
{
// Do something here to retrieve the resource
var res = FindResourceInSomeWay(id);
return Ok(res);
}
[HttpPut]
public AcceptedResult UpdateResource(Guid id, string content)
{
// Do something here to update the resource
var res = UpdateResourceInSomeWay(id, content);
var path = String.Format("/api/resource/", res.Id);
return Accepted(new Uri(path));
}
[HttpPost]
public CreatedResult AddNews(MyResource res)
{
// Do something here to create the resource
var resId = CreateResourceInSomeWay(res);
// Returns HTTP 201 and sets the URI to the Location header
var path = String.Format("/api/resource/", resId);
return Created(path, res);
}
[HttpDelete]
public NoContentResult DeleteResource(Guid id)
{
// Do something here to delete the resource
// ...
return NoContent();
}
若要进一步探索如何实现 ASP.NET Core 控制器来生成 Web API,请参阅 bit.ly/2j4nyUe 中的 GitHub 文件夹。
总结
Web API 是现今大多数应用程序中的常见元素。它用于向 Angular 或 MVC 前端提供数据,并向移动或桌面应用程序提供服务。
在 ASP.NET Core 上下文中,“Web API”一词终于实现了自身的真正含义,既没有含糊之处,也无需进一步解释概要。Web API 是一种编程接口,包含许多公开的 HTTP 终结点。这些终结点通常(但不一定)向调用方返回 JSON 或 XML 数据。
ASP.NET Core 中的控制器基础结构完全支持此理念,同时改进了实现,并新增了操作结果类型。在 ASP.NET 中生成 RESTful API 从未如此简单!
看完本文有收获?请转发分享给更多人
关注「DotNet」,提升.Net技能
领取专属 10元无门槛券
私享最新 技术干货