dazhong

导航

使用 AJAX Extensions 客户端进行 Web 服务调用


非常 ASP.NET
使用 AJAX Extensions 客户端进行 Web 服务调用


Download ImageGet the sample code for this article.


目录
使用 AJAX 调用 Web 服务
工作原理
序列化
总结


从根本上讲,ASP.NET 自始至终都是一项服务器端技术。当然,在某些情况下 ASP.NET 会生成客户端 JavaScript,特别是在验证控件中以及在新推出的 Web 部件基础结构中,但它通常只是简单地将客户端属性转换成客户端行为。作为开发人员,在收到下一个 POST 请求之前不必考虑与客户端进行交互。对于需要使用客户端 JavaScript 和 DHTML 构建更具交互性的页面的开发人员而言,则需要在 ASP.NET 2.0 脚本回调功能提供的一些帮助下自己编写代码。这一情况在去年得到了彻底改变。

在 2005 年 9 月的 Microsoft 在 Microsoft 专业开发人员大会上发布了一个新的 ASP.NET 插件(代号为“Atlas”),主要是为了充分利用客户端 JavaScript、DHTML 和 XMLHttpRequest 对象。其目的是帮助开发人员创建更具交互性的支持 AJAX 的 Web 应用程序。此框架从此更名为正式名称 Microsoft® AJAX Library 和 ASP.NET 2.0 AJAX Extensions,它提供了许多出色的功能,包括客户端数据绑定、DHTML 动画和行为以及使用 UpdatePanel 实现的完善的对客户端 POST 回调的拦截。这些功能中的许多功能依赖的是以易于通过客户端 JavaScript 调用进行分析和交互的形式从服务器异步检索数据的能力。本月专栏的主题便是这一新的非常有用的能力,即在支持 ASP.NET 2.0 AJAX Extensions 的页面中通过客户端 JavaScript 调用服务器端 Web 服务的能力。


使用 AJAX 调用 Web 服务

如果您曾经使用过 Microsoft .NET Framework 中的 Web 服务,无论是使用 wsel.exe 实用程序创建代理还是使用 Visual Studio® 的“添加 Web 引用”功能,您就会习惯于使用 .NET 类型调用 Web 服务。实际上,通过 .NET 代理调用 Web 服务方法与在其他类上调用方法非常相似。代理会根据您传递的参数准备 XML,它会妥善地将它收到的 XML 响应转换成代理方法指定的 .NET 类型。开发人员可以非常方便地利用 .NET Framework 使用 Web 服务端点,这也使目前面向服务的应用程序变得可行。

ASP.NET 2.0 AJAX Extensions 使得在浏览器中运行的客户端 JavaScript 实现了无缝的、与 Web 服务完全相同的代理生成体验。您可以编写一个在您的服务器上承载的 .asmx 文件,并通过一个客户端 JavaScript 类调用该服务上方法。例如,图 1 显示了一个简单的 .asmx 服务,该服务实现了模拟的股票报价检索(使用随机数据)。

除了标准的 .asmx Web 服务属性外,此服务还增添了 ScriptService 属性,使其同样可适用于 JavaScript 客户端。如果此 .asmx 文件部署在支持 ASP.NET AJAX 的 Web 应用程序中,您可以通过为您的 .aspx 文件中的 ScriptManager 控件添加 ServiceReference,从 JavaScript 调用服务的方法(当您使用支持 ASP.NET AJAX 的网站模板在 Visual Studio 中创建网站时,此控件会自动添加到您的 default.aspx 页面中):

<asp:ScriptManager ID="_scriptManager" runat="server">
            <Services>
            <asp:ServiceReference Path="StockQuoteService.asmx" />
            </Services>
            </asp:ScriptManager>
            

 

现在您可以通过任何客户端 JavaScript 例程,使用 MsdnMagazine.StockQuoteService 类调用服务的任何方法。由于调用的基本机制在本质上是异步的,因此没有同步方法可用。每个代理方法带有一个额外的参数(除了标准的输入参数外),该参数引用了另一个在该方法完成时异步调用的客户端 JavaScript 函数。图 2 中显示的示例页面使用客户端 JavaScript 将调用股票报价 Web 服务的结果显示在页面上的标签 (span) 中。

