了解 ASP.NET AJAX Web 服务

作者 :Scott Cate

Web 服务是 .NET Framework 不可或缺的一部分,该框架提供跨平台解决方案,用于在分布式系统之间交换数据。 尽管 Web 服务通常用于允许不同的操作系统、对象模型和编程语言发送和接收数据,但它们也可用于将数据动态注入 ASP.NET AJAX 页或将数据从页面发送到后端系统。 所有这些操作都可以在不采用回发操作的情况下完成。

使用 ASP.NET AJAX 调用 Web 服务

Dan Wahlin

Web 服务是 .NET Framework 不可或缺的一部分,该框架提供跨平台解决方案,用于在分布式系统之间交换数据。 尽管 Web 服务通常用于允许不同的操作系统、对象模型和编程语言发送和接收数据,但它们也可用于将数据动态注入 ASP.NET AJAX 页或将数据从页面发送到后端系统。 所有这些操作都可以在不采用回发操作的情况下完成。

虽然 ASP.NET AJAX UpdatePanel 控件为 AJAX 启用任何 ASP.NET 页提供了一种简单方法,但有时可能需要在不使用 UpdatePanel 的情况下动态访问服务器上的数据。 本文介绍如何通过在 ASP.NET AJAX 页面中创建和使用 Web 服务来实现此目的。

本文重点介绍核心 AJAX 扩展 ASP.NET 中提供的功能,以及名为 AutoCompleteExtender 的 AJAX 工具包 ASP.NET 启用了 Web 服务的控件。 涵盖的主题包括定义已启用 AJAX 的 Web 服务、创建客户端代理以及使用 JavaScript 调用 Web 服务。 你还将了解如何直接对 ASP.NET 页方法进行 Web 服务调用。

Web 服务配置

使用 Visual Studio 2008 创建新网站项目时,web.config文件包含许多可能不熟悉以前版本的 Visual Studio 用户的新添加项。 其中一些修改将“asp”前缀映射到 ASP.NET AJAX 控件,以便可以在页面中使用它们,而其他修改则定义所需的 HttpHandlers 和 HttpModules。 列表 1 显示了对 <httpHandlers> 影响 Web 服务调用的 web.config 中的 元素所做的修改。 删除用于处理 .asmx 调用的默认 HttpHandler,并将其替换为位于 System.Web.Extensions.dll 程序集中的 ScriptHandlerFactory 类。 System.Web.Extensions.dll包含 ASP.NET AJAX 使用的所有核心功能。

列表 1. ASP.NET AJAX Web 服务处理程序配置

<httpHandlers>
     <remove verb="*" path="*.asmx"/>
     <add verb="*" path="*.asmx" validate="false"
          type="System.Web.Script.Services.ScriptHandlerFactory,
          System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,
          PublicKeyToken=31bf3856ad364e35"/>
</httpHandlers>

进行此 HttpHandler 替换是为了允许使用 JavaScript Web 服务代理从 ASP.NET AJAX 页面到 .NET Web Services 的 JavaScript 对象表示法 (JSON) 调用。 ASP.NET AJAX 将 JSON 消息发送到 Web 服务,而不是标准简单对象访问协议 (SOAP) 通常与 Web 服务关联的调用。 这会导致请求和响应消息整体减少。 它还允许更高效的客户端处理数据,因为 ASP.NET AJAX JavaScript 库已优化为使用 JSON 对象。 清单 2 和清单 3 显示了序列化为 JSON 格式的 Web 服务请求和响应消息的示例。 清单 2 中显示的请求消息传递值为“比利时”的国家/地区参数,而列表 3 中的响应消息传递 Customer 对象及其关联属性的数组。

清单 2. 序列化为 JSON 的 Web 服务请求消息

{"country":"Belgium"}

> [!注意] 操作名称定义为 Web 服务的 URL 的一部分;此外,请求消息并不总是通过 JSON 提交。 Web 服务可以利用 ScriptMethod 属性,并将 UseHttpGet 参数设置为 true,这会导致通过查询字符串参数传递参数。

列表 3. 序列化为 JSON 的 Web 服务响应消息

