WCF中的REST架构三 (支持AJAX的WCF Service – 使用服务)

在昨天的文章WCF中的REST架构二(支持AJAX的WCF服务 - 创建服务)中, 我介绍了一个包含一些ASP.NET AJAX 客户端库控件的入门解决方案。这个解决方案还没有具体实现的代码, 等着我们为它编写支持AJAX的WCF服务和客户端代码去调用这些服务. 我随后介绍了如何利用webHttpBinding和enableWebScript 这两个endpoint behavior去实现一个支持AJAX的WCF服务。

在本篇中,我将会编写客户端的代码去调用我们的服务。 你可能还记得我们上一篇中的Catalog.aspx (应用程序中包含AJAX控件的主要页面)有一个空方法GetData。这个方法在页面初始化的时候,以及在用户在不同商品的不同页面间跳转时被调用。这个方法就是我们要编写代码去调用我们的服务的地方。

注册服务

在我过于深入之前,让我们先短暂停留一会儿。之前我已经编写了一个支持AJAX的WCF服务,但是这究竟意味着什么?从本质上来说,这意味着我们的服务有一个endpoint behavior, 它允许我们创建客户端的AJAX代理类,用几行代码就可以调用我们提供的服务。然后,我们仅仅只需用ScriptManager注册我们的服务就可以实现将这个代理类转化成”流”(stream)传给浏览器。请看下面的代码:

<asp:ScriptManager ID="scriptManager" runat="server">

<Scripts>

<asp:ScriptReference Path="Controls/Catalog.js" />

<asp:ScriptReference Path="Controls/HighlightImageBehavior.js" />

<asp:ScriptReference Path="Controls/ShoppingCart.js" />

<asp:ScriptReference Path="Controls/LoginStatus.js" />

</Scripts>

<Services>

<asp:ServiceReference Path="~/CatalogService.svc" />

</Services>

</asp:ScriptManager>

如果你对ScriptManager不熟悉,他是一个ASP.NET控件,责任是按需分发页面必需的javacript. 如果你有ASP.NET AJAX服务器控件的经验,你会知道它们就是通过ScriptManager来交流他们的脚本需求的。ScriptManager然后给出相应的脚本元素, 在上面的代码中,这个Script节点包含了4个指向专用于这个购物示例应用程序的ASP.NET AJAX客户端库的引用。他们与我们当前的议题(调用WCF服务)并不相关。重要的部分是Service节点。你会注意到这个节点包含了一个指向CatalogService.svc文件路径(即服务的路径)的 ServiceReference。 我们这里主要做的就是把这个支持AJAX的WCF服务注册到ScriptManager中来。

通过注册,我们告诉ScriptManager为网页分发一个指向一个HttpHandler的script元素,它知道如何生成服务的客户端代理。为了展示这一点,我将直接在浏览器中打开这个网页,然后在页面上点击右键,选择”查看源码”。往下翻动,下面那是我找到的内容:

image_2

代理类

如果你想看一下客户端代理的代码(你应该看一下),在你的网页URL后面加上CatalogService.svc/jsdebug。在我的例子中,完整的URL是: https://localhost:50183/ClientStore/CatalogService.svc/jsdebug。打开后你会得到如下的对话框:

image_thumb_1

我将代理保存到我桌面的本地文件,更名为proxy.js。然后我将这个文件拖放到我的好友Visual Studio中去。以下是我看到的部分:

image_thumb_2

第一个值得注意的地方是这里的代理利用了ASP.NET 客户端库。在这些库中,我们”.NET化”了JavaScript。 我们添加了命名空间(namespace),类等一系列概念。如果你对更详细的ASP.NET AJAX客户端库感兴趣的话,可以参看我写的12 part webcast series。里面介绍了几乎ASP.NET 客户端类实现的所有方面。

第二个值得我们注意的地方是,AJAX类的命名空间和我们在第二篇里面创建的服务的命名空间是一致的。最后一件我想讨论的是,GetPatients操作(operation)被加到了AJAX类的原型(prototype)当中(在JavaScript当中,每一个对象都有一个对象原型。这个原型就是用来存放那些非实例特有的成员,例如方法,的正确地方)。你会注意到这里的操作的签名(signature)基本上是和我们服务操作的签名是一致的。不同的地方是这里多了调用成功或失败的回调函数,以及context参数。这些参数是必要的,因为回调函数是在异步模式下被调用的。换一种说法, 我们的服务操作被”AJAX化”了。现在调用我们的服务就变得很简单了:1) 创建一个代理类的实例。 2) 调用代理的GetPatients 方法。事实上,我们还有第三步和第四步要做的事情,我们还需要编写我们的回调方法。让我来通过扩充GetData的代码来给大家演示。

完成调用

在你刚开始创建你的代理实例的时候就会注意到的是,VS2008为代理类提供了IntelliSense支持。见下图:

image_thumb_3

下面是GetData的完整代码:

// This function calls the GetProductGrouping AJAX method,

