可打印版本       提交     
请单击以进行评分并提供反馈
Related Articles

Cobra 是 Python 的子代,提供动静结合的编程模型、内置的单元测试工具以及脚本功能等。通过本文了解其强大功能。

Ted Neward

MSDN Magazine 六月 2009

...

Read more!

本月,我们将在 AJAX 应用程序的上下文中分析表单,并介绍自动保存、实时验证和提交限制等功能的各种实现方法。

Dino Esposito

MSDN Magazine 六月 2009

...

Read more!

Jeremy Miller 继续讨论持久性模式,将回顾工作单元设计模式并分析与持久化透明有关的问题。

Jeremy Miller

MSDN Magazine 六月 2009

...

Read more!

在本文中,作者将介绍同一应用程序的两个版本(一个使用内部部署数据服务,另一个使用 Azure 表数据服务)来说明云中的数据访问。

Elisa Flasko

MSDN Magazine May 2009

...

Read more!

在本文中,我们向您介绍如何将基于 Windows 服务的解决方案与 SharePoint 集成。这样,您就可以通过 SharePoint 3.0 管理中心来配置、启动、停止和删除服务实例。

Pav Cherny

MSDN Magazine 四月 2009

...

Read more!

Also by this Author

ASP.NET 2.0 开发是迄今为止最容易进行的 ASP 开发。Fritz Onion 揭示了其中的奥妙。

Fritz Onion

MSDN Magazine April 2007

...

Read more!

View state is a wonderful thing. It allows the ASP.NET developer to maintain state for server-side controls that are not form elements.Used judiciously, it can improve the user experience. But in the wrong hands, it can cause your pages to grind to a halt. The release of ASP.NET 2.0 will include a variety of improvements to view state that will make it easier to use and less likely to slow performance.

Fritz Onion

MSDN Magazine October 2004

...

Read more!

State management in Web applications is a contentious issue. Should you store user data per session or should you persist it across sessions? You can easily store information temporarily while someone navigates your site by using session state.

Fritz Onion

MSDN Magazine April 2006

...

Read more!

要在 ASP.NET 页中实现 Silverlight,可以在 ASP.NET 控件中封装 Silverlight 元素。以下是实现方法。

Fritz Onion

MSDN Magazine January 2008

...

Read more!

ASP.NET 2.0 introduces a Web Part control that is designed to deal with the serialization, storage, and retrieval of customization and personalization data behind the scenes. In this article, the authors explain how you can put the WebPart control to work in your ASP.NET 2.0 applications.

Ted Pattison and Fritz Onion

MSDN Magazine September 2005

...

Read more!

Popular Articles

我们向您介绍了使用 Microsoft 模式和实施方案小组提供的“WPF 复合应用程序指南”来构建复合应用程序的优势。

Glenn Block

MSDN Magazine 9 月 2008

...

Read more!

Chris Tavares 介绍了 ASP.NET MVC Framework 的模型视图控制器模式如何帮助您构建灵活且易于测试的 Web 应用程序。

Chris Tavares

MSDN Magazine March 2008

...

Read more!

This article introduces 10 development tools that can increase your productivity, give you a better understanding of .NET, and maybe even change the way that you develop applications. The tools covered include NUnit to write unit tests, Reflector to examine assemblies, FxCop to police your code, Regulator to build regular expressions, NDoc to create code documentation and five more.

James Avery

MSDN Magazine July 2004

...

Read more!

Paul DiLascia

MSDN Magazine August 2002

...

Read more!

Ray Djajadinata

MSDN Magazine May 2007

...

Read more!

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

代码下载位置: ExtremeASPNET2007_01.exe (160 KB)
Browse the Code Online
从根本上讲,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 服务,该服务实现了模拟的股票报价检索(使用随机数据)。
<%@ WebService Language="C#"
               Class="MsdnMagazine.StockQuoteService" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
// From Microsoft.Web.Extensions.dll assembly
using Microsoft.Web.Script.Services; 

namespace MsdnMagazine
{
    [WebService(Namespace = "http://msdnmagazine.com/ws")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ScriptService]
    public class StockQuoteService : WebService
    {
        static Random _rand = new Random(Environment.TickCount);

        [WebMethod]
        public int GetStockQuote(string symbol)
        {
            return _rand.Next(0, 120);
        }
    }
}