[{"__type":"Model.Customer","Country":"Belgium","CompanyName":"Maison
     Dewey","CustomerID":"MAISD","ContactName":"Catherine
     Dewey"},{"__type":"Model.Customer","Country":"Belgium","CompanyName":"Suprêmes
     délices","CustomerID":"SUPRD","ContactName":"Pascale
     Cartrain"}]

在下一部分中,你将了解如何创建能够处理 JSON 请求消息并使用简单和复杂类型进行响应的 Web 服务。

创建AJAX-Enabled Web 服务

ASP.NET AJAX 框架提供了几种不同的方法来调用 Web 服务。 可以使用 ASP.NET AJAX 工具包) 或 JavaScript 中提供的 AutoCompleteExtender 控件 (。 但是,在调用服务之前,必须启用 AJAX,以便客户端脚本代码可以调用该服务。

无论你是否不熟悉 ASP.NET Web 服务,你都会发现创建和启用 AJAX 的服务非常简单。 自 2002 年首次发布以来,.NET Framework 一直支持创建 ASP.NET Web 服务,ASP.NET AJAX 扩展提供了基于 .NET Framework 的默认功能集构建的其他 AJAX 功能。 Visual Studio .NET 2008 Beta 2 内置支持创建 .asmx Web 服务文件,并从 System.Web.Services.WebService 类自动派生类旁边的关联代码。 将方法添加到 类时,必须应用 WebMethod 属性,以便 Web 服务使用者调用它们。

清单 4 显示了将 WebMethod 属性应用于名为 GetCustomersByCountry () 的方法的示例。

清单 4. 在 Web 服务中使用 WebMethod 属性

[WebMethod]
public Customer[] GetCustomersByCountry(string country)
{
     return Biz.BAL.GetCustomersByCountry(country);
}

GetCustomersByCountry () 方法接受国家/地区参数并返回 Customer 对象数组。 传递到 方法的国家/地区值将转发到业务层类,后者又调用数据层类以从数据库中检索数据、使用数据填充 Customer 对象属性并返回数组。

使用 ScriptService 属性

虽然添加 WebMethod 属性允许向 Web 服务发送标准 SOAP 消息的客户端调用 GetCustomersByCountry () 方法,但它不允许从 ASP.NET AJAX 应用程序开箱即用进行 JSON 调用。 若要允许进行 JSON 调用,必须将 AJAX 扩展的 ScriptService ASP.NET 属性应用于 Web 服务类。 这使 Web 服务能够发送使用 JSON 格式的响应消息,并允许客户端脚本通过发送 JSON 消息来调用服务。

清单 5 显示了将 ScriptService 属性应用于名为 CustomersService 的 Web 服务类的示例。

列表 5. 使用 ScriptService 属性启用 AJAX Web 服务

[System.Web.Script.Services.ScriptService]
[WebService(Namespace = "http://xmlforasp.net")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class CustomersService : System.Web.Services.WebService
{
     [WebMethod]
     public Customer[] GetCustomersByCountry(string country)
     {
          return Biz.BAL.GetCustomersByCountry(country);
     }
}

ScriptService 属性充当标记,指示可以从 AJAX 脚本代码调用它。 它实际上不会处理任何在后台发生的 JSON 序列化或反序列化任务。 在 web.config) 和其他相关类中配置的 ScriptHandlerFactory (执行大量 JSON 处理。

使用 ScriptMethod 属性

ScriptService 属性是唯一 ASP.NET AJAX 属性,它必须在 .NET Web 服务中定义才能供 ASP.NET AJAX 页面使用。 但是,另一个名为 ScriptMethod 的属性也可以直接应用于服务中的 Web 方法。 ScriptMethod 定义了三个属性,包括 UseHttpGetResponseFormatXmlSerializeString。 当 Web 方法需要以 或 XmlElement 对象的形式XmlDocument返回原始 XML 数据,或者从服务返回的数据应始终序列化为 XML 而不是 JSON 时,如果 Web 方法接受的请求类型需要更改为 GET,则更改这些属性的值非常有用。

当 Web 方法应接受 GET 请求而不是 POST 请求时,可以使用 UseHttpGet 属性。 使用 URL 发送请求,其中 Web 方法输入参数已转换为 QueryString 参数。 UseHttpGet 属性默认为 false,并且仅当已知操作是安全的并且敏感数据未传递到 Web 服务时,才应设置为 true 。 列表 6 显示了一个将 ScriptMethod 属性与 UseHttpGet 属性配合使用的示例。

清单 6. 将 ScriptMethod 属性与 UseHttpGet 属性配合使用。

[WebMethod]
[ScriptMethod(UseHttpGet = true)]
public string HttpGetEcho(string input)
{
     return input;
}

下面显示了调用清单 6 中显示的 HttpGetEcho Web 方法时发送的标头示例:

GET /CustomerViewer/DemoService.asmx/HttpGetEcho?input=%22Input Value%22 HTTP/1.1

除了允许 Web 方法接受 HTTP GET 请求外,当 XML 响应需要从服务而不是 JSON 返回时,也可以使用 ScriptMethod 属性。 例如,Web 服务可以从远程站点检索 RSS 源,并将其作为 XmlDocument 或 XmlElement 对象返回。 然后,可以在客户端上处理 XML 数据。

列表 7 显示了使用 ResponseFormat 属性指定应从 Web 方法返回 XML 数据的示例。

清单 7. 将 ScriptMethod 属性与 ResponseFormat 属性一起使用。

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml)]
public XmlElement GetRssFeed(string url)
{
     XmlDocument doc = new XmlDocument();
     doc.Load(url);
     return doc.DocumentElement;
}

ResponseFormat 属性还可以与 XmlSerializeString 属性一起使用。 XmlSerializeString 属性的默认值为 false,这意味着当属性设置为 ResponseFormat.XmlResponseFormat,除从 Web 方法返回的字符串之外的所有返回类型都序列化为 XML。 当 设置为 trueXmlSerializeString,从 Web 方法返回的所有类型都序列化为 XML,包括字符串类型。 如果 ResponseFormat 属性的值为 ResponseFormat.Json XmlSerializeString,则忽略属性。

清单 8 显示了使用 XmlSerializeString 属性强制字符串序列化为 XML 的示例。

列表 8. 将 ScriptMethod 属性与 XmlSerializeString 属性一起使用

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml,XmlSerializeString=true)]
public string GetXmlString(string input)
{
     return input;
}

