作者 :Scott Cate
Web 服务是 .NET 框架的组成部分,提供跨平台解决方案,用于在分布式系统之间交换数据。 尽管 Web 服务通常用于允许不同的操作系统、对象模型和编程语言发送和接收数据,但它们还可用于将数据动态注入 a ASP.NET AJAX 页或将数据从页面发送到后端系统。 无需执行回发操作即可完成所有这些操作。
使用 ASP.NET AJAX 调用 Web 服务
Dan Wahlin
Web 服务是 .NET 框架的组成部分,提供跨平台解决方案,用于在分布式系统之间交换数据。 尽管 Web 服务通常用于允许不同的操作系统、对象模型和编程语言发送和接收数据,但它们还可用于将数据动态注入 a ASP.NET AJAX 页或将数据从页面发送到后端系统。 无需执行回发操作即可完成所有这些操作。
虽然 ASP.NET AJAX UpdatePanel 控件为 AJAX 启用任何 ASP.NET 页面提供了一种简单方法,但有时,无需使用 UpdatePanel 即可动态访问服务器上的数据。 在本文中,你将了解如何通过创建和使用 ASP.NET AJAX 页面中的 Web 服务来实现此目的。
本文将重点介绍核心 ASP.NET AJAX 扩展中提供的功能,以及名为 AutoCompleteExtender 的 ASP.NET AJAX 工具包中启用了 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>
为了允许使用 JavaScript Web 服务代理将 AJAX 页面从 ASP.NET AJAX 页面到 .NET Web 服务的 JavaScript 对象表示法(JSON)调用,进行此 HttpHandler 替换。 ASP.NET AJAX 将 JSON 消息发送到 Web 服务,而不是通常与 Web 服务关联的标准简单对象访问协议(SOAP)调用。 这会导致整体请求和响应消息更小。 它还允许更高效的客户端处理数据,因为 ASP.NET AJAX JavaScript 库经过优化,可以处理 JSON 对象。 列表 2 和列表 3 显示了 Web 服务请求和序列化为 JSON 格式的响应消息的示例。 一览 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 的 Web 服务
ASP.NET AJAX 框架提供了几种不同的方法来调用 Web 服务。 可以使用 AutoCompleteExtender 控件(ASP.NET AJAX 工具包中提供)或 JavaScript。 但是,在调用服务之前,必须启用 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 属性允许将标准 SOAP 消息发送到 Web 服务的客户端调用 GetCustomersByCountry()方法,但不允许从现成的 AJAX 应用程序 ASP.NET 发出 JSON 调用。 若要允许进行 JSON 调用,必须将 ASP.NET AJAX 扩展 ScriptService
的属性应用到 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 序列化或反序列化任务。 ScriptHandlerFactory(在 web.config 中配置)和其他相关类执行大量 JSON 处理。
使用 ScriptMethod 属性
ScriptService 属性是唯一必须在 .NET Web 服务中定义的 ASP.NET AJAX 属性,以便 ASP.NET AJAX 页面使用它。 但是,另一个名为 ScriptMethod 的属性也可以直接应用于服务中的 Web 方法。 ScriptMethod 定义三个属性,包括UseHttpGet
和 XmlSerializeString
ResponseFormat
。 如果 Web 方法接受的请求类型需要更改为 GET,则当 Web 方法需要以或对象的形式 XmlDocument
返回原始 XML 数据或 XmlElement
从服务返回的数据应始终序列化为 XML 而不是 JSON 时,更改这些属性的值可能很有用。
当 Web 方法应接受 GET 请求而不是 POST 请求时,可以使用 UseHttpGet 属性。 请求使用 URL 和转换为 QueryString 参数的 Web 方法输入参数发送。 UseHttpGet 属性默认为 false,仅当已知操作安全且敏感数据未传递到 Web 服务时,才应设置为 true
false。 列表 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 请求之外,当需要从服务而不是 JSON 返回 XML 响应时,还可以使用 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.Xml
XML 时ResponseFormat
,除从 Web 方法返回的字符串之外的所有返回类型都序列化为 XML。 设置为 XmlSerializeString
,从 Web 方法返回的所有类型都序列化为 XML,包括字符串类型。 如果 ResponseFormat 属性具有 XmlSerializeString 属性的值 ResponseFormat.Json
,则忽略该属性。
列表 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 服务使用的嵌套复杂类型也必须在客户端页中使用,可以将 AJAX GenerateScriptType 属性 ASP.NET 添加到 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 在客户端上使用,因为它们是嵌套类型(地址是类,并且 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 代码使用。 在 Web 服务上添加 GenerateScriptType 属性,自动生成并发送到客户端的 JavaScript 示例显示在一览 11 中。 本文稍后将介绍如何使用嵌套复杂类型。
列出 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 消息。 可以使用 ASP.NET AJAX ScriptManager 控件自动生成 JavaScript 代理。
使用 ScriptManager 的服务属性创建可调用 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 代理代码,可以将所需 .NET Web 服务的 URL 键入到 Internet Explorer 的地址框中,并将 /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 方法。 它传递从文本框获取的国家/地区值,以及一个名为 OnWSRequestComplete 的回调函数,该函数应在异步 Web 服务调用返回时调用。 OnWSRequestComplete 处理从服务返回的 Customer 对象的数组,并将其转换为页面中显示的表。 从调用生成的输出如图 1 所示。
图 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() 回调函数,该函数接受将错误表示为参数的对象。 错误对象公开多个不同的函数,以确定错误的原因以及调用是否超时。列表 14 显示了使用不同错误函数的示例,图 2 显示了函数生成的输出示例。
图 2:通过调用 AJAX 错误函数 ASP.NET 生成的输出。 (单击可查看全尺寸图像)
处理从 Web 服务返回的 XML 数据
之前,你了解了 Web 方法如何使用 ScriptMethod 属性及其 ResponseFormat 属性返回原始 XML 数据。 当 ResponseFormat 设置为 ResponseFormat.Xml 时,从 Web 服务返回的数据将序列化为 XML 而不是 JSON。 当需要将 XML 数据直接传递到客户端以使用 JavaScript 或 XSLT 进行处理时,这非常有用。 目前,由于对 MSXML 的内置支持,Internet Explorer 5 或更高版本提供了用于分析和筛选 XML 数据的最佳客户端对象模型。
从 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 属性应用于服务,否则不能直接在客户端上访问嵌套的复杂类型。 为什么要在客户端上使用嵌套复杂类型?
若要回答此问题,假设 AJAX 页面 ASP.NET 显示客户数据,并允许最终用户更新客户的地址。 如果 Web 服务指定地址类型(CustomerDetails 类中定义的复杂类型)可以发送到客户端,则可以将更新过程划分为单独的函数,以便更好地重复使用代码。
图 3:从调用返回 RSS 数据的 Web 服务创建的输出。 (单击可查看全尺寸图像)
列表 16 显示了一个客户端代码示例,该代码调用模型命名空间中定义的 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")+ "!");
}
创建和使用 Page 方法
Web 服务提供了向各种客户端(包括 AJAX 页面 ASP.NET)公开可重用服务的绝佳方法。 但是,在某些情况下,页面需要检索其他页面永远不会使用或共享的数据。 在这种情况下,使 .asmx 文件允许页面访问数据可能看起来过于过度,因为服务只供单个页面使用。
ASP.NET AJAX 提供了另一种机制,用于在不创建独立的 .asmx 文件的情况下进行类似于 Web 服务的调用。 这是通过使用称为“页面方法”的技术完成的。 页面方法是静态的(在 VB.NET 中共享)方法,直接嵌入在已应用 WebMethod 属性的页面或代码旁文件中。 通过应用 WebMethod 属性,可以使用名为 PageMethod 的特殊 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 代理对象非常相似。 首先指定应传递给页面方法的所有参数数据,然后定义在异步调用返回时应调用的回调函数。 还可以指定失败回调(有关处理失败的示例,请参阅列表 14)。
AutoCompleteExtender 和 ASP.NET AJAX 工具包
ASP.NET AJAX 工具包(可从 https://github.com/DevExpress/AjaxControlToolkit中获取)提供多个可用于访问 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" %>
图 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 参数表示最终用户键入的字符,计数参数表示要返回的项数(默认值为 10)。 一览 20 显示了一个示例,该示例显示了 AutoCompleteExtender 控件调用的 GetCustomerIDs Web 方法示例,该控件在一览 19 中所示。 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 为 ASP.NET 开发人员网站(www.XMLforASP.NET)创立了 XML,位于 INETA 议长局,并在几个会议上发表演讲。 Dan 共同创作的专业 Windows DNA(Wrox),ASP.NET:提示、教程和代码(Sams),ASP.NET 1.1 预览体验成员解决方案、专业 ASP.NET 2.0 AJAX (Wrox),ASP.NET 2.0 MVP 黑客,并为 ASP.NET 开发人员(Sams)创作 XML。 当他不编写代码、文章或书籍时,Dan 喜欢写和录制音乐,并与妻子和孩子一起打高尔夫球和篮球。
斯科特·凯特自1997年以来一直在与Microsoft Web 技术合作,是 myKB.com(www.myKB.com)的总裁,专门编写专注于知识库软件解决方案的基于 ASP.NET 的应用程序。 斯科特可以通过电子邮件联系 ScottCate.com scott.cate@myKB.com