// using the script proxy

function GetData()

{

var startIndex = ((currentPage) * pageSize);

var proxy = new shopping.services.CatalogService();

proxy.GetProductGrouping(startIndex, pageSize, ProductsReturnedEventHandler, ProductsFailedEventHandler, null);

}

我们曾经需要为实现正确的行为写上几十上百行代码,而不是这里的两行。当提到这点的时候,我是在特别针对那些已经不必再由我们自己去实现的那些串行化(serialize)和反串行化(deserialize)代码。如果你真的要自己去实现所有的代码的话,你必须写代码把你的客户端参数(以及方法信息,如果你用POST的话)串行化成例如JSON或者XML那样的格式。然后你需要在服务器端去写代码反串行化你的那些参数,调用服务,得到.NET类型的服务响应。接着你还需要将这些.NET类型再串行化回JSON或者XML格式(或者其他你选择的格式)并将他们以异步的方式传回客户端。最后,你还需要在客户端将这些数据流反串行化给JavaScript。 现在,.NET已经帮你打理好了这所有的一切。

在第二篇中,我讨论了ProductsReturnedEventHandler是如何简单的得到一个AJAX控件,设置两个属性的值(他们其中之一的值被设为服务调用响应的返回值),以及调用一个方法去做DOM插入。让我们在Visual Studio调试功能和WebDevHelper的帮助下来看一下往返发送的数据内容。第一个我想让大家看的是在做完所有串行化/反串行化以后传递给回调方法的参数。请注意传递给回调方法的JavaScript类型是我们在第二篇中详细讲述的.NET类型的近似表示。 就连.NET的类型名也通过”__type”被传递了。看下图:

image_10

下一个我要展示给大家的是真正的请求和响应(这里借助Nikhil的WebDevHelper工具):

image_thumb_6

我们也可以看到请求和响应的内容:

image_thumb_7

控制如何曝露(Expose) 服务

你在上面注意到,服务默认是以POST的方式曝露的。甚至大多数自由REST主义者(liberal RESTafarians)看到这个都会皱眉头。GET才是大多数人对于获取操作的实际选择。还有,你会注意到默认的数据表达格式(数据串行化方式)是JSON。但万一如果你并不想用JSON,而想用XML的某种形式那该怎么做呢?结论是,通过使用enableWebScript endpoint behavior你可以控制两样东西。通过webHttp endpoint behavior, 可以控制更多的东西。你将在以后的文章中读到这些内容。

为了控制操作的Http 动词(Verb), 你可以用下面的一个或两个属性来修饰你的操作:

  • [WebGet] – 将Http 动词设成GET
  • [WebInvoke(Method=”…”)] – 将Http 动词设成GET以外的任意动词。不过,当使用了enableWebScript的时候,只有POST是被支持的。其他的动词只被webHttp endpoint behavior支持。

WebGet和WebInvoke都有一个ResponseFormat参数。你可以将它设成WebMessageFormat.Xml 或者WebMessageFormat.Json。你可以从下面的代码看到我如何设置这些选择。

[OperationContract]

[WebGet(ResponseFormat=WebMessageFormat.Xml)]

public ProductGroupingData GetProductGrouping(int startIndex, int pageSize)

为了演示这些设置的结果,我打开另一个的页面调用这个服务。下面是WebDevHelper里面的结果:

image_thumb_8

支持AJAX的REST服务的总结

在这两篇文章中, 我实现了一个支持AJAX的WCF服务和一个使用这个服务的AJAX客户端。那么,这个服务是否落脚在了REST领域中了呢?如果是的话,在哪里?我相信它满足了RESTFUL的标准。首先,它没有用到SOAP包,这无疑迈出了第一步。当我们比照不同的RESTfulness的不同标准的时候,我们发现我们的实现很明显落在了 LO REST这一边。考虑下面几点:

  • 我们只用了两个Http 动词,GET 和POST。我们把PUT和DELETE都忽略了。在一个HI REST场景中,人们可以说,通过Http动词就足以理解调用的目的(GET用来获取,PUT用来插入或更新,DELETE用来删除,POST用来添加 – 或者一些类似的任务)。在我们的例子中,我们反复使用了POST。我们必须通过检查请求的内容才能够理解调用的目的。结论 -> LO
  • 在URI可定位性(addressability)这方面,我们在GET中使用查询字符串。更进一步,我们的实现感觉上有点RPC的意味。结论-> LO
  • 在表达格式这方面,我们可以选择任意二者之一。虽然JSON是标准,但他并不影响数据的含义,而只是一种语法罢了。并且,你当然也可以返回一种标准,在你的服务中接收XML的表示。结论-> 无法确定

请不要把我上面这段蠢话误解为在给这个服务打分。我不是这个意思,我喜欢这里的支持AJAX的实现。我只是想给你们一点东西去回味咀嚼它在REST领域中的定位。

在后面的文章中,我将会给大家展示一种更靠近于HI REST 一端的实现。