如果客户端 Web 服务调用出现了问题,您一定希望让客户端知道这一情况,通常明智的做法是在出现错误、中止或超时时,调用另一个方法进行传递。例如,您可以按如下所示更改上面显示的 OnLookup 方法,并额外添加一个 OnError 方法,以显示所有问题:

function OnLookup()
            {
            var stb = document.getElementById("_symbolTextBox");
            MsdnMagazine.StockQuoteService.GetStockQuote(
            stb.value, OnLookupComplete, OnError);
            }
            function OnError(result)
            {
            alert("Error: " + result.get_message());
            }
            

 

这样,如果 Web 服务调用失败,您将通过警报框通知客户端。您还可以在从客户端发出的对任何 Web 服务的调用中加入 userContext 参数,该参数是作为 Web 方法的最后参数传入的任意字符串,它将作为额外的参数传播给成功和失败的方法。在这种情况下,将被请求股票的实际符号作为 userContext 进行传递会比较有意义,这样您就可在 OnLookupComplete 方法中显示它:

function OnLookup()
            {
            var stb = document.getElementById("_symbolTextBox");
            MsdnMagazine.StockQuoteService.GetStockQuote(
            stb.value, OnLookupComplete, OnError, stb.value);
            }
            function OnLookupComplete(result, userContext)
            {
            // userContext contains symbol passed into method
            var res = document.getElementById("_resultLabel");
            res.innerHTML = userContext + " : <b>" + result + "</b>";
            }
            

 

如果您发现您要对某个 Web 服务进行多个不同的调用,并且对于每个调用您重复使用相同的错误和/或完成方法,那么您还可以设置全局默认的失败和成功的回调方法。这就避免了在每次调用时都必须指定一对回调方法,而且您还可以为各个方法分别进行选择,使其忽略全局定义。以下是 OnLookup 方法的示例,其中设置了全局的(而不是对单个调用的)默认的成功和失败的回调方法。

// Set default callbacks for stock quote service
            MsdnMagazine.StockQuoteService.set_defaultSucceededCallback(
            OnLookupComplete);
            MsdnMagazine.StockQuoteService.set_defaultFailedCallback(
            OnError);
            function OnLookup()
            {
            MsdnMagazine.StockQuoteService.GetStockQuote(stb.value);
            }
            

 

还有一种可以为您的 Web 服务方法构建完整的 .asmx 文件的有趣方法,就是将 Web 服务方法直接内嵌在页类中。如果为您希望调用的方法构建完整的 Web 服务端点没有意义,那么您可以在您的页面中公开一个可通过客户端 JavaScript 调用的 Web 方法,做法是向页面中添加一个服务器端方法(直接在页面中添加或者以代码隐藏的方式添加)并用 WebMethod 属性对其进行批注。然后您就可以通过客户端对象 PageMethods 调用它了。图 3 中的示例显示的是经过重新编写的股票报价服务示例,它完全包含在一个页中,而不是分割到各个 Web 服务中。

请记住,这些客户端代理只能由 ASP.NET .asmx 端点、Windows Communication Foundation .svc 端点或直接内嵌在页面中的 WebMethod 生成,而且不是调用任意 Web 服务的通用机制。实际上,对于基本的 XmlHTTPRequest 对象有一般性限制,请求的范围仅限于加载页面的域(出于安全的原因),因此这一做法无法用于调用任意 Web 服务,无论客户端代理是否支持此操作。如果您发现需要调用外部 Web 服务,最好在您调用外部 Web 服务的 .NET 代理类(使用 wsdl.exe 或 Visual Studio 中的“添加 Web 引用”生成)的应用程序中设置一个桥接的 .asmx 端点。

Back to top

工作原理

您可以采用标准的 .asmx Web 服务,几乎不做任何更改即可在浏览器中通过客户端 JavaScript 对其进行访问,乍看起来有些匪夷所思。秘密就在于注册了一个新的 .asmx HTTP 处理程序,并将其添加到了每个支持 ASP.NET AJAX 的网站的配置文件中:

<httpHandlers>
            <remove verb="*" path="*.asmx"/>
            <add verb="*" path="*.asmx"
            type="Microsoft.Web.Services.ScriptHandlerFactory"
            validate="false"/>
            </httpHandlers>
            

 

如果对一个 .asmx 端点进行标准的 Web 服务请求,则这个新注册的处理程序将调用标准 Web 服务处理程序 (System.Web.Services.Protocols.WebServiceHandlerFactory)。但是,如果请求在 URL 中有后缀 /js 或者包含带有 mn= 变量的查询字符串(如 ?mn=GetStockQuote),则处理程序会返回一个 JavaScript 块,为 Web 服务创建一个客户端代理(带有 /js 的情况),或者会调用 WebService 派生类中定义的相应方法,并把响应打包在 JavaScript Object Notation (JSON) 编码的字符串中(带有 ?mn= 的情况)。

当页面包含对 .asmx 服务的客户端引用(通过 ScriptManager 控件中的 ServiceReference 元素)时,它会注入使用后缀 /js 引用 .asmx 文件的脚本元素,从而在客户端创建代理。例如,我在上面构建的股票报价页面会在其中显示以下脚本元素:

<script src="StockQuoteService.asmx/js"
            type="text/javascript"></script>
            

 

当然,这是在添加了对 Microsoft AJAX Library 的脚本引用的基础上,AJAX Library 中包含了与此代理进行交互所需的客户端功能。如果您尝试自己导航至此端点,您将看到以下 JavaScript(部分):

Type.registerNamespace('MsdnMagazine');
            MsdnMagazine.StockQuoteService=function() {
            this._timeout = 0;
            this._userContext = null;
            this._succeeded = null;
            this._failed = null;
            }
            MsdnMagazine.StockQuoteService.prototype={
            GetStockQuote:Sys.Net._WebMethod._createProxyMethod(this,
            "GetStockQuote",
            "MsdnMagazine.StockQuoteService.GetStockQuote",
            "symbol"), ...
            }
            

 

此 JavaScript 使用每个包含 ScriptManager 控件的页面中所包含的 Microsoft AJAX Library 的功能(如命名空间和 WebMethod 类)。此 JavaScript 创建的代理方法经过初始化,利用此例中的查询字符串 ?mn=GetStockQuote 调用 .asmx 端点,因此无论您何时从客户端调用 MsdnMagazine.StockQuoteService.GetStockQuote,它都会变成对同一 .asmx 端点的异步 Web 请求。将客户端代理生成和服务器端对 JavaScript 发出的 Web 服务调用的支持相结合,意味着您可以以一种直观的方式包含对您的 .asmx Web 服务的客户端调用。

Back to top

序列化

基于 AJAX 的 Web 服务的默认序列化是 JSON。如果您注意到上一部分所显示的一系列页面操作,会发现 Web 服务请求和响应的主体部分类似于:

Request: {"symbol":"ABC"}
            Response: 51
            

 

这当然不是您在调用 .asmx Web 服务时所习惯看到的标准 XML 格式,因为 .asmx 端点在构建时已序列化到 XML 中,所以 ASP.NET 2.0 AJAX Extensions 所增加一个主要内容便是 JSON 序列化程序。实际上共有两个序列化程序:一个在 JavaScript 中实现,用于客户端,一个在 .NET 中实现,用于服务器,尤其用在 AJAX 客户端调用 .asmx 端点时。服务器端序列化程序可通过 Microsoft.Web.Script.Serialization.JavaScriptSerializer 使用,客户端序列化程序可通过 Sys.Serialization.JavaScriptSerializer 使用。使用 JSON 作为基于 XML 的序列化格式的一个主要优势是您只需简单地求得 JSON 字符串的值即可对 JavaScript 中的对象反序列化。客户端序列化程序类的反序列化方法最终会变得非常简短(去掉了错误检查):

Sys.Serialization.JavaScriptSerializer.deserialize=
            function(){eval('('+data+')');}
            

 

而另一方面,JavaScriptSerializer 的序列化方法却更复杂了。使用 JSON 的另一优势就是它与对应的 XML 相比,其表示形式更加精简。