从调用清单 8 中显示的 GetXmlString Web 方法返回的值如下所示:

<?xml version="1.0"?>
     <string>Test</string>

尽管默认 JSON 格式最大程度地减少了请求和响应消息的总体大小,并且更容易 ASP.NET AJAX 客户端以跨浏览器方式使用,但当客户端应用程序(如 Internet Explorer 5 或更高版本)希望从 Web 方法返回 XML 数据时,可以使用 ResponseFormat 和 XmlSerializeString 属性。

使用复杂类型

清单 5 显示了从 Web 服务返回名为 Customer 的复杂类型的示例。 Customer 类在内部定义了多个不同的简单类型,例如 FirstName 和 LastName 等属性。 在启用 AJAX 的 Web 方法上用作输入参数或返回类型的复杂类型在发送到客户端之前,会自动序列化为 JSON。 但是,嵌套复杂类型 (其他类型内部定义的类型) 默认情况下不会作为独立对象提供给客户端。

如果还必须在客户端页中使用 Web 服务使用的嵌套复杂类型,可以将 ASP.NET AJAX GenerateScriptType 属性添加到 Web 服务。 例如,清单 9 中显示的 CustomerDetails 类包含表示嵌套复杂类型的 Address 和 Gender 属性。

清单 9. 此处显示的 CustomerDetails 类包含两个嵌套复杂类型。

public class CustomerDetails : Customer
{
     public CustomerDetails()
     {
     }
     Address _Address;
     Gender _Gender = Gender.Unknown;
     public Address Address
     {
          get { return _Address; }
          set { _Address = value; }
     }
     public Gender Gender
     {
          get { return _Gender; }
          set { _Gender = value; }
     }
}

清单 9 中显示的 CustomerDetails 类中定义的 Address 和 Gender 对象不会自动通过 JavaScript 在客户端上可用,因为它们是嵌套类型, (Address 是类,Gender 是枚举) 。 在 Web 服务中使用的嵌套类型必须在客户端上可用的情况下,可以使用前面提到的 GenerateScriptType 属性 (请参阅清单 10) 。 如果从服务返回了不同的嵌套复杂类型,则可以多次添加此属性。 它可以直接应用于 Web 服务类或高于特定 Web 方法。

清单 10. 使用 GenerateScriptService 属性定义应可供客户端使用的嵌套类型。