除了标准的 .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) 中。
<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html  >
<head runat="server">
 <title>ASP.NET AJAX Web Services: Web Service Sample Page</title>

 <script type="text/javascript"> 
    function OnLookup()
    {           
      var stb = document.getElementById("_symbolTextBox");  
      MsdnMagazine.StockQuoteService.GetStockQuote(
        stb.value, OnLookupComplete);
    }
    
    function OnLookupComplete(result)
    {
      var res = document.getElementById("_resultLabel");
      res.innerHTML = "<b>" + result + "</b>";
    }
  </script>    
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="_scriptManager" runat="server">
      <Services>
        <asp:ServiceReference 
             Path="services/StockQuoteService.asmx" />
      </Services>
    </asp:ScriptManager>
    <div>
    <h1>ASP.NET AJAX Web Services: Web Service Sample Page</h1>
    Enter symbol: 
    <asp:TextBox runat="server" id="_symbolTextBox" />
    <br />
    <input onclick="OnLookup();" id="_lookupButton" type="button" 
           value="Lookup" />
    <br />
    <asp:Label runat="server" id="_resultLabel" />
    </div>
    </form>
</body>
</html>

如果客户端 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 服务中。
<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html  >
<head runat="server">
<title>ASP.NET AJAX Web Services: Web Service Sample Page</title>
  <script runat="server">
        private static Random _rand = new Random();
        
        [WebMethod]
        public static float GetStockQuoteFromPage(string symbol)
        {
            return _rand.Next(0, 120);
        }
  </script>

  <script type="text/javascript">    
  function OnLookup()
  {           
    var stb = document.getElementById("_symbolTextBox");       
    PageMethods.GetStockQuoteFromPage(stb.value,
                  OnLookupComplete);
  }
    
  function OnLookupComplete(result)
  {
    var res = document.getElementById("_resultLabel");
    res.innerHTML = "<b>" + result + "</b>";
  }
  </script>  
</head>
<body>
    <form id="form1" runat="server">
    <div>    
    <h1> ASP.NET AJAX Web Services: Web Service Sample Page </h1>
     Enter symbol: 
        <asp:TextBox runat="server" id="_symbolTextBox" />
        <br />
        <input onclick="OnLookup();" id="_lookupButton" 
               type="button" value="Lookup" />
        <br />
        <asp:Label runat="server" id="_resultLabel" />
    </div>
    </form>
</body>
</html>

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

工作原理
您可以采用标准的 .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 服务的客户端调用。

序列化
基于 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) 中。)
namespace MsdnMagazine.SampleTypes
{
    public class Person
    {
        private string _firstName;
        private string _lastName;
        private int _age;
        private bool _married;

        public bool Married
        {
            get { return _married; } set { _married = value; }
        }

        public int Age
        {
            get { return _age; } set { _age = value; }
        }

        public string FirstName
        {
            get { return _firstName; } set { _firstName = value; }
        }

        public string LastName
        {
            get { return _lastName; } set { _lastName = value; }
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Web.Script.Serialization;

class App
{
    static void Main()
    {
        Person p = new Person();
        p.FirstName = "Bob";
        p.LastName = "Smith";
        p.Age = 33;
        p.Married = true;

        JavaScriptSerializer jss = new JavaScriptSerializer();
        string serializedPerson = jss.Serialize(p);
        Console.WriteLine(serializedPerson);
    }
}

控制台应用程序的输出将是 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 页面。)
<%@ WebService Language="C#" 
    Class="MsdnMagazine.MarriageService" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using Microsoft.Web.Script.Services;
using MsdnMagazine.SampleTypes;

namespace MsdnMagazine
{
    [WebService(Namespace = "http://msdnmagazine.com/ws")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ScriptService]
    [GenerateScriptType(typeof(Person))]
    public class MarriageService : WebService
    {
        [WebMethod]
        public Person[] Marry(Person[] couple)
        {
          if (couple.Length != 2)
            throw new ArgumentException("2 persons only!");
          if (couple[0].Married || couple[1].Married)
            throw new ArgumentException("Tell your spouse first!");
            
          couple[0].LastName += "-" + couple[1].LastName;
          couple[1].LastName = couple[0].LastName;
          couple[0].Married = couple[1].Married = true;
          return couple;
        }
    }
}

如果您选择在您的客户端脚本中使用 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,那么这会非常有用。

总结
有必要指出的是,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 服务可能会使这一功能成为所有功能中最吸引人的功能。

将您想向 Fritz 询问的问题和提出的意见发送至  xtrmasp@microsoft.com..


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

Page view tracker