与标准 Web 服务使用 XmlSerializer 将类型序列化到 XML 中非常类似,您可以采用几乎任何 .NET 类型并使用 JavaScriptSerializer 将其序列化到 JSON 中。如果您希望亲自尝试,只需调用 JavaScriptSerializer 类的 Serialize 方法。图 4 显示了一个示例控制台应用程序,此例中,它对复杂的 Person 类型(如图 5 所示)进行序列化。(该程序必须包含对 Microsoft.Web.Extensions.dll 的程序集引用,该 DLL 随 ASP.NET AJAX Extensions 安装在全局程序集缓存 (GAC) 中。)

控制台应用程序的输出将是 JSON 格式的 Person 类,或:

{"Married":true,"Age":33,"FirstName":"Bob","LastName":"Smith"}
            

 

正像 XmlSerializer 那样,JavaScriptSerializer 将仅对一种类型的公共可访问数据进行序列化,并且不支持对循环引用的解析。但任何可由标准 .asmx Web 服务序列化的类型也都能与此序列化程序一起正常工作(当然其中包括 DataSet)。鉴于这一点,您可以构建更复杂的 Web 服务,因为它处理复杂类型就像 .asmx 文件中定义的任何基于 SOAP 的 Web 服务一样轻松。

图 6 中的示例显示了一个名为 MarriageService 的 Web 服务,它实现了 Marry 方法,它采用两个 Person 对象(正如前面定义的)并对其属性进行相应的修改。(本期的代码下载部分包含有此例附带的 ASP.NET 页面。)

如果您选择在您的客户端脚本中使用 XML,该选项仍然可用。除了在定义 Web 服务时使用的标准 WebMethod 属性外,Microsoft.Web.Script.Services 命名空间中还有一个名为 ScriptMethod 的新属性,它具有 ResponseFormat 特性,该特性可设置为 Json 或 Xml(默认值为 Json)。

namespace PS
            {
            [ScriptService]
            [WebService(Namespace = "http://pluralsight.com/ws")]
            [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
            public class StockQuoteService : WebService
            {
            [WebMethod]
            public int GetStockQuote(string symbol)
            {
            return (new Random()).Next(0, 120);
            }
            }
            }
            
这样,序列化的响应将为:
<?xml version="1.0" encoding="utf-8"?><int>74</int>
            

 

当您通过客户端 JavaScript 调用此方法来处理 XML 响应时,怎样选择由您自己决定。如果您计划对其执行转换,或者已经在使用 MSXML,那么这会非常有用。

Back to top

总结

有必要指出的是,ASP.NET 2.0 AJAX Extensions 提供了两个预先构建的服务,用于通过客户端代码访问特定 ASP.NET 2.0 应用程序服务,它们是:ProfileService 和 AuthenicationService。使用这两个客户端代理类,您可以为单个客户端设置和检索配置文件值,并完全在客户端脚本中执行身份验证(通过默认的成员资格提供程序)和授予身份验证 cookies。

尽管许多有关 ASP.NET 2.0 AJAX Extensions 的讨论和演示都偏重于介绍那些使用户界面具备更高响应能力的漂亮控件,但是能够直接通过客户端 JavaScript 调用 Web 服务是最吸引人、最实用的功能之一。凭借完善的 .NET Framework/JSON 序列化程序、与熟悉的 .asmx Web 服务的直接集成、对批处理的支持和自动生成的与外部 Web 服务的桥接,如此全面且深入地支持 Web 服务可能会使这一功能成为所有功能中最吸引人的功能。

Back to top

将您想向 Fritz 询问的问题和提出的意见发送至  xtrmasp@microsoft.com..
Download Image NEW: Explore the sample code online! - or - 代码下载位置: ExtremeASPNET2007_01.exe (160KB)

Fritz Onion是 Microsoft .NET 培训提供商 Pluralsight 的创始人之一,负责 Web 开发课程。Fritz 是《Essential ASP.NET》(Addison Wesley,2003)和《Essential ASP.NET 2.0》(Addison Wesley,2006)的作者。您可以通过 pluralsight.com/fritz 与他联系。

Subscribe  摘自 January 2007 期刊 MSDN Magazine.

posted on 2007-04-05 17:32  大钟  阅读(183)  评论(0编辑  收藏  举报