[System.Web.Script.Services.ScriptService]
[System.Web.Script.Services.GenerateScriptType(typeof(Address))]
[System.Web.Script.Services.GenerateScriptType(typeof(Gender))]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class NestedComplexTypeService : System.Web.Services.WebService
{
     //Web Methods
}

通过将 属性应用于 GenerateScriptType Web 服务,地址和性别类型将自动可供客户端 ASP.NET AJAX JavaScript 代码使用。 清单 11 中显示了通过添加 Web 服务上的 GenerateScriptType 属性自动生成并发送到客户端的 JavaScript 示例。 本文稍后将介绍如何使用嵌套复杂类型。

清单 11. ASP.NET AJAX 页面可用的嵌套复杂类型。

if (typeof(Model.Address) === 'undefined')
{
     Model.Address=gtc("Model.Address");
     Model.Address.registerClass('Model.Address');
}
Model.Gender = function() { throw Error.invalidOperation(); }
Model.Gender.prototype = {Unknown: 0,Male: 1,Female: 2}
Model.Gender.registerEnum('Model.Gender', true);

现在,你已了解如何创建 Web 服务并使其可供 ASP.NET AJAX 页面访问,让我们看看如何创建和使用 JavaScript 代理,以便可以检索或将数据发送到 Web 服务。

创建 JavaScript 代理

调用标准 Web 服务 (.NET 或其他平台) 通常涉及创建代理对象,该对象可避免发送 SOAP 请求和响应消息的复杂性。 使用 ASP.NET AJAX Web 服务调用,可以创建并使用 JavaScript 代理轻松调用服务,而无需担心序列化和反序列化 JSON 消息。 JavaScript 代理可以使用 ASP.NET AJAX ScriptManager 控件自动生成。

可以使用 ScriptManager 的 Services 属性创建可调用 Web 服务的 JavaScript 代理。 此属性允许定义一个或多个服务,ASP.NET AJAX 页面可以异步调用来发送或接收数据,而无需回发操作。 通过使用 ASP.NET AJAX ServiceReference 控件并将 Web 服务 URL 分配给控件的属性 Path 来定义服务。 清单 12 显示了引用名为 CustomersService.asmx 的服务的示例。

<asp:ScriptManager ID="ScriptManager1" runat="server">
     <Services>
          <asp:ServiceReference Path="~/CustomersService.asmx" />
     </Services>
</asp:ScriptManager>

清单 12. 定义 ASP.NET AJAX 页中使用的 Web 服务。

通过 ScriptManager 控件添加对 CustomersService.asmx 的引用会导致页面动态生成并引用 JavaScript 代理。 使用脚本>标记嵌入代理,<并通过调用 CustomersService.asmx 文件并将 /js 追加到其末尾来动态加载代理。 以下示例演示在 web.config 中禁用调试时,JavaScript 代理如何嵌入页面:

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

> [!注意]若要查看生成的实际 JavaScript 代理代码,可以在 Internet Explorer 的地址框中键入所需 .NET Web 服务的 URL,并在其末尾追加 /js。

如果在 web.config中启用了调试,则 JavaScript 代理的调试版本将嵌入页面,如下所示:

<script src="CustomersService.asmx/jsdebug" type="text/javascript"></script>

ScriptManager 创建的 JavaScript 代理也可以直接嵌入页面,而不是使用 <脚本> 标记的 src 属性进行引用。 为此,可以将 ServiceReference 控件的 InlineScript 属性设置为 true, (默认值为 false) 。 如果代理未在多个页面之间共享,并且你想要减少对服务器发出的网络调用数,这非常有用。 当 InlineScript 设置为 true 时,浏览器不会缓存代理脚本,因此,如果代理由 ASP.NET AJAX 应用程序中的多个页面使用,建议使用默认值 false。 下面显示了使用 InlineScript 属性的示例:

<asp:ServiceReference InlineScript="true" Path="~/CustomersService.asmx"/>

使用 JavaScript 代理

