WCF REST 初学者工具包开发人员指南
Aaron Skonnard, Pluralsight
2009 年 8 月
注意:本文基于 WCF REST 初学者工具包预览版 2。
概述
Windows Communication Foundation (WCF) 3.5 引入了用于在 .NET 中生成 RESTful 服务的“Web”编程模型。 尽管 WCF 3.5 为构建各种 RESTful 服务奠定了坚实的基础,但它仍要求开发人员为其生成的每个 RESTful 服务实现大量的锅炉板代码,并直接处理重要的 HTTP 协议功能。 WCF REST 初学者工具包提供一组 WCF 扩展和项目模板,旨在进一步简化 REST 开发。 尽管 WCF REST 初学者工具包目前被视为“预览版”技术,但其许多功能很可能进入.NET Framework的未来版本。
在本白皮书中,我们将全面探讨各种 WCF REST 初学者工具包功能,并向你展示如何开始充分利用这些功能来处理当今一些最常见的 REST 开发方案。 作为 Microsoft .NET 的一部分,Microsoft 致力于为 RESTful 服务提供丰富的平台。
如果不熟悉 REST 概念或 WCF 3.5 REST 编程模型,请务必阅读使用 WCF 3.5 设计和生成 RESTful 服务指南,然后再继续操作。
WCF REST 初学者工具包简介
自 WCF 3.5 发布以来,Microsoft 一直在努力使 .NET 平台上生成和使用 RESTful 服务的过程更加轻松。 这项工作的结果之一是,一套新的帮助程序类、扩展方法和 Visual Studio 项目模板打包到所谓的 WCF REST 初学者工具包中。 目前,可以从 CodePlex 下载 WCF REST 初学者工具包,但其许多功能可能会进入官方 .NET 框架的未来版本。 可以在 MSDN WCF REST 登陆页上找到 WCF REST 初学者工具包的最新信息。
WCF REST 初学者工具包 (预览版 2) 附带了三个新的 .NET 程序集,可在代码中利用这些程序集来简化常见的 REST 编程任务 (请参阅图 1) 。
程序集名称 | 说明 |
---|---|
Microsoft.ServiceModel.Web |
包含一组新类和扩展方法,用于简化使用 WCF 生成和托管 RESTful 服务的过程。 |
Microsoft.Http |
包含一组新类和扩展方法,可简化使用 HTTP 使用 RESTful 服务的过程。 |
Microsoft.Http.Extensions |
包含一组用于使用特定类型的 RESTful 服务和响应格式的新类和扩展方法。 |
图 1:WCF REST 初学者工具包程序集
Microsoft.ServiceModel.Web 程序集包含一个新的 WebServiceHost2 类 (派生自 WCF 3.5 中的 WebServiceHost) 专门用于托管 RESTful 服务。 此类启动多个特定于 REST 的功能,并配置基础 WCF 运行时,最终使 RESTful 服务更易于生成,更易于其他人使用。 此新程序集还附带了一些 .NET 属性和扩展方法,也可以在代码中利用这些属性和扩展方法。 这些扩展允许你利用 WebServiceHost2 提供的各种功能。
Microsoft.Http 程序集包含用于使用 RESTful 服务的新客户端 HTTP API。 此程序集中感兴趣的主要类是 HttpClient。 使用 HttpClient,可以轻松发出 HTTP GET、POST、PUT 和 DELETE 请求,并通过各种不同的特定于内容的 API 处理响应。 它还简化了发送表单数据和查询字符串值的过程,并通过一组新的类型标头类简化了使用 HTTP 标头的过程。 此新的客户端 API 提供更自然的 HTTP 体验,用于使用 Web 上发现的任何 RESTful 服务。
最后,Microsoft.Http.Extensions 程序集包含一些专注于特定方案的专用 HttpClient 派生类。 它还提供了相当多的扩展方法,这些扩展方法侧重于以各种不同格式处理 HTTP 消息的正文, (XML、JSON、Atom 源等) 。 使用服务时,你将结合使用这些扩展方法与 HttpClient。
WCF REST 初学者工具包还附带一组有用的 Visual Studio 项目模板, (请参阅面向常见 REST 方案的图 2) 。 这些项目模板提供入门所需的样板代码,利用上述程序集中的新类/扩展。 例如,有一个用于生成“单一实例”服务的模板 (公开单个资源) ,另一个模板用于生成“集合”服务 (公开) 的资源集合。 还有一个用于生成 Atom 源的模板,另一个模板用于生成功能齐全的 AtomPub 服务。 这些模板可帮助快速启动这些不同 REST 方案的服务实现。
项目模板 | 说明 |
---|---|
REST 单一实例服务 |
生成一个服务, (SampleItem) 定义示例单一实例资源,以及用于与单一实例 (GET、POST、PUT 和 DELETE) 交互的完整 HTTP 接口,同时支持 XML 和 JSON 表示形式。 |
REST 收集服务 |
与 REST 单一实例服务类似,它还支持管理 SampleItem 资源的集合。 |
Atom 源服务 |
生成一个服务,用于公开包含虚拟数据的示例 Atom 源。 |
AtomPub 服务 |
生成一个功能齐全的 AtomPub 服务,该服务能够管理资源集合和媒体条目。 |
HTTP 纯 XML 服务 |
生成具有简单 GET 和 POST 方法的服务,可以基于这些方法构建普通的 XML (POX) 服务,这些服务不完全符合 RESTful 设计原则,而仅依赖于 GET 和 POST 操作。 |
图 2:WCF REST 初学者工具包项目模板
在后续部分中,我们将更详细地探讨这些程序集和项目模板,并在此过程中了解如何处理一些常见的 REST 方案。
使用 Microsoft.ServiceModel.Web 生成和托管服务
若要开始利用 WCF 服务项目中的 WCF REST 初学者工具包,需要修改主机应用程序以使用 Microsoft.ServiceModel.Web 中的 WebServiceHost2 类。 此类为主机应用程序公开的所有服务终结点启动新的 WCF REST 初学者工具包功能。 完成此操作后,可以开始利用自动帮助页、HTTP 缓存支持、新的异常处理行为和新的请求拦截功能。 首先,我将向你展示如何连接 WebServiceHost2,然后我们将探索其中每个新功能领域。
使用 WebServiceHost2 托管 REST 服务
WebServiceHost2 类派生自 WCF 3.5 中的 WebServiceHost 类。 因此,可以像任何其他 ServiceHost 派生类一样使用它。 如果在主机应用程序中使用自承载技术,则可能有一些代码如下所示:
WebServiceHost host = new WebServiceHost(typeof(BookmarkService));
host.Open();
若要开始将 WCF REST 初学者工具包功能用于服务,只需在代码中将类名从“WebServiceHost”更改为“WebServiceHost2”即可:
WebServiceHost2 host = new WebServiceHost2(typeof(BookmarkService));
host.Open();
在 IIS 中承载服务时,还可以利用 WebServiceHost2。 如果现在在 IIS 内托管 WCF 服务,你将获得如下所示的 SVC 文件:
<%@ ServiceHost Language="C#" Debug="true" Service="BookmarkService"
Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>
WCF REST 初学者工具包附带了一个新的 WebServiceHost2Factory 类 - 它负责激活 WebServiceHost2 实例。 只需将工厂类替换为 WebServiceHost2Factory,IIS 托管服务将由 WebServiceHost2 实例自动管理:
<%@ ServiceHost Language="C#" Debug="true" Service="BookmarkService"
Factory="Microsoft.ServiceModel.Web.WebServiceHost2Factory"%>
那么,WebServiceHost2 与 WCF 3.5 附带的 WebServiceHost 类有何不同呢? 它执行两项关键操作。 首先,它将所有终结点上的 WebHttpBehavior 替换为 WebHttpBehavior2 的实例。 新的 WebHttpBehavior2 类负责提供自动帮助页功能和服务器“Web”错误处理逻辑。 其次,它向每个终结点添加新的绑定元素,以注入新的请求拦截逻辑。 因此,只需更改主机类型,WCF REST 服务就可以利用这些新功能。
自动帮助页
使用 WebServiceHost2 后,服务将自动享受新的自动帮助页功能的好处,这是 RESTful 服务向前迈出的巨大一步。 可以通过浏览到服务的基址来查看帮助页面,其中末尾追加了“help” (请参阅图 3) 。
帮助页提供了使用 [WebGet] 或 [WebInvoke] 批注的每个 WCF 服务操作的可读说明,并且对于每个操作,它描述了 URI 模板、支持的 HTTP 操作和请求/响应格式,基本上是使用者需要知道的所有内容。
对于每个请求/响应,帮助页还提供 XML 架构和相应的示例 XML 实例,使用者可以使用该实例与服务集成。 使用者可以使用架构来生成适当的客户端可序列化类型,或者只需检查示例 XML 文档即可手动确定如何编写相应的 XML 处理代码。 这两种方法都很有用。
图 3:RESTFul 服务的自动帮助页
请务必注意,帮助页作为 Atom 源返回。 大多数 Web 浏览器都提供内置的源呈现来方便人工查看,这是 Internet Explorer 在图 中执行的操作。 但是,由于它是一个源,因此使用者还可以根据需要以编程方式使用说明。 如果要在 Internet Explorer 选项中关闭“源阅读视图”,则实际上会看到呈现的源 XML – 还可以通过查看页面源来检查源 XML。
默认情况下,帮助页在基址处可用,末尾追加了“help”,但可以通过 WebServiceHost2 上的 HelpPageLink 属性自定义帮助页地址。
还可以通过 Microsoft.ServiceModel.Web 中新的 [WebHelp] 属性向每个 RESTful 操作添加人工可读的说明,如以下示例所示:
[WebHelp(Comment = "Returns the user account details for the authenticated user.")]
[WebGet(UriTemplate = BookmarkServiceUris.User)]
[OperationContract]
User GetUserAsXml(string username)
{
return HandleGetUser(username);
}
现在,当你重新运行主机应用程序并浏览回帮助页时,你将看到此注释文本显示在 GetUserAsXml 说明中 (请参阅图 4) 。
图 4:包含自定义说明的自动帮助页
此新的帮助页面会自动使 RESTful 服务更易于发现,最终使其他人更容易使用它们。 使用者可以发现服务的 URI 设计、支持的 HTTP 操作以及请求/响应格式,并且说明将始终与 WCF 代码保持同步,这与使用 ASP.NET Web 服务的方式类似。
你仍然没有获得完整的客户端代码生成体验 (la WSDL) 但当你将此帮助页面与新的 HttpClient 功能相结合时,你确实不需要它。
ExceptionHandling
实现 RESTful 服务的一个比较繁琐的方面是直接处理一些 HTTP 协议详细信息,例如返回相应的 HTTP 状态代码和说明,尤其是在 WCF 服务操作的上下文中。 以下代码演示如何为未经授权的用户检查,并在必要时返回 401“未授权”响应:
if (!IsUserAuthorized(username)) {
WebOperationContext.Current.OutgoingResponse.StatusCode =
HttpStatusCode.Unauthorized;
WebOperationContext.Current.OutgoingResponse.StatusDescription = "Unauthorized";
return null;
}
此代码并不十分困难,但它也不会返回详细的响应消息,这通常对使用者非常有用。 如果想要返回此类详细响应消息,复杂性会显著上升,因为在 WCF 操作中没有简单的方法。
为了简化此常见方案,WCF REST 初学者工具包提供了一个新的 WebProtocolException 类,使向调用方返回 HTTP 错误变得非常简单。 只需在 WCF 服务操作中引发 WebProtocolException 的实例,指定 HTTP 状态代码和错误消息, (假设你使用的是 WebServiceHost2) 基础运行时负责生成相应的 HTTP 响应消息。
以下示例演示如何引发几个指定不同 HTTP 状态代码和错误消息的不同 WebProtocolException 实例:
if (!IsUserAuthorized(username)) {
throw new WebProtocolException(HttpStatusCode.Unauthorized,
"Missing or invalid user key (supply via the Authorization header)", null);
}
if (bookmark_id <= 0)
throw new WebProtocolException(HttpStatusCode.BadRequest,
"The bookmark_id field must be greater than zero", null);
引发 WebProtocolException 后,将由 WebHttpBehavior2) 引入的自定义 WCF 错误处理程序 (进行处理。 错误处理程序将 WebProtocolException 实例转换为相应的 HTTP 响应消息,其中包含使用者权益的详细响应。
图 5 演示了第一个 WebProtocolException 在浏览器中呈现时的外观。 请注意生成的 XHTML 如何清楚地显示 HTTP 状态代码以及“详细信息”消息。 此标准 XHTML 模板内置于 WebProtocolException 类中,因此,如果你喜欢它的工作方式,则无需进一步执行任何操作,使用者将收到合理的内容。
图 5:在浏览器中呈现的 WebProtocolException
但是,如果要自定义生成的 XHTML,可以使用其他构造函数重载之一并提供要返回的精确 XHTML,如下所示:
if (!IsUserAuthorized(username)) {
throw new WebProtocolException(HttpStatusCode.Unauthorized, "Unauthorized",
new XElement("html",
new XElement("body"),
new XElement("h1", "Unauthorized"),
new XElement("p", "Missing or invalid user key " +
"(supply via the Authorization header)")), true, null);
}
图 6 显示了在浏览器中呈现时引发此 WebProtocolException 的结果:
图 6:使用自定义 XHTML 响应的 WebProtocolException
此方法可让你完全自由地使用响应 XHTML。
但是,如果你不关心在 XHTML) 中返回人类可读的错误消息 (,则可以改为返回一个自定义类型,该类型将使用 DataContractSerializer) 序列化为 XML (。 以下代码示例演示如何使用名为 CustomErrorMessage 的自定义类型完成此操作:
if (!IsUserAuthorized(username)) {
throw new WebProtocolException(HttpStatusCode.Unauthorized,
"Custom error message",
new CustomErrorMessage() {
ApplicationErrorCode = 5000,
Description = "Authentication token missing" },
null);
}
在这种情况下,使用者会收到图 7 中所示的自定义 XML 消息。
图 7:使用自定义 XML 响应的 WebProtocolException
此外,如果 WCF 操作为响应指定 WebMessageFormat.Json,则生成的 XML 将使用 DataContractJsonSerializer 进行序列化,以便将 JSON 返回到使用者。
甚至可以通过与 ASP.NET 自定义错误功能集成来更进一步。 为此,可以将一些代码添加到 Global.asax,以检查是否存在 WebProtocolException,并在使用者找到自定义错误页时将其重定向到自定义错误页:
protected void Application_EndRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Error != null)
{
WebProtocolException webEx =
HttpContext.Current.Error as WebProtocolException;
if (webEx != null && webEx.StatusCode == HttpStatusCode.BadRequest)
{
HttpContext.Current.ClearError();
HttpContext.Current.Response.Redirect("BadRequest.htm");
}
if (webEx != null && webEx.StatusCode == HttpStatusCode.Unauthorized)
{
HttpContext.Current.ClearError();
HttpContext.Current.Response.Redirect("Unauthorized.htm");
}
}
}
WCF REST 初学者工具包 SDK 中有两个示例演示了这些 WebProtocolException 功能的工作原理 - 一个名为“WebException”,另一个名为“WebException2”。 最后,这些功能使生成包含描述性响应消息的自定义 HTTP 错误消息变得更加容易,只需引发异常即可,这是 .NET 开发人员更自然的模型。
缓存支持
REST 的主要潜在优势之一是 HTTP 缓存。 但是,为了实现这一优势,必须在请求和响应消息中利用各种 HTTP 缓存标头。 可以通过 WebOperationContext 实例手动访问请求/响应标头,在 WCF 服务操作中完成此操作,但正确执行并非易事。
WCF REST 初学者工具包还提供了一个更简单的模型,用于通过 [WebCache] 属性控制缓存,可以声明性地应用于各种 GET 操作。 此属性允许为每个操作指定缓存配置文件,然后缓存行为 (CachingParameterInspector) 负责处理所有基础 HTTP 缓存详细信息。
[WebCache] 实现基于 ASP.NET 输出缓存,并提供 System.Web.UI.OutputCacheParameters 类上的大多数相同属性。 它只是将相同行为与 WCF 运行时集成,并且可以轻松地以声明方式将该行为应用于 WCF 服务操作。 以下示例演示如何将 [WebGet] 响应缓存 60 秒:
[WebCache(Duration = 60)]
WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
OperationContract]
ookmarks GetPublicBookmarksByTagAsXml(string tag)
return HandleGetPublicBookmarks(tag);
但是,由于此操作可以为每个提供的“标记”返回不同的响应,因此我们确实希望逐个标记更改缓存的响应。 可以使用 VaryByParam 属性 (来实现此目的 ASP.NET) ,如下所示:
[WebCache(Duration = 60, VaryByParam = "tag")]
[WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
[OperationContract]
Bookmarks GetPublicBookmarksByTagAsXml(string tag)
{
return HandleGetPublicBookmarks(tag);
}
除了 VaryByParam 之外,[WebCache] 属性还提供 VaryByHeader 和 VaryByCustom,这允许您指定影响输出缓存条目的不同变量。 还可以使用“位置”来控制允许将响应缓存 (例如 Any、Client、Server、ServerAndClient 等) 的位置,并且为了完全阻止缓存,可以将 NoStore 属性设置为“true”。
[WebCache] 还支持 CacheProfile 属性,该属性允许引用在 web.config 中找到的“输出缓存配置文件”。例如,以下web.config包含名为“CacheFor1Min”的输出缓存配置文件,该配置文件指定响应将缓存 60 秒,缓存的响应可以存储在任意位置,并且缓存条目将因“tag”参数而异:
<configuration>
<system.web>
<compilation debug="true"/>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<clear/>
<add name="CacheFor1Min" duration="60" enabled="true"
location="Any" varyByParam="tag"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>
</configuration>
这使你可以将缓存行为与编译的 WCF 代码分离。 可以通过 [WebCache] 属性将此输出缓存配置文件应用于 WCF 操作,如下所示:
[WebCache(CacheProfileName="CacheFor1Min")]
WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
OperationContract]
ookmarks GetPublicBookmarksByTagAsXml(string tag)
return HandleGetPublicBookmarks(tag);
最后,[WebCache] 属性甚至允许通过 SqlDependency 属性将缓存行为绑定到 SQL 依赖项。 为了利用这一点,需要为有问题的数据库添加 sqlCacheDependency <> 条目以web.config。然后使用 [WebCache] 属性的 SqlDependency 属性指定缓存项应依赖的数据库&表名称对列表。 修改任何指定表时,缓存条目将过期。
以下web.config演示了如何配置新的 <sqlCacheDependency> 条目:
<configuration>
<connectionStrings>
<add name="bmconn" connectionString=
"Data Source=.; Initial Catalog=BookmarksDB; Integrated Security=true" />
</connectionStrings>
<system.web>
<caching>
<sqlCacheDependency enabled="true" pollTime="1000" >
<databases>
<add name="bmdb" connectionStringName="bmconn" />
</databases>
</sqlCacheDependency>
</caching>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>
</configuration>
然后,可以将 [WebCache] 应用于操作,以将输出缓存行为绑定到 SQL 数据库中的特定表。 以下示例将输出缓存绑定到“Bookmarks”表:
[WebCache(SqlDependency="bmdb:Bookmarks", VaryByParam = "tag")]
WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
OperationContract]
ookmarks GetPublicBookmarksByTagAsXml(string tag)
return HandleGetPublicBookmarks(tag);
这样,将为每个唯一的“标记”) 缓存此特定操作的输出 (,直到基础 Bookmarks 表中的数据发生更改。
[WebCache] 属性使你能够更轻松地利用 HTTP 缓存,而无需直接使用 HTTP 缓存标头。 基础 WCF 行为负责在响应中注入 HTTP Cache-Control、Date、Expires 和 Vary HTTP 标头,然后客户端可以利用这些标头来缓存响应并减少将来的往返次数。
WCF REST 初学者工具包 SDK 附带两个完整的示例,更详细地说明了如何使用 [WebCache] 功能- 一个名为“Caching1”,另一个名为“Caching2”,“Caching2”示例提供了一个使用 SQL 缓存依赖项的完整示例。
除了 [WebCache] 功能外,WCF REST 初学者工具包还附带了一些扩展方法,使 ETag 更易于使用,从而简化了实现条件 GET 和条件 PUT 方案的过程。 稍后我会在本文中向你展示一个示例。
请求拦截
构建 RESTful 服务时的另一个常见需求是“请求拦截”。 例如,当需要实现将应用于身份验证或自定义调度逻辑) 等所有操作 (的服务行为时,通常最好将其实现为 WCF 行为,因为行为模型允许将处理“拦截器”的请求注入运行时。 唯一的问题是编写 WCF 行为,拦截器相当复杂,而不是对微弱的心脏。 因此,为了简化此常见方案,WCF REST 初学者工具包提供了一种更简单的“请求拦截”机制,可阻止你编写更复杂的 WCF 扩展性组件。
在 Microsoft.ServiceModel.Web 中,你将找到一个名为 RequestInterceptor 的新抽象基类,该基类定义了一个名为 ProcessRequest 的抽象方法。 下面是完整的类定义:
public abstract class RequestInterceptor
{
protected RequestInterceptor(bool isSynchronous);
public bool IsSynchronous { get; }
public virtual IAsyncResult BeginProcessRequest(RequestContext context,
AsyncCallback callback, object state);
public virtual RequestContext EndProcessRequest(IAsyncResult result);
public abstract void ProcessRequest(ref RequestContext requestContext);
}
如果要支持异步调用) ,可以从 RequestInterceptor 派生一个类,并重写 ProcessRequest (和 BeginProcessRequest/EndProcessRequest。 ProcessRequest 的实现是实现请求拦截逻辑的位置。 请注意,你获得了一个请求上下文实例,该实例使你能够访问请求消息,并提供一些方法,用于使请求管道短路并返回响应消息。
WebServiceHost2 类管理为特定服务配置的 RequestInterceptor 实例的集合。 在主机实例上调用 Open 之前,只需将 RequestInterceptor 实例添加到侦听器集合。 然后,调用 Open 时,它们将通过新的绑定元素) 插入到后台的请求处理管道中, (。
以下示例演示如何实现执行 API 密钥身份验证并拒绝未经授权的请求的 RequestInterceptor:
public class AuthenticationInterceptor : RequestInterceptor
{
public AuthenticationInterceptor() : base(false) { }
public override void ProcessRequest(ref RequestContext requestContext)
{
if (!IsValidApiKey(requestContext))
GenerateErrorResponse(requestContext,
HttpStatusCode.Unauthorized,
"Missing or invalid user key (supply via the Authorization header)");
}
public bool IsValidUserKey(Message req, string key, string uri)
{
... // ommitted for brevity
}
public void GenerateErrorResponse(RequestContext requestContext,
HttpStatusCode statusCode, string errorMessage)
{
// The error message is padded so that IE shows the response by default
string errorHtml =
"<html><HEAD><TITLE>Request Error</TITLE></HEAD><BODY>" +
"<H1>Error processing request</H1><P>{0}</P></BODY></html>";
XElement response = XElement.Load(new StringReader(
string.Format(errorHtml, errorMessage)));
Message reply = Message.CreateMessage(MessageVersion.None, null, response);
HttpResponseMessageProperty responseProp = new HttpResponseMessageProperty()
{
StatusCode = statusCode
};
responseProp.Headers[HttpResponseHeader.ContentType] = "text/html";
reply.Properties[HttpResponseMessageProperty.Name] = responseProp;
requestContext.Reply(reply);
// set the request context to null to terminate processing of this request
requestContext = null;
}
}
现在,可以通过编写自定义 ServiceHostFactory,在首次创建 WebServiceHost2 实例时注入侦听器,从而利用 IIS 托管服务的此请求侦听器。 以下示例演示了如何实现此目的:
public class SecureWebServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType,
Uri[] baseAddresses)
{
WebServiceHost2 host = new WebServiceHost2(serviceType, true, baseAddresses);
host.Interceptors.Add(new AuthenticationInterceptor());
return host;
}
}
然后,只需使用 Factory 属性) 在 .svc 文件中指定 SecureWebServiceHostFactory (,侦听器将自动启动。 事实证明,这比编写等效的 WCF 行为、侦听器和属性类要简单得多,这些类是完成此操作所必需的。
WCF REST 初学者工具包 SDK 附带了几个其他示例,这些示例演示了 RequestInterceptor 的一些可能性。 其中一个示例演示如何使用 RequestInterceptor 实现 X-HTTP-Method-Override 行为。 下面是 RequestInterceptor 实现:
public class XHttpMethodOverrideInterceptor : RequestInterceptor
{
public XHttpMethodOverrideInterceptor() : base(true) {}
public override void ProcessRequest(ref RequestContext requestContext)
{
if (requestContext == null || requestContext.RequestMessage == null)
{
return;
}
Message message = requestContext.RequestMessage;
HttpRequestMessageProperty reqProp = (HttpRequestMessageProperty)
message.Properties[HttpRequestMessageProperty.Name];
string methodOverrideVal = reqProp.Headers["X-HTTP-Method-Override"];
if (!string.IsNullOrEmpty(methodOverrideVal))
{
reqProp.Method = methodOverrideVal;
}
}
}
它会查找 X-HTTP-Method-Override 标头,如果找到标头,则会将请求消息的 HTTP 方法重置为标头中找到的值。 这为此方案提供了一个非常简单的解决方案,并且可以轻松地在所有 RESTful WCF 服务解决方案中重复使用它。
他们与 WCF REST 初学者工具包一起提供的另一个 RequestInterceptor 示例是基于内容类型的调度。 换句话说,可以根据 HTTP Accept 或 Content-Type 请求标头的值调度到不同的服务操作。 以下示例演示如何通过另一个 RequestInterceptor 实现来实现此目的:
public class ContentTypeRequestInterceptor : RequestInterceptor
{
public ContentTypeRequestInterceptor() : base(true) {}
public override void ProcessRequest(ref RequestContext requestContext)
{
if (requestContext == null) return;
Message request = requestContext.RequestMessage;
if (request == null) return;
HttpRequestMessageProperty prop = (HttpRequestMessageProperty)
request.Properties[HttpRequestMessageProperty.Name];
string format = null;
string accepts = prop.Headers[HttpRequestHeader.Accept];
if (accepts != null)
{
if (accepts.Contains("text/xml") || accepts.Contains("application/xml"))
{
format = "xml";
}
else if (accepts.Contains("application/json"))
{
format = "json";
}
}
else
{
string contentType = prop.Headers[HttpRequestHeader.ContentType];
if (contentType != null)
{
if (contentType.Contains("text/xml") ||
contentType.Contains("application/xml"))
{
format = "xml";
}
else if (contentType.Contains("application/json"))
{
format = "json";
}
}
}
if (format != null)
{
UriBuilder toBuilder = new UriBuilder(request.Headers.To);
if (string.IsNullOrEmpty(toBuilder.Query))
{
toBuilder.Query = "format=" + format;
}
else if (!toBuilder.Query.Contains("format="))
{
toBuilder.Query += "&format=" + format;
}
request.Headers.To = toBuilder.Uri;
}
}
}
这些只是使用 RequestInterceptor 机制可以完成的几个示例。 可以使用此方法完成各种不同的请求处理行为,例如日志记录、验证,甚至自定义缓存。 该解决方案可轻松跨 RESTful 服务重复使用。
其他类和扩展方法
除了我刚才介绍的关键功能外,WCF REST 初学者工具包还附带各种扩展方法,可简化常见的 REST 编程任务。 这些扩展方法分散在 Microsoft.ServiceModel.Web 中的多个类中。 我在这里重点介绍其中一些。
WebOperationContextExtensions 类包含核心 WebOperationContext 类的一组扩展方法。 其中一些旨在使 URI 和 UriTemplate 操作更容易 (GetBaseUri、GetRequestUri 和 BindTemplateToRequestUri) 。 其余部分旨在通过多个 SetHashEtag 重载和 ThrowIfEtagMissingOrStale) 简化 HTTP ETag 处理 (。 下面显示了 WebOperationContextExtensions 的类定义:
public static class WebOperationContextExtensions
{
public static Uri BindTemplateToRequestUri(this WebOperationContext context,
UriTemplate template, params string[] values);
public static Uri GetBaseUri(this IncomingWebRequestContext context);
public static NameValueCollection GetQueryParameters(
this IncomingWebRequestContext context);
public static Uri GetRequestUri(this IncomingWebRequestContext context);
public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
T entityToHash);
public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
BinaryFormatter formatter, T entityToHash);
public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
XmlObjectSerializer serializer, T entityToHash);
public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
XmlSerializer serializer, T entityToHash);
public static void ThrowIfEtagMissingOrStale(
this IncomingWebRequestContext context, string expectedEtag);
}
SerializationExtensions 类包含多个扩展方法,这些扩展方法在使用 XLinq) 时,可简化 XElement 实例 (的序列化对象。 它提供多个 ToObject 和 ToXml 重载:
public static class SerializationExtensions
{
public static TObject ToObject<TObject>(this XElement xml);
public static TObject ToObject<TObject>(this XElement xml,
XmlObjectSerializer serializer);
public static TObject ToObject<TObject>(this XElement xml,
XmlSerializer serializer);
public static XElement ToXml<TObject>(TObject obj);
public static XElement ToXml<TObject>(TObject obj,
XmlObjectSerializer serializer);
public static XElement ToXml<TObject>(TObject obj, XmlSerializer serializer);
}
最后,SyndicationExtensions 类包含多个扩展方法,这些扩展方法通过 SyndicationFeed 和 SyndicationItem 类简化 RSS/Atom 源的处理。 通过这些方法,可以更轻松地向源添加各种类型的链接,包括“自我”链接、“编辑”链接和导航链接:
public static class SyndicationExtensions
{
public static void AddEditLink(this SyndicationItem entry, Uri uri);
public static void AddEditMediaLink(this SyndicationItem entry, Uri uri,
string contentType, long contentLength);
public static void AddNextPageLink(this SyndicationFeed feed, Uri uri);
public static void AddPreviousPageLink(this SyndicationFeed feed, Uri uri);
public static void AddSelfLink(this SyndicationFeed feed, Uri uri);
}
除了这些扩展方法外,Microsoft.ServiceModel.Web.SpecializedServices 命名空间还包含一组服务协定接口和基类定义,适用于一些最常见的“专用”RESTful 服务类型 (请参阅图 8) 。 在这里,你将找到单一实例服务、收集服务和 AtomPub 服务的类型。
接口 | 基类 | 说明 |
---|---|---|
ISingletonService<TItem> |
SingletonServiceBase<TItem> |
定义“单一实例”REST 服务的泛型服务协定和基本实现,例如仅公开单个 TItem 资源的 RESTful 服务。 |
ICollectionService<TItem> |
CollectionServiceBase<TItem> |
定义“集合”REST 服务的泛型服务协定和基本实现,例如公开 TItem 资源集合的 RESTful 服务。 |
IAtomPubService |
AtomPubServiceBase |
定义 AtomPub 服务的泛型服务协定和基本实现。 |
图 8:专用 REST 服务协定接口和基类
这些类型定义了每种类型的服务的 REST 协定详细信息,保护你免受 HTTP 详细信息的影响,同时允许你专注于资源定义 (TItem) 和核心 CRUD 功能。
如果要实现其中一种标准服务类型,只需从感兴趣的基类和相应的服务协定定义派生一个类。 然后重写抽象方法,为有问题的资源定义 CRUD 功能,然后就可以托管它了。 无需担心 HTTP 详细信息,因为它们由基类型处理。
例如,假设你想要使用 ICollectionService<TItem 和 CollectionServiceBase<TItem>> 类型实现一个简单的书签服务。 为此,可以在为资源类型指定 Bookmark 的同时从这两种类型派生一个新类。 然后重写基类上定义的少数抽象方法,包括 OnAddItem、OnDeleteItem、OnGetItem、OnGetItems 和 OnUpdateItem。 方法实现定义 CRUD 功能。
图 9 显示了供你参考的完整示例实现。
public class BookmarkService : CollectionServiceBase<Bookmark>,
ICollectionService<Bookmark>
{
Dictionary<string, Bookmark> bookmarks = new Dictionary<string, Bookmark>();
protected override Bookmark OnAddItem(Bookmark initialValue, out string id)
{
id = Guid.NewGuid().ToString();
bookmarks.Add(id, initialValue);
return initialValue;
}
protected override bool OnDeleteItem(string id)
{
bookmarks.Remove(id);
return true;
}
protected override Bookmark OnGetItem(string id)
{
return bookmarks[id];
}
protected override IEnumerable<KeyValuePair<string, Bookmark>> OnGetItems()
{
return bookmarks;
}
protected override Bookmark OnUpdateItem(string id, Bookmark newValue)
{
bookmarks[id] = newValue;
return bookmarks[id];
}
}
图 9:使用 ICollectionService<TItem 和 CollectionServiceBase<TItem> 的示例实现>
图 9 中显示的示例实现现已完全准备好托管。 为了简化托管方面,Microsoft.ServiceModel.Web.SpecializedServices 命名空间还包含每个专用服务类型的专用主机类。 例如,你可以随意使用 SingletonServiceHost、CollectionServiceHost 和 AtomPubServiceHost 类。 这些专用主机类型在后台为你配置必要的 WCF 行为,因此你不必担心它。
以下代码示例演示如何在简单的控制台应用程序中托管图 9 所示的 BookmarkService 实现:
class Program
{
static void Main(string[] args)
{
CollectionServiceHost host = new CollectionServiceHost(
typeof(BookmarkService),
new Uri("https://localhost:8080/bookmarkservice"));
host.Open();
Console.WriteLine("Host is up and running...");
Console.ReadLine();
host.Close();
}
}
如果运行此控制台应用程序,然后浏览到服务的基址,则应返回由 OnGetItems) 返回 (书签列表,如图 10 所示。
图 10:在 BookmarkService 运行时浏览到 BookmarkService
如果将“/help”追加到基本 URI 的末尾,则会看到 REST 帮助页 (请参阅 ;如果将“/help”追加到基本 URI 的末尾,则会看到 REST 帮助页 (请参阅图 11) .) 。
图 11:描述书签服务的帮助页
如果浏览帮助页,你将看到此服务实现支持每个逻辑操作的 XML 和 JSON 消息格式,而无需我们执行任何操作。
可以使用这些相同的技术来实现单一实例服务, (只公开单个资源) 或功能齐全的 AtomPub 服务,这些服务在当今整个行业都变得非常流行。 这些专用服务类型可以更轻松地启动和运行 RESTful 服务,前提是它符合泛型实现施加的约束。
为了使这些专用类型更易于开发人员使用,WCF REST 初学者工具包还附带了一组 Visual Studio 项目模板,可帮助启动使用这些“专用”服务类型创建新服务实现的过程。
Visual Studio 项目模板
WCF REST 初学者工具包附带一些有用的 Visual Studio 项目模板,这些模板为几种类型的“专用”服务提供必要的锅炉板代码。 安装 WCF REST 初学者工具包后,你将在 Visual Studio“新建项目”对话框中看到一套新项目模板, (请参阅图 12) 。 我在图 13 中描述了每个项目模板提供的内容。
只需选择一个,输入剩余项目详细信息,然后按“确定”。 然后,你将获得一个主干 REST 项目,可以立即运行并开始构建。 项目模板基本上将生成一个服务实现,如我刚才在上一部分中演示的实现。
使用其中一个项目模板时,主要重点是修改资源类定义并在实现过程中传播这些更改, (可以使用 Visual Studio 重构来完成此) ,并为每个 HTTP 操作实现 CRUD 方法存根。
让我们通过一些使用 WCF REST 初学者工具包项目模板的示例来演示它如何简化生成这些类型的 RESTful 服务的过程。
图 12:WCF REST 初学者工具包项目模板
接口 | 说明 |
---|---|
Atom 源 WCF 服务 |
生成一个示例 WCF 服务,该服务演示如何以编程方式生成和返回 SyndicationFeed。 只需更改实现即可使用业务数据填充源。 |
AtomPub WCF 服务 |
为完全合规的 AtomPub 服务生成完整的主干实现,大大减少了为此方案必须编写的代码量,并允许你主要专注于如何将业务资源映射到 AtomPub 协议。 |
HTTP 纯 XML WCF 服务 |
生成不支持完整 HTTP 接口的基于 HTTP 服务的 XML。 相反,它提供简单的 GET 和 POST 操作,并可以轻松生成并非真正 RESTful 的基于 XML 的服务。 |
REST 集合 WCF 服务 |
生成一个服务,该服务 (GET、POST、PUT 和 DELETE) 围绕资源集合公开完整的 HTTP 接口,并为基础资源提供 XML 和 JSON 表示形式。 |
REST 单一实例 WCF 服务 |
生成一个服务,该服务 (GET、POST、PUT 和 DELETE) 围绕单一实例资源公开完整的 HTTP 接口,并为基础资源提供 XML 和 JSON 表示形式。 |
图 13:WCF REST 初学者工具包项目模板
REST 单一实例服务
让我们首先创建一个简单的服务,它公开一个资源,表示我在任何时候的当前行踪。 我将创建新的“REST 单一实例 WCF 服务”项目,并将其命名为 MyWhereabouts。 生成项目后,我将有一个 service.svc 文件,其中包含我的服务实现, (代码实际位于 service.svc.cs) 。 此时,我实际上可以按 F5 来测试说明初始生成的项目已完成且已准备好运行的服务。
当按 F5 加载服务并浏览到该服务时,该服务将 <返回 XML 表示形式的 SampleItem> 资源,该资源在浏览器中呈现, (见图 14) 。
图 14:浏览到生成的单一实例服务 (无更改)
如果查看 service.svc.cs 中的源代码,你会发现它们已为 SampleItem 提供了类定义 - 此类表示服务实现公开的单一实例资源。 目的是让你修改此类定义,以表示要公开的实际资源。 此类在整个文件中被引用到其他位置,因此你需要利用 Visual Studio 的重构支持在完成更改后传播更改。 初始类如下所示:
// TODO: Modify the SampleItem. Use Visual Studio refactoring while modifying so
// that the references are updated.
/// <summary>
/// Sample type for the singleton resource.
/// By default all public properties are DataContract serializable
/// </summary>
public class SampleItem
{
public string Value { get; set; }
}
如果查看服务类定义,你将看到它派生自 SingletonServiceBase<SampleItem> 和 ISingletonService<SampleItem>。 现在,需要将 SampleItem 类定义修改为更合适的定义。 由于我尝试公开“我的行踪”,因此我将类名更改为 MyWhereabouts。 执行此操作时,我将使用重构菜单并选择“将'SampleItem'重命名为'MyWhereabouts'” (请参阅图 15) 。
图 15:使用 Visual Studio 重构更改资源类名称
选择此选项负责重命名整个文件的其余部分的类引用,以便在更改后仍应生成并运行所有内容。
现在,我只需专注于修改类定义,为尝试公开的资源表示形式建模。 对于 MyWhereabouts 资源,我只需添加一些名为 Placename、Timezone、Lattitude 和 Longitude 的公共字段。 下面是我修改后的资源类的外观:
public class MyWhereabouts
{
public string Placename { get; set; }
public string Timezone { get; set; }
public string Lattitude { get; set; }
public string Longitude { get; set; }
}
进行此更改后,我还修改了 MyWhereabouts 字段初始化代码,以在编写本白皮书时将这些字段设置为我的当前位置:
public class Service : SingletonServiceBase<MyWhereabouts>,
ISingletonService<MyWhereabouts>
{
// TODO: This variable used by the sample implementation. Remove if needed
MyWhereabouts item = new MyWhereabouts()
{
Placename = "Fruit Heights",
Timezone = "GMT-07:00 Mountain Time",
Lattitude = "41.016962",
Longitude = "-111.904238"
};
完成这些更改后,现在可以再次按 F5 浏览到 service.svc 文件,并且应会看到 <浏览器中显示的 MyWhereabouts> 资源 (见图 16) 。
图 16:浏览到 MyWhereabouts 资源
此泛型服务实现支持 (GET、POST、PUT 和 DELETE) 的完整 HTTP 接口,每个操作都支持 XML 和 JSON 表示形式。 只需将“?format=json”添加到 URI 的末尾,即可检索 JSON 格式的当前资源。 现在,可以使用 Fiddler 或编写自定义客户端来测试 POST、PUT 和 DELETE) 的其他 (操作。
REST 集合服务
接下来,创建一个 REST 收集服务来实现另一个 BookmarkService。 首先,我们将通过选择“REST 集合 WCF 服务”模板创建新项目。 与上一个示例一样,我们最终将得到一个 WCF 项目,其中包含名为 SampleItem 的资源类以及派生自 CollectionServiceBase<SampleItem> 和 ICollectionService<Bookmark> 的服务类。 这些基类型实现完整的 HTTP 接口,如前所述。 现在,只需进行一些更改。
我们需要更改的第一件事是资源类的名称 - 我们将它从“SampleItem”更改为“Bookmark”,我将利用 Visual Studio 重构再次在整个项目中传播更改。 现在,我可以使用表示书签资源所需的字段填充 Bookmark 类。 我将 Bookmark 类定义更改为以下内容:
public class Bookmark
{
public Uri Url { get; set; }
public string User { get; set; }
public string Title { get; set; }
public string Tags { get; set; }
public bool Public { get; set; }
public DateTime LastModified { get; set; }
}
有了这些更改,我的新书签收集服务就可以测试了。 只需在 Visual Studio 中按 F5,它将加载服务并浏览到 service.svc 文件。 浏览器启动时,你将看到一个空 <的 ItemInfoList> 元素,因为 Bookmark 集合当前为空。
此时,可以使用 Fiddler 之类的方法通过 POST) 将一些书签添加到集合 (,也可以在服务构造函数中预填充 Bookmark 项集合。 填充 Bookmark 集合后,再次浏览到服务时,将返回一些 <Bookmark> 元素, (请参阅图 17) 。 请注意,生成的列表包含指向单个书签资源的链接。
可以按照其中一个 ItemInfo> 元素中找到的 <EditLink> 元素浏览到单个<书签。 例如,可以通过浏览到“”https://localhost:26826/Service.svc/7b5b4a15-3b05-4f94-a7b8-1f324b5cfc7d获取集合中的第一个书签 (请参阅图 18) 。
图 17:浏览到书签收集服务
图 18:浏览到集合中的单个书签
此时,还可以使用 POST、PUT 和 DELETE 操作,而无需任何其他编码 - 生成的项目模板已包含每个项目的默认实现。 可以将新的 <Bookmark> 元素 POST 到服务的根地址,它将生成一个新的 Bookmark 资源并为其分配新的 ID,并返回具有相应 Location 标头的 201 Created 响应。 还可以将书签元素 PUT <Bookmark> 元素添加到单个书签 URI 中以执行更新。 可以将 DELETE 请求发送到单个书签资源,以将其从集合中删除。
如你所看到的,对于这种特定类型的 RESTful 服务 (面向集合的服务) ,WCF REST Starter Kit 使我们的实现能够启动并运行,而我们的代码很少。
Atom 源服务
如果需要生成简单的 Atom 源服务,请从 WCF REST 初学者工具包创建一个类型为“Atom Feed WCF 服务”的新项目。 它将生成一个 WCF 服务,其中包含示例源实现,如图 19 所示。 此服务将按原样运行,并生成可在任何标准 Atom 读取器中呈现的示例 Atom 源 (请参阅图 20,了解它在 IE) 中的呈现方式。
// TODO: Please set IncludeExceptionDetailInFaults to false in production
// environments
[ServiceBehavior(IncludeExceptionDetailInFaults = true),
AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed), ServiceContract]
public partial class FeedService
{
// TODO: Modify the URI template and method parameters according to your
// application. An example URL is http://<url-for-svc-file>?numItems=1
[WebHelp(Comment = "Sample description for GetFeed.")]
[WebGet(UriTemplate = "?numItems={i}")]
[OperationContract]
public Atom10FeedFormatter GetFeed(int i)
{
SyndicationFeed feed;
// TODO: Change the sample content feed creation logic here
if (i < 0) throw new WebProtocolException(HttpStatusCode.BadRequest,
"numItems cannot be negative", null);
if (i == 0) i = 1;
// Create the list of syndication items. These correspond to Atom entries
List<SyndicationItem> items = new List<SyndicationItem>();
for (int j = 1; j <= i; ++j)
{
items.Add(new SyndicationItem()
{
// Every entry must have a stable unique URI id
Id = String.Format(CultureInfo.InvariantCulture,
"http://tempuri.org/Id{0}", j),
Title = new TextSyndicationContent(
String.Format("Sample item '{0}'", j)),
// Every entry should include the last time it was updated
LastUpdatedTime = new DateTime(
2008, 7, 1, 0, 0, 0, DateTimeKind.Utc),
// The Atom spec requires an author for every entry. If the entry has
// no author, use the empty string
Authors =
{
new SyndicationPerson()
{
Name = "Sample Author"
}
},
// The content of an Atom entry can be text, xml, a link or arbitrary
// content. In this sample text content is used.
Content = new TextSyndicationContent("Sample content"),
});
}
// create the feed containing the syndication items.
feed = new SyndicationFeed()
{
// The feed must have a unique stable URI id
Id = "http://tempuri.org/FeedId",
Title = new TextSyndicationContent("Sample feed"),
Items = items
};
feed.AddSelfLink(
WebOperationContext.Current.IncomingRequest.GetRequestUri());
#region Sets response content-type for Atom feeds
WebOperationContext.Current.OutgoingResponse.ContentType = ContentTypes.Atom;
#endregion
return feed.GetAtom10Formatter();
}
}
图 20:在 Internet Explorer 中呈现的示例源
此模板仅提供用于生成典型 Atom 源服务的示例代码。 需要修改代码以将业务实体映射到 SyndicationFeed 实例,将每个单独的实体映射到新的 SyndicationItem 实例,并利用它提供的字段。
AtomPub 服务
如果要实现符合 Atom 发布协议的服务,则应使用 WCF REST 初学者工具包附带的“Atom 发布协议 WCF 服务”项目模板。 此模板生成一个完整的 AtomPub 服务,该服务公开单个示例集合。 生成的服务类派生自前面所述的 AtomPubServiceBase 和 IAtomPubService。
可以通过浏览到 service.svc 文件立即测试服务,该服务将返回一个 AtomPub 服务文档,描述它支持的集合 (请参阅图 21) 。 如你所看到的,此服务公开了一个名为“Sample Collection”的集合,你可以通过将“collection1”添加到服务的根 URL 的末尾来访问该集合。 访问集合时,服务返回一个 Atom 源,表示示例集合 (见图 22) 。 该服务还支持通过标准 AtomPub HTTP 接口添加、更新和删除 Atom 条目。
使用 WCF REST 初学者工具包生成 AtomPub 服务时,你的工作是专注于要公开的逻辑集合。 你需要在业务实体集合和服务公开的 AtomPub 集合之间定义映射,这实质上归结为定义自定义业务实体类与 WCF SyndicationFeed/Item 类之间的映射。
图 21:浏览到 AtomPub 服务
图 22:浏览到 AtomPub 服务公开的示例集合
HTTP 纯 XML 服务
如果确实不需要或不想执行完全 RESTful 服务, (遵守所有 REST 约束并支持完整的 HTTP 接口) 但宁愿使用简单的 XML-over-HTTP 服务来解决,则不希望使用我们刚刚介绍的项目模板。 相反,你需要检查“HTTP 纯 XML WCF 服务”项目模板,它不会尝试提供 RESTful 实现。 相反,它提供了一些示例 XML over-HTTP 操作来帮助你入门。
图 23 显示了使用此项目模板时将获取的示例实现。 请注意,它如何提供一个采用某些查询字符串参数作为输入的 [WebGet] 操作,以及它提供另一个接受并返回 XML 实体正文的 [WebInvoke] 操作。 这些操作只是作为示例提供,演示了如何开始使用普通的旧 XML 服务 (POX) 。
如果尝试生成返回数据的 POX 服务,可以保留 GetData 方法并调整请求/响应数据以满足需求。 如果需要支持 POST 请求,可以保留 DoWork 方法并相应地进行调整。 你最终有可能重写这些方法的全部内容,因此此特定项目模板不会提供其他一些方法的价值。
[ServiceBehavior(IncludeExceptionDetailInFaults = true),
AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed), ServiceContract]
public partial class Service
{
[WebHelp(Comment = "Sample description for GetData")]
[WebGet(UriTemplate = "GetData?param1={i}¶m2={s}")]
[OperationContract]
public SampleResponseBody GetData(int i, string s)
{
// TODO: Change the sample implementation here
if (i < 0) throw new WebProtocolException(HttpStatusCode.BadRequest,
"param1 cannot be negative", null);
return new SampleResponseBody()
{
Value = String.Format("Sample GetData response: '{0}', '{1}'", i, s)
};
}
[WebHelp(Comment = "Sample description for DoWork")]
[WebInvoke(UriTemplate = "DoWork")]
[OperationContract]
public SampleResponseBody DoWork(SampleRequestBody request)
{
//TODO: Change the sample implementation here
return new SampleResponseBody()
{
Value = String.Format("Sample DoWork response: '{0}'", request.Data)
};
}
}
图 23:HTTP 纯 XML WCF 服务实现示例
通过 HttpClient 使用 RESTful 服务
使用 RESTful 服务的一个更具挑战性的方面是编写客户端代码来使用它们。 由于 RESTful 服务不提供类似于 WSDL 的元数据,因此客户端开发人员不会享受代码生成和强类型代理类的奢侈,这些类使大多数 SOAP 服务非常易于以编程方式集成。 这种现实通常会导致开发人员认为与 RESTful 服务集成比典型的 SOAP 服务更麻烦。 在我看来,这在很大程度上是一个视角问题。 关键是选择正确的客户端 HTTP API 进行编程。
由于 REST 服务只是基于 HTTP 的服务,因此可以按字面使用任何 HTTP API 来使用它们。 这在客户端上提供了极大的灵活性。 Microsoft .NET 提供 System.Net 类,例如 WebRequest 和 WebResponse,用于对 HTTP 客户端代码进行编程。 这些类将完成工作,但它们确实使客户端体验感觉比应该复杂。 习惯于使用基于 SOAP 的代理的开发人员通常觉得它不太吸引人或很自然。
为了简化使用 RESTful 服务的客户端编程体验,WCF REST 初学者工具包 (预览版 2) 附带了一个名为 HttpClient 的新 HTTP API,它为统一 HTTP 接口编程提供了更自然的模型,以及许多扩展方法,使处理 HTTP 消息中发现的各种不同内容类型更加简单。
使用 HttpClient 入门
HttpClient 类提供用于发送 HTTP 请求和处理 HTTP 响应的简单 API。 该功能在两个主要类定义中定义 - 一个称为 HttpClient,另一个称为 HttpMethodExtensions (请参阅图 24) 。 前者定义 类的基本功能,而后者提供一组面向不同逻辑 HTTP 方法的分层扩展方法。
如果检查 HttpClient 类,你将看到它提供了一种用于指定目标基址的方法、一种操作 HTTP 请求标头的方法,以及用于发送请求的大量重载。 通过 HttpContent 实例提供请求消息内容,并通过返回的 HttpResponseMessage 对象处理响应。 类还提供异步 Send 方法,用于在等待响应返回时不想阻止调用线程的情况。
HttpMethodExtensions 类通过将多个扩展方法添加到 HttpClient 以发出逻辑 Get、Post、Put 和 Delete 请求,使事情变得更加简单。 这些是使用 HttpClient 的 RESTful 服务时最有可能使用的方法。
public class HttpClient : IDisposable
{
public HttpClient();
public HttpClient(string baseAddress);
public HttpClient(Uri baseAddress);
public Uri BaseAddress { get; set; }
public RequestHeaders DefaultHeaders { get; set; }
public IList<HttpStage> Stages { get; set; }
public HttpWebRequestTransportSettings TransportSettings { get; set; }
public event EventHandler<SendCompletedEventArgs> SendCompleted;
public IAsyncResult BeginSend(HttpRequestMessage request,
AsyncCallback callback, object state);
protected virtual HttpStage CreateTransportStage();
public void Dispose();
protected virtual void Dispose(bool disposing);
public HttpResponseMessage EndSend(IAsyncResult result);
public HttpResponseMessage Send(HttpMethod method);
public HttpResponseMessage Send(HttpRequestMessage request);
public HttpResponseMessage Send(HttpMethod method, string uri);
public HttpResponseMessage Send(HttpMethod method, Uri uri);
public HttpResponseMessage Send(HttpMethod method, string uri,
HttpContent content);
public HttpResponseMessage Send(HttpMethod method, string uri,
RequestHeaders headers);
public HttpResponseMessage Send(HttpMethod method, Uri uri, HttpContent content);
public HttpResponseMessage Send(HttpMethod method, Uri uri,
RequestHeaders headers);
public HttpResponseMessage Send(HttpMethod method, string uri,
RequestHeaders headers, HttpContent content);
public HttpResponseMessage Send(HttpMethod method, Uri uri,
RequestHeaders headers, HttpContent content);
public void SendAsync(HttpRequestMessage request);
public void SendAsync(HttpRequestMessage request, object userState);
public void SendAsyncCancel(object userState);
protected void ThrowIfDisposed();
}
public static class HttpMethodExtensions
{
public static HttpResponseMessage Delete(this HttpClient client, string uri);
public static HttpResponseMessage Delete(this HttpClient client, Uri uri);
public static HttpResponseMessage Get(this HttpClient client);
public static HttpResponseMessage Get(this HttpClient client, string uri);
public static HttpResponseMessage Get(this HttpClient client, Uri uri);
public static HttpResponseMessage Get(this HttpClient client, Uri uri,
HttpQueryString queryString);
public static HttpResponseMessage Get(this HttpClient client, Uri uri,
IEnumerable<KeyValuePair<string, string>> queryString);
public static HttpResponseMessage Head(this HttpClient client, string uri);
public static HttpResponseMessage Head(this HttpClient client, Uri uri);
public static HttpResponseMessage Post(this HttpClient client, string uri,
HttpContent body);
public static HttpResponseMessage Post(this HttpClient client, Uri uri,
HttpContent body);
public static HttpResponseMessage Post(this HttpClient client, string uri,
string contentType, HttpContent body);
public static HttpResponseMessage Post(this HttpClient client, Uri uri,
string contentType, HttpContent body);
public static HttpResponseMessage Put(this HttpClient client, string uri,
HttpContent body);
public static HttpResponseMessage Put(this HttpClient client, Uri uri,
HttpContent body);
public static HttpResponseMessage Put(this HttpClient client, string uri,
string contentType, HttpContent body);
public static HttpResponseMessage Put(this HttpClient client, Uri uri,
string contentType, HttpContent body);
}
图 24:HttpClient 和 HttpMethodExtensions 类定义
让我们看一个示例,了解 HttpClient 如何简化操作。 我们将使用 Web 上发现的实际 RESTful 服务,该服务不是使用 .NET 生成的。 我们将使用 Twitter REST API。
如果在 上 http://apiwiki.twitter.com/Twitter-API-Documentation浏览到 Twitter REST API 文档,则可以快速了解如何开始发出适当的 HTTP 请求以与服务集成。 以下代码演示如何检索 Twitter 用户的“好友时间线”:
HttpClient http = new HttpClient("http://twitter.com/statuses/");
http.TransportSettings.Credentials =
new NetworkCredential("{username}", "{password}");
HttpResponseMessage resp = http.Get("friends_timeline.xml");
resp.EnsureStatusIsSuccessful();
ProcessStatuses(resp.Content.ReadAsStream());
构造 HttpClient 实例时,我们将提供 Twitter REST API 服务的基址,然后通过 TransportSettings.Credentials 属性提供用户的 HTTP 凭据。 现在,我们可以使用各种 Get、Post、Put 和 Delete 方法与服务公开的不同资源进行交互。 在此示例中,我调用 Get 从服务中检索“friends_timeline.xml”资源。 返回后,我可以对 resp 对象调用 EnsureStatusIsSuccessful,以确保我们获得了 200 级 HTTP 状态代码。
接下来,我们需要处理响应消息的内容。 实现此目的的一种方法是将响应作为流读取出来,然后使用你喜欢的 XML API (ReadAsStream) 处理该流。 以下示例演示如何使用 XmlDocument 处理响应 XML:
static void ProcessStatuses(Stream str)
{
XmlDocument doc = new XmlDocument();
doc.Load(str);
XmlNodeList statuses = doc.SelectNodes("/statuses/status");
foreach (XmlNode n in statuses)
Console.WriteLine("{0}: {1}",
n.SelectSingleNode("user/screen_name").InnerText,
n.SelectSingleNode("text").InnerText);
}
HttpClient 类具有将消息内容处理为流、字符串或字节数组的功能。 除了此基本功能外,WCF REST Start Kit (Preview 2) 还附带了另一个名为 Microsoft.Http.Extensions 的程序集,其中包含许多扩展方法,可用于使用 XLinq、XmlSerializer、SyndicationFeed 等) 的其他常用 (技术来处理消息内容。 我们将在下一节中了解这些扩展方法的工作原理。
让我们看一下另一个示例,演示如何更新用户的 Twitter 状态。 可以使用 HttpClient 使用以下代码 (假设 HttpClient 已在上面实例化) 来实现此目的:
HttpUrlEncodedForm form = new HttpUrlEncodedForm();
form.Add("status", "my first HttpClient app");
resp = http.Post("update.xml", form.CreateHttpContent());
resp.EnsureStatusIsSuccessful();
“update.xml”资源需要 POST 请求,并且它要求消息包含包含新状态文本的 URL 编码字符串。 使用 HttpClient 可以轻松通过 HttpUrlEncodedForm 类生成 URL 编码的消息,然后只需调用 Post 来提供内容。
此快速示例演示了使用 HttpClient 发出 GET 和 POST 请求是多么容易。 在以下部分中,我们将更深入地了解 HttpClient 的详细信息,并重点介绍其一些关键功能。
处理消息内容
通常,你需要使用比 Stream 更复杂的内容来处理 HTTP 响应的正文。 WCF REST 初学者工具包 (预览版 2) 附带了另一个名为 Microsoft.Http.Extensions 的程序集,该程序集使用许多扩展方法增强了 HttpContent 类,使可以使用特定 API 处理消息内容。
在当前版本中,这些扩展方法分散在多个命名空间中。 请务必注意,除非添加了对 Microsoft.Http.Extensions.dll 的引用,并且已将 using 语句添加到相应的命名空间,否则不会在 Intellisense 中看到它们。 图 25 列出了此附加程序集提供的 HttpContent 扩展方法。
扩展方法 | 命名空间 |
---|---|
ReadAsDataContract |
System.Runtime.Serialization |
ReadAsJsonDataContract |
System.Runtime.Serialization.Json |
ReadAsServiceDocument |
System.ServiceModel.Syndication |
ReadAsSyndicationFeed |
System.ServiceModel.Syndication |
ReadAsXmlReader |
System.Xml |
ReadAsXElement |
System.Xml.Linq |
ReadAsXmlSerializable |
System.Xml.Serialization |
图 25:HttpContent 扩展方法
让我们看一些示例,这些示例演示如何使用其中一些扩展方法。 我们将从 XLinq 示例开始,因为它是当今常见的 .NET XML 编程选择。 现在,我们将调用 ReadAsXElement,而不是对 Content 属性调用 ReadAsStream,如下所示:
HttpResponseMessage resp = http.Get("friends_timeline.xml");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsXElement(resp.Content.ReadAsXElement());
请记住,需要对 Microsoft.Http.Extensions 程序集的引用,并且需要将 using 语句添加到文件中,以便System.Xml。Linq – 假设你已完成这两个步骤,则应在 Content 属性的 Intellisense 中看到 ReadAsXElement。 以下示例演示了如何将消息内容作为 XElement 进行处理:
static void ProcessStatusesAsXElement(XElement root)
{
var statuses = root.Descendants("status");
foreach (XElement status in statuses)
Console.WriteLine("{0}: {1}",
status.Element("user").Element("screen_name").Value,
status.Element("text"));
}
如果要使用 XmlReader 而不是 XElement 处理消息内容,则非常类似 – 只需调用 ReadAsXmlReader 并相应地处理内容。
许多开发人员宁愿利用 XML 序列化技术,而不是直接使用 XML API,后者允许他们使用强类型 .NET 类型而不是泛型 XML 节点。 ReadAsDataContract 和 ReadAsJsonDataContract 扩展方法允许你分别利用 XML 或 JSON 内容的 DataContractSerializer () 进行序列化,而 ReadAsXmlSerializable 则允许你利用 XmlSerializer 序列化引擎。
但是,在使用这些基于序列化的任何方法之前,需要获取一些 .NET 类,这些类适当地为要处理的消息内容建模。 可以从服务提供的 XML 架构定义或从在开发期间实际使用该服务时检索的一些示例 XML 生成适当的类。 但是,如果其他所有操作都失败,你始终可以根据对 XML 格式的了解手动创作这些类型。
例如,下面是一些 C# 类,这些类对使用 DataContractSerializer 执行反序列化时从上述 Twitter 返回的状态建模:
[assembly: ContractNamespace("", ClrNamespace = "TwitterShell")]
[CollectionDataContract(Name = "statuses", ItemName = "status")]
public class statusList : List<status> { }
public class user
{
public string id;
public string name;
public string screen_name;
}
public class status
{
public string id;
public string text;
public user user;
}
使用这些类后,我们现在可以使用 ReadAsDataContract 方法指定在反序列化过程中要使用的根类型 (在本例中,statusList) :
HttpResponseMessage resp = http.Get("friends_timeline.xml");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsDataContract(resp.Content.ReadAsDataContract<statusList>());
将消息内容反序列化为 statusList 对象后,处理代码变得更加简单,如下所示:
static void ProcessStatusesAsDataContract(statusList list)
{
foreach (status status in list)
Console.WriteLine("{0}: {1}", status.user.screen_name, status.text);
}
如果 HTTP 响应将以 JSON 格式 (返回,而不是 XML) ,则只需调用 ReadAsJsonDataContract,如下所示:
HttpResponseMessage resp = http.Get("friends_timeline.json");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsDataContract(resp.Content.ReadAsJsonDataContract<statusList>());
其余的处理代码保持不变。
此外,如果要使用 XmlSerializer 引擎,首先需要确保具有与 XmlSerializer 映射兼容的可序列化类型。 对于此特定示例,我们需要将 statusList 类型替换为以下根类型 (,因为 XML 映射) 不同:
public class statuses
{
[XmlElement("status")]
public status[] status;
}
然后,只需调用 ReadAsXmlSerializable 而不是 ReadAsDataContract,指定要反序列化的根类型的状态。 通常,XmlSerializer 在能够处理的 XML 方面比 DataContractSerializer 更灵活。 因此,XmlSerializer 很可能成为使用 RESTful 服务的更常见选择。 此外,WCF REST 初学者工具包 (预览版 2) 附带 Visual Studio 插件,使生成 XmlSerializer 类型变得简单。 我们将在下一部分介绍其工作原理。
最后一个示例是,如果服务返回 Atom 源,该怎么办? 在这种情况下,只需调用 ReadAsSyndicationFeed,然后返回要处理的 SyndicationFeed 对象:
HttpResponseMessage resp = http.Get("friends_timeline.atom");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsFeed(resp.Content.ReadAsSyndicationFeed());
然后,可以处理 SyndicationFeed 对象以提取感兴趣的信息, (需要知道信息在 Atom 馈送结构中的位置) :
static void ProcessStatusesAsFeed(SyndicationFeed feed)
{
foreach (SyndicationItem item in feed.Items)
Console.WriteLine("{0}: {1}", item.Authors[0].Name, item.Title.Text);
}
如你所看到的,新的 HttpClient 类使得在 .NET 中使用 RESTful 服务变得非常简单。 Microsoft.Http.Extensions 中的新扩展方法在消息处理方面提供了极大的灵活性,并简化了一些最常见的 REST 方案, (XML、JSON 和 Atom 等) 。
Visual Studio 中的“将 XML 粘贴为类型”
由于 RESTful 服务不附带 WSDL,因此无法像你习惯使用 SOAP 那样生成强类型代理。 但是,由于所有 REST 服务 (HTTP 统一接口) 实现相同的服务协定,因此除了 HttpClient (Get、Post、Put 和 Delete) 提供的操作之外,你实际上不需要任何内容。 但是,你可能希望一些可序列化的类型来简化 HTTP 消息处理逻辑,我在上一部分中介绍了如何执行此操作。
如果相关服务提供描述内容 (的 XML 架构定义(如自动“帮助”页)在服务端) 提供 WCF REST 初学者工具包,则可以使用 xsd.exe 或 svcutil.exe 等工具来生成适当的类型。 如果没有,可以利用 WCF REST 初学者工具包 (预览版 2) 引入的新 Visual Studio 插件,称为“将 XML 粘贴为类型”。
此新功能允许将 XML 架构定义或示例 XML 实例复制到剪贴板,然后可以从“编辑”菜单中选择“将 XML 粘贴为类型”。 执行此操作时,它将为剪贴板中的架构/XML 生成相应的 XmlSerializer 类型,并将其粘贴到文件中的当前位置。 使用 XML 示例实例时,它并不总是能够生成完全精确的类型,因此可能需要在生成后执行一些额外的按摩。
让我们看看如何在 Twitter 示例中使用它。 如果使用浏览器 http://twitter.com/statuses/friends\_timeline.xml () 浏览到 Twitter“friends_timeline.xml”资源,则将返回此资源返回的实际 XML (见图 26) 。 现在执行“查看源”并将 XML 复制到剪贴板。 复制后,可以返回到 Visual Studio 并将光标置于我们希望生成的类型所在的位置。 然后,只需从“编辑”菜单中选择“将 XML 粘贴为类型” (请参阅图 27) ,所需的 XmlSerializer 类型将添加到文件中。 就位后,可以将这些类型与 ReadAsXmlSerializable 结合使用来处理消息内容。
在实现服务时,可以将此功能与 WCF REST 初学者工具包提供的“帮助”页结合使用。 可以在开发期间浏览帮助页,导航到不同资源和操作的架构或示例 XML 消息,还可以使用此插件在客户端应用程序中生成适当的消息类型。 通常,这降低了尝试与 RESTful 服务集成的客户端的限值,并简化了开发人员体验。
图 26:Twitter friends_timeline.xml 资源返回的示例 XML
图 27:将 XML 粘贴为“类型”菜单项
处理服务输入
上一部分重点介绍如何处理 HTTP 响应消息中的消息内容。 考虑如何生成 RESTful 服务所需的适当输入也很重要。 许多服务以查询字符串、URL 编码的表单数据的形式或通过各种其他格式接受输入,这些格式可在 HTTP 请求实体正文 ((例如 XML、JSON、Atom 等)) 。
如果服务需要查询字符串输入,则可以使用 HttpQueryString 类正确生成它。 以下示例演示如何生成可提供给 Get 方法的查询字符串:
HttpQueryString vars = new HttpQueryString();
vars.Add("id", screenname);
vars.Add("count", count);
resp = http.Get(new Uri("user_timeline.xml", UriKind.Relative), vars);
resp.EnsureStatusIsSuccessful();
DisplayTwitterStatuses(resp.Content.ReadAsXElement());
如果服务需要 URL 编码的表单输入 (例如,从 HTML <表单> 提交) 获得的内容,则可以使用 HttpUrlEncodedForm 或 HttpMultipartMimeForm 类来生成正确的内容, (稍后使用 (如果需要生成多部分 MIME 表单) )。 以下示例演示了如何生成可提供给 Post 方法的简单表单提交:
HttpUrlEncodedForm form = new HttpUrlEncodedForm();
form.Add("status", status);
resp = http.Post("update.xml", form.CreateHttpContent());
resp.EnsureStatusIsSuccessful();
Console.WriteLine("Status updated!");
除了这些类,Microsoft.Http.Extensions 程序集还附带了一些附加的扩展方法,这些方法可简化生成 HttpContent 对象的过程,这些对象可作为输入提供给 HttpClient。 这些方法在 HttpContentExtensions 类中定义,包括从 XElement) 创建 (、CreateDataContract、CreateJsonDataContract、CreateXmlSerializable、CreateAtom10SyndicationFeed 和 CreateRss20SyndicationFeed 等内容。 需要为 HTTP 请求消息生成其中一种类型的内容时,需要使用这些方法。
使用类型化标头简化标头处理
Microsoft.Http 程序集还附带一套强类型 HTTP 标头类。 可以在 Microsoft.Http.Headers 命名空间中找到它们。 例如,你将找到 CacheControl、Connection、Cookie、Credential、EntityTag、Expect 等类。
HttpClient 类提供了一个 DefaultHeaders 属性,该属性通过这些标头类型向你公开请求标头。 可以在发送请求之前操作单个标头。 HttpResponseMessage 类还附带了一个 Headers 属性,该属性通过这些标头类再次向你公开各种 HTTP 响应标头。 以下示例演示如何操作请求/响应中的标头以发出条件 GET 请求:
HttpResponseMessage resp = http.Get("public_timeline.atom");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsFeed(resp.Content.ReadAsSyndicationFeed());
DateTime? lastAccessDate = resp.Headers.Date;
...
http.DefaultHeaders.IfModifiedSince = lastAccessDate;
resp = http.Get("public_timeline.atom");
Console.WriteLine("status={0}", resp.StatusCode);
这些强类型 HTTP 标头类使代码中的 HTTP 标头更易于使用,因为它们可保护你免受基础 HTTP 协议详细信息的许多方面的限制。
HttpClient“阶段”处理
HttpClient 类附带一个请求拦截机制,可将自定义代码插入其中,类似于服务端 WebServiceHost2 提供的 RequestInterceptor 模型。 此请求拦截模型专为客户端 HTTP 交互而设计。
下面是它的工作原理。 HttpClient 类管理由 HttpStage 类建模的“HTTP 处理阶段”集合。 在发送任何消息之前,请使用 HttpClient 实例配置 HttpStage 派生对象的集合。 然后,每当发出 HTTP 请求时,HttpClient 实例都会调用每个 HttpStage 对象,使其有机会执行其处理。
有几个派生自 HttpStage、HttpProcessingStage 和 HttpAsyncStage 的类,它们分别提供同步和异步模型。 实现自己的自定义阶段时,通常会从这两个类之一派生。 从这些类派生时,你的工作是重写 ProcessRequest 和 ProcessResponse 方法来定义逻辑。
以下代码演示如何实现自定义 HttpProcessingStage,该自定义 HttpProcessingStage 只需将消息打印到控制台窗口:
public class MyHttpStage : HttpProcessingStage
{
public override void ProcessRequest(HttpRequestMessage request)
{
Console.WriteLine("ProcessRequest called: {0} {1}",
request.Method, request.Uri);
}
public override void ProcessResponse(HttpResponseMessage response)
{
Console.WriteLine("ProcessResponse called: {0}",
response.StatusCode);
}
}
实现自定义 HttpStage 派生类后,可以在使用 HttpClient 时利用它。 以下示例演示如何在发出任何 HTTP 请求之前将 MyHttpStage 实例添加到组合中:
HttpClient http = new HttpClient("http://twitter.com/statuses/");
http.TransportSettings.Credentials =
new NetworkCredential("skonnarddemo", "baby95");
// configure the custom stage
http.Stages.Add(new MyHttpStage());
HttpResponseMessage resp = http.Get("public_timeline.atom");
现在,调用 Get 方法时,你将看到 MyHttpStage 消息在向目标服务发出 HTTP 请求之前和之后打印到控制台窗口。 可以使用此拦截技术来实现各种客户端 HTTP 处理需求, (例如安全性、日志记录、跟踪、自定义缓存等) ,从而在 HTTP 客户端应用程序之间提高可重用性。
扩展 HttpClient 以创建专用客户端
除了 HttpStage 扩展性机制之外,还可以从 HttpClient 派生来创建自己的专用 REST 客户端库。 这允许你提供对 RESTful 服务的用户更有意义的方法和属性,并最终提供与典型 SOAP 代理类体验 (并行) 的开发人员体验。
作为 WCF REST 初学者工具包 (预览版 2) 的一部分,它们提供了名为 AtomPubClient 的专用 HttpClient 派生类的示例。 它提供用于与标准 AtomPub 服务交互的自定义客户端体验。 AtomPubClient 类定义如下所示:
public class AtomPubClient : HttpClient
{
public AtomPubClient();
public SyndicationItem AddEntry(SyndicationFeed feed, SyndicationItem newEntry);
public SyndicationItem AddEntry(Uri feedUri, SyndicationItem newEntry);
public SyndicationItem AddMediaResource(SyndicationFeed feed, string contentType,
string description, HttpContent mediaContent);
public SyndicationItem AddMediaResource(Uri mediaCollectionUri,
string contentType, string description, HttpContent mediaContent);
public void DeleteEntry(SyndicationItem entry);
public void DeleteEntry(Uri itemUri);
public SyndicationItem GetEntry(Uri itemUri);
public SyndicationFeed GetFeed(Uri feedUri);
public ServiceDocument GetServiceDocument(Uri serviceDocumentUri);
public SyndicationItem UpdateEntry(SyndicationItem oldValue,
SyndicationItem newValue);
public SyndicationItem UpdateEntry(Uri editUri, SyndicationItem newValue);
}
请注意,它如何提供特定于 AtomPub 服务的方法(如 AddEntry、GetEntry、UpdateEntry、GetFeed 等),与在 HttpClient 上使用基础 Get、Post、Put 和 Delete 方法相比,这是考虑与这些类型的服务交互更自然的方式。
下面的代码示例演示如何使用 AtomPubClient 类导航 AtomPub 服务并将新的 Atom 条目添加到第一个工作区集合中:
AtomPubClient client = new AtomPubClient();
ServiceDocument doc = client.GetServiceDocument(
new Uri("https://localhost:30807/Service.svc/"));
Uri feedUri = doc.Workspaces[0].Collections[0].Link;
SyndicationFeed feed = client.GetFeed(feedUri);
SyndicationItem item = new SyndicationItem()
{
Title = new TextSyndicationContent("New Item"),
PublishDate = DateTime.Now
};
client.AddEntry(feed, item);
WCF REST 初学者工具包 (预览版 2) 还附带了一个名为 PollingAgent 的类,该类可以更轻松地实现客户端逻辑来“轮询”服务资源,并且仅在资源更改时执行某些操作。 将 PollingAgent 与实际执行 HTTP 工作的 HttpClient 对象结合使用。 下面是 PollingAgent 的完整类定义:
public class PollingAgent : IDisposable
{
public PollingAgent();
public HttpClient HttpClient { get; set; }
public bool IgnoreExpiresHeader { get; set; }
public bool IgnoreNonOKStatusCodes { get; set; }
public bool IgnoreSendErrors { get; set; }
public TimeSpan PollingInterval { get; set; }
public event EventHandler<ConditionalGetEventArgs> ResourceChanged;
public void Dispose();
public void StartPolling();
public void StartPolling(Uri uri);
public void StartPolling(Uri uri, EntityTag etag, DateTime? lastModifiedTime);
public void StopPolling();
}
因此,创建 PollingAgent 类的实例,并为其提供要使用的 HttpClient 对象。 然后,为 ResourceChanged 事件建立回调方法并指定轮询间隔。 完成所有操作后,只需调用 StartPolling 并提供目标资源 URI。 以下代码示例演示了如何设置此项以“轮询”Twitter 公共时间线:
class Program
{
static void Main(string[] args)
{
PollingAgent pollingClient = new PollingAgent();
pollingClient.HttpClient = new HttpClient();
pollingClient.HttpClient.TransportSettings.Credentials =
new NetworkCredential("skonnarddemo", "baby95");
pollingClient.PollingInterval = TimeSpan.FromSeconds(10);
pollingClient.ResourceChanged += new EventHandler<ConditionalGetEventArgs>(
pollingClient_ResourceChanged);
pollingClient.StartPolling(
new Uri("http://twitter.com/statuses/public_timeline.xml"));
Console.WriteLine("polling...");
Console.ReadLine();
}
static void pollingClient_ResourceChanged(object s, ConditionalGetEventArgs e)
{
ProcessStatusesAsXElement(e.Response.Content.ReadAsXElement());
}
static void ProcessStatusesAsXElement(XElement root)
{
var statuses = root.Descendants("status");
foreach (XElement status in statuses)
Console.WriteLine("{0}: {1}",
status.Element("user").Element("screen_name").Value,
status.Element("text"));
}
}
从这些示例中可以看到,HttpClient 提供了一个简单的 HTTP 基础,你可以扩展该基础以提供更专业的客户端编程模型。 投资一些时间来构建自己的 HttpClient 派生类,在客户端开发人员体验方面会带来巨大的收益。
结论
Microsoft 正在努力提供一流的编程模型,以便通过 Microsoft .NET Framework 实现和使用 RESTful 服务。 WCF 3.5 引入了生成 RESTful 服务所需的基本“Web”编程模型,但它只是一个开始。 WCF REST 初学者工具包是 Microsoft 赞助的 CodePlex 项目,它提供一组 WCF 扩展和关键的 Visual Studio 集成,旨在简化侧重于 REST 的开发任务。 目前在 WCF REST 初学者工具包中找到的许多功能很可能在 .NET Framework 的未来版本中找到它们的方式,但是没有理由等待,你可以立即开始使用这些功能。
关于Author
Aaron Skonnard 是 Pluralsight 的联合创始人,Pluralsight 是一家 Microsoft 培训提供商,提供讲师引导式和按需开发人员课程。 这些天,亚伦大部分时间花在录制 Pluralsight 按需! 侧重于云计算、Windows Azure、WCF 和 REST 的课程。 Aaron 花了数年时间在世界各地编写、演讲和教授专业开发人员。 你可以通过 http://pluralsight.com/aaron 和 http://twitter.com/skonnard联系他。