使用 ScriptManager 控件由 ASP.NET AJAX 页引用 Web 服务后,即可对 Web 服务进行调用,并使用回调函数处理返回的数据。 如果存在) 、类名和 Web 方法名称,则通过引用其命名空间 (来调用 Web 服务。 可以定义传递给 Web 服务的任何参数以及处理返回数据的回调函数。

清单 13 中显示了使用 JavaScript 代理调用名为 GetCustomersByCountry () 的 Web 方法的示例。 当最终用户单击页面上的按钮时,将调用 GetCustomersByCountry () 函数。

清单 13. 使用 JavaScript 代理调用 Web 服务。

function GetCustomerByCountry()
{
     var country = $get("txtCountry").value;
     InterfaceTraining.CustomersService.GetCustomersByCountry(country, OnWSRequestComplete);
}
function OnWSRequestComplete(results)
{
     if (results != null)
     {
          CreateCustomersTable(results);
          GetMap(results);
     }
}

此调用引用在服务中定义的 InterfaceTraining 命名空间、CustomersService 类和 GetCustomersByCountry Web 方法。 它传递从文本框获取的国家/地区值,以及应在异步 Web 服务调用返回时调用的名为 OnWSRequestComplete 的回调函数。 OnWSRequestComplete 处理从服务返回的 Customer 对象的数组,并将其转换为页面中显示的表。 调用生成的输出如图 1 所示。

通过对 Web 服务进行异步 AJAX 调用来绑定数据。

图 1:通过对 Web 服务进行异步 AJAX 调用来绑定数据。 (单击以查看全尺寸图像)

在应调用 Web 方法但代理不应等待响应的情况下,JavaScript 代理还可以对 Web 服务进行单向调用。 例如,你可能希望调用 Web 服务来启动一个进程(如工作流),但不要等待服务返回值。 如果需要对服务进行单向调用,只需省略清单 13 中显示的回调函数。 由于未定义回调函数,因此代理对象不会等待 Web 服务返回数据。

处理错误

Web 服务的异步回调可能会遇到不同类型的错误,例如网络关闭、Web 服务不可用或返回异常。 幸运的是,ScriptManager 生成的 JavaScript 代理对象允许定义多个回调,以处理错误和失败,以及前面所示的成功回调。 在调用 Web 方法时,可以在标准回调函数之后立即定义错误回调函数,如清单 14 所示。

清单 14. 定义错误回调函数并显示错误。

function GetCustomersByCountry() 
{
     var country = $get("txtCountry").value;
     InterfaceTraining.CustomersService.GetCustomersByCountry(country, 
          OnWSRequestComplete, OnWSRequestFailed);
}
function OnWSRequestFailed(error)
{
     alert("Stack Trace: " + error.get_stackTrace() + "/r/n" +
          "Error: " + error.get_message() + "/r/n" +
          "Status Code: " + error.get_statusCode() + "/r/n" +
          "Exception Type: " + error.get_exceptionType() + "/r/n" +
          "Timed Out: " + error.get_timedOut());
}

调用 Web 服务时发生的任何错误都将触发要调用的 OnWSRequestFailed () 回调函数,该回调函数接受将错误表示为参数的对象。 error 对象公开多个不同的函数,以确定错误的原因以及调用是否超时。列表 14 显示了使用不同错误函数的示例,图 2 显示了函数生成的输出示例。

通过调用 ASP.NET AJAX 错误函数生成的输出。

图 2:调用 ASP.NET AJAX 错误函数生成的输出。 (单击以查看全尺寸图像)

处理从 Web 服务返回的 XML 数据

之前,你已了解 Web 方法如何使用 ScriptMethod 属性及其 ResponseFormat 属性返回原始 XML 数据。 当 ResponseFormat 设置为 ResponseFormat.Xml 时,从 Web 服务返回的数据将序列化为 XML 而不是 JSON。 当 XML 数据需要直接传递到客户端以使用 JavaScript 或 XSLT 进行处理时,这非常有用。 目前,Internet Explorer 5 或更高版本提供了用于分析和筛选 XML 数据的最佳客户端对象模型,因为它内置了对 MSXML 的支持。

从 Web 服务检索 XML 数据与检索其他数据类型没有什么不同。 首先调用 JavaScript 代理来调用相应的函数并定义回调函数。 调用返回后,可以在回调函数中处理数据。

列表 15 显示了调用名为 GetRssFeed () 的 Web 方法的示例,该方法返回 XmlElement 对象。 GetRssFeed () 接受表示要检索的 RSS 源的 URL 的单个参数。

清单 15. 使用从 Web 服务返回的 XML 数据。

function GetRss()
{
     InterfaceTraining.DemoService.GetRssFeed(
          "https://blogs.interfacett.com/dan-wahlins-blog/rss.xml",
          OnWSRequestComplete);
}
function OnWSRequestComplete(result)
{
     if (document.all) //Filter for IE DOM since other browsers are limited
     {
          var items = result.selectNodes("//item");
          for (var i=0;i<items.length;i++)
          {
               var title = items[i].selectSingleNode("title").text;
               var href = items[i].selectSingleNode("link").text;
               $get("divOutput").innerHTML +=
               "<a href='" + href + "'>" + title + "</a><br/>";
          }
     }
     else
     {
          $get("divOutput").innerHTML = "RSS only available in IE5+";
     }
}

此示例将 URL 传递到 RSS 源,并在 OnWSRequestComplete () 函数中处理返回的 XML 数据。 OnWSRequestComplete () 首先检查浏览器是否为 Internet Explorer,以了解 MSXML 分析程序是否可用。 如果是,则使用 XPath 语句查找 <RSS 源中的所有项> 标记。 然后循环访问每个项目,并找到并处理关联的 <标题> 和 <链接> 标记,以显示每个项的数据。 图 3 显示了通过 JavaScript 代理对 GetRssFeed () Web 方法进行 ASP.NET AJAX 调用时生成的输出示例。

处理复杂类型

Web 服务接受或返回的复杂类型通过 JavaScript 代理自动公开。 但是,除非如前所述将 GenerateScriptType 属性应用于服务,否则无法直接在客户端上访问嵌套复杂类型。 为什么要在客户端上使用嵌套复杂类型?

若要回答此问题,假设 ASP.NET AJAX 页面显示客户数据并允许最终用户更新客户的地址。 如果 Web 服务指定 Address 类型 (CustomerDetails 类中定义的复杂类型) 可以发送到客户端,则可以将更新过程划分为单独的函数,以便更好地重用代码。

从调用返回 RSS 数据的 Web 服务创建的输出。

图 3:从调用返回 RSS 数据的 Web 服务创建的输出。 (单击以查看全尺寸图像)

清单 16 显示了一个客户端代码示例,该示例调用在 Model 命名空间中定义的 Address 对象,用更新的数据填充该对象,并将其分配给 CustomerDetails 对象的 Address 属性。 然后,CustomerDetails 对象将传递给 Web 服务进行处理。

清单 16. 使用嵌套复杂类型

function UpdateAddress()
{
     var cust = new Model.CustomerDetails();
     cust.CustomerID = $get("hidCustomerID").value;
     cust.Address = CreateAddress();
     InterfaceTraining.DemoService.UpdateAddress(cust,OnWSUpdateComplete);
}
function CreateAddress()
{
     var addr = new Model.Address();
     addr.Street = $get("txtStreet").value;
     addr.City = $get("txtCity").value;
     addr.State = $get("txtState").value;
     return addr;
}
function OnWSUpdateComplete(result)
{
     alert("Update " + ((result)?"succeeded":"failed")+ "!");
}

创建和使用页面方法

Web 服务提供了一种向各种客户端(包括 ASP.NET AJAX 页面)公开可重用服务的绝佳方法。 但是,在某些情况下,页面可能需要检索不会由其他页面使用或共享的数据。 在这种情况下,创建一个 .asmx 文件以允许页面访问数据似乎过于过分,因为该服务仅由单个页面使用。

ASP.NET AJAX 提供了另一种机制,用于进行类似于 Web 服务的调用,而无需创建独立的 .asmx 文件。 这是通过使用一种称为“页面方法”的技术完成的。 页面方法是静态 (共享 VB.NET) 方法中直接嵌入页面或代码旁文件的方法,这些方法应用了 WebMethod 属性。 通过应用 WebMethod 属性,可以使用在运行时动态创建的名为 PageMethods 的特殊 JavaScript 对象调用它们。 PageMethods 对象充当代理,可保护你免受 JSON 序列化/反序列化过程的防护。 请注意,若要使用 PageMethods 对象,必须将 ScriptManager 的 EnablePageMethods 属性设置为 true。

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
</asp:ScriptManager>

列表 17 显示了在 ASP.NET 代码旁的类中定义两个页面方法的示例。 这些方法从位于网站的 App_Code 文件夹中的业务层类检索数据。

列表 17. 定义页面方法。

[WebMethod]
public static Customer[] GetCustomersByCountry(string country)
{
     return Biz.BAL.GetCustomersByCountry(country);
}
[WebMethod]
public static Customer[] GetCustomersByID(string id)
{
     return Biz.BAL.GetCustomersByID(id);
}

当 ScriptManager 检测到页面中是否存在 Web 方法时,它会生成对前面提到的 PageMethods 对象的动态引用。 调用 Web 方法是通过引用 PageMethods 类,后跟方法的名称以及应传递的任何必要参数数据来完成的。 列表 18 显示了调用前面所示的两个页面方法的示例。

列表 18. 使用 PageMethods JavaScript 对象调用页面方法。

function GetCustomerByCountry() 
{
     var country = $get("txtCountry").value;
     PageMethods.GetCustomersByCountry(country, OnWSRequestComplete);
}
function GetCustomerByID() 
{
     var custID = $get("txtCustomerID").value;
     PageMethods.GetCustomersByID(custID, OnWSRequestComplete);
}
function OnWSRequestComplete(results) 
{
     var searchResults = $get("searchResults");
     searchResults.control.set_data(results);
     if (results != null) GetMap(results[0].Country,results);
}

使用 PageMethods 对象与使用 JavaScript 代理对象非常相似。 首先指定应传递给 page 方法的所有参数数据,然后定义在异步调用返回时应调用的回调函数。 还可以指定失败回调 (请参阅列表 14 获取) 处理失败的示例。

AutoCompleteExtender 和 ASP.NET AJAX 工具包

) 提供的 https://www.devexpress.com/Products/AJAX-Control-Toolkit ASP.NET AJAX 工具包 (提供了多个可用于访问 Web 服务的控件。 具体而言,该工具包包含一个名为 AutoCompleteExtender 的有用控件,可用于调用 Web 服务并在页面中显示数据,而无需编写任何 JavaScript 代码。

AutoCompleteExtender 控件可用于扩展文本框的现有功能,并帮助用户更轻松地找到他们要查找的数据。 当他们在文本框中键入内容时,该控件可用于查询 Web 服务,并在文本框下方动态显示结果。 图 4 显示了使用 AutoCompleteExtender 控件显示支持应用程序的客户 ID 的示例。 当用户在文本框中键入不同的字符时,将根据其输入在其下方显示不同的项。 然后,用户可以选择所需的客户 ID。

在 ASP.NET AJAX 页中使用 AutoCompleteExtender 需要将AjaxControlToolkit.dll程序集添加到网站的 bin 文件夹中。 添加工具包程序集后,需要在web.config中引用它,以便其包含的控件可用于应用程序中的所有页面。 这可以通过在web.config的 <控件> 标记中添加以下标记来完成:

<add namespace="AjaxControlToolkit" assembly="AjaxControlToolkit" tagPrefix="ajaxToolkit"/>

如果只需要在特定页面中使用 控件,则可以通过将 Reference 指令添加到页面顶部(如下所示)来引用它,而不是更新web.config:

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" 
     TagPrefix="ajaxToolkit" %>

使用 AutoCompleteExtender 控件。

图 4:使用 AutoCompleteExtender 控件。 (单击以查看全尺寸图像)

将网站配置为使用 ASP.NET AJAX 工具包后,可以将 AutoCompleteExtender 控件添加到页面中,就像添加常规 ASP.NET 服务器控件一样。 列表 19 显示了使用 控件调用 Web 服务的示例。

列表 19. 使用 ASP.NET AJAX Toolkit AutoCompleteExtender 控件。

<ajaxToolkit:AutoCompleteExtender ID="extTxtCustomerID" runat="server"
     MinimumPrefixLength="1" ServiceMethod="GetCustomerIDs"
     ServicePath="~/CustomersService.asmx"
     TargetControlID="txtCustomerID" />

AutoCompleteExtender 具有多个不同的属性,包括在服务器控件上找到的标准 ID 和 runat 属性。 除此之外,它还允许在查询 Web 服务数据之前定义最终用户键入的字符数。 清单 19 中显示的 MinimumPrefixLength 属性会导致每次在文本框中键入字符时调用服务。 需要小心设置此值,因为每次用户键入字符时,都会调用 Web 服务来搜索与文本框中的字符匹配的值。 要调用的 Web 服务以及目标 Web 方法分别使用 ServicePath 和 ServiceMethod 属性进行定义。 最后,TargetControlID 属性标识将 AutoCompleteExtender 控件挂钩到哪个文本框。

所调用的 Web 服务必须应用前面所述的 ScriptService 属性,并且目标 Web 方法必须接受名为 prefixText 和 count 的两个参数。 prefixText 参数表示最终用户键入的字符,count 参数表示要返回的项数, (默认值为 10) 。 列表 20 显示了前面在列表 19 中显示的 AutoCompleteExtender 控件调用的 GetCustomerIDs Web 方法的示例。 Web 方法调用业务层方法,而业务层方法又调用处理数据筛选和返回匹配结果的数据层方法。 数据层方法的代码如清单 21 所示。

列表 20. 筛选从 AutoCompleteExtender 控件发送的数据。

[WebMethod]
public string[] GetCustomerIDs(string prefixText, int count) 
{
     return Biz.BAL.GetCustomerIDs(prefixText, count);
}

列表 21. 根据最终用户输入筛选结果。

public static string[] GetCustomerIDs(string prefixText, int count)
{
     //Customer IDs cached in _CustomerIDs field to improve performance
     if (_CustomerIDs == null)
     {
          List<string> ids = new List<string>();
          //SQL text used for simplicity...recommend using sprocs
          string sql = "SELECT CustomerID FROM Customers";
          DbConnection conn = GetDBConnection();
          conn.Open();
          DbCommand cmd = conn.CreateCommand();
          cmd.CommandText = sql;
          DbDataReader reader = cmd.ExecuteReader();
          while (reader.Read())
          {
               ids.Add(reader["CustomerID"].ToString());
          }
          reader.Close();
          conn.Close();
          _CustomerIDs = ids.ToArray();
     }
     int index = Array.BinarySearch(_CustomerIDs, prefixText, new CaseInsensitiveComparer());
     //~ is bitwise complement (reverse each bit)
     if (index < 0) index = ~index;
     int matchingCount;
     for (matchingCount = 0; matchingCount < count && index + matchingCount < _CustomerIDs.Length; matchingCount++)
     {
          if (!_CustomerIDs[index + matchingCount].StartsWith(prefixText, StringComparison.CurrentCultureIgnoreCase))
          {
               break;
          }
     }
     String[] returnValue = new string[matchingCount];
     if (matchingCount > 0)
     {
          Array.Copy(_CustomerIDs, index, returnValue, 0, matchingCount);
     }
     return returnValue;
}

结论

ASP.NET AJAX 为调用 Web 服务提供了出色的支持,无需编写大量自定义 JavaScript 代码来处理请求和响应消息。 本文介绍了如何通过 AJAX 启用 .NET Web 服务来处理 JSON 消息,以及如何使用 ScriptManager 控件定义 JavaScript 代理。 你还了解了如何使用 JavaScript 代理来调用 Web 服务、处理简单和复杂类型以及处理故障。 最后,你了解了如何使用页面方法简化创建和发出 Web 服务调用的过程,以及 AutoCompleteExtender 控件如何在最终用户键入时为其提供帮助。 尽管 ASP.NET AJAX 中提供的 UpdatePanel 因其简单性而成为许多 AJAX 程序员的首选控件,但了解如何通过 JavaScript 代理调用 Web 服务在许多应用程序中都很有用。

个人简介

Dan Wahlin (Microsoft 最有价值专家 ASP.NET 和 XML Web Services) 是接口技术培训 (http://www.interfacett.com) 的 .NET 开发讲师和体系结构顾问。 Dan 创立了 xml for ASP.NET Developers 网站 (www.XMLforASP.NET) , 是 INETA 发言人局的一部分, 并在几次会议中发言。 Dan 共同创作了 Professional Windows DNA (Wrox) ,ASP.NET:Tips, Tutorials and Code (Sams) ,ASP.NET 1.1 Insider Solutions,Professional ASP.NET 2.0 AJAX (Wrox) ,ASP.NET 2.0 MVP Hacks,并为 ASP.NET Developers (Sams) 创作 XML。 当他不写代码、文章或书籍时,丹喜欢写和录制音乐,和他的妻子和孩子一起打高尔夫球和打篮球。

Scott Cate 自 1997 年以来一直从事 Microsoft Web 技术工作,是 myKB.com (www.myKB.com) 的总裁,专门编写基于 ASP.NET 的应用程序,专注于知识库软件解决方案。 可以通过电子邮件 scott.cate@myKB.com 联系 Scott,也可以通过他的博客 在 ScottCate.com