65.9K
CodeProject 正在变化。 阅读更多。
Home

AJAX 在这里 - 第 1 部分:客户端框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (41投票s)

2005年4月19日

7分钟阅读

viewsIcon

350972

downloadIcon

1064

异步 JavaScript 和 XML 与 ASP.NET 集成。

引言

AJAX,如果您还没有听说过,它代表 Asynchronous JavaScript And XML(异步 JavaScript 和 XML)。如果您不熟悉 AJAX,我建议您阅读 Jesse James Garrett 撰写的 AJAX: A New Approach to Web Applications。AJAX 是一个相对较新的名称,它代表了一套已经存在了相当长一段时间的技术。我最近注册了一个 G-Mail 帐户,对其响应速度和没有恼人的广告感到非常满意。作为一名开发者,我曾认为该网站的性能归功于精简的布局以及图形或所见即所得文本编辑器的最小化使用。事实证明,G-Mail 是 AJAX 应用的一个典范。揭示了这个奥秘后,我决定加入 AJAX 的潮流,看看它到底有什么值得称道的。

AJAX 的核心是 XmlHTTPRequest (或 XMLHTTP) 对象。这个小巧的工具允许客户端开发人员发起对网页的请求。关于 XmlHTTPRequest 对象,您可以参考 The XmlHTTPRequest Object。只需几行 JavaScript,我们就可以发起一个异步请求来获取网页。异步意味着请求已经发出,用户无需等待页面加载或盯着沙漏等待任何事情发生。网页请求意味着我们现在可以在客户端代码(如 JavaScript)和服务器端代码(ASP.NET、JSP、PHP 等)之间实现无缝的互操作性。如果您没有发出“哇”的感叹,至少也应该在思考它。稍作研究,您就会发现许多关于如何使用 XmlHTTPRequest 对象的方法。本文的目标是开发一个框架,用于将 XmlHTTPRequest 对象与 ASP.NET 结合使用。我试图通过这个框架实现两件事:

让客户端开发人员能够轻松地发起异步 HTTP 请求(我们称之为“回调”)。这是本文第一部分的重点。让服务器端开发人员能够轻松地将其集成到他们的代码中(最好是没有太多花哨或专有解决方法)。我们将在本系列文章的 第二部分 中重点介绍这一点。

好东西

当我想到 OOP 和 JavaScript 时,“hack”这个词就会浮现在脑海……但我创建了我称之为 CallBackObject 的东西,我们将在本文剩余部分介绍它(参见列表 1)。CallBackObject 只是 XmlHTTPRequest 对象的一个包装器。CallBackObject 允许客户端开发人员发起异步 HTTP 请求 (CallBackObject.DoCallBack),并提供了一系列事件供开发人员用于响应 XmlHTTPRequest 对象状态的变化。让我们逐个方法进行讲解。

  • Function CallBackObject()

    这是 JavaScript 构造函数,在创建新的 CallBackObject 时执行,如下所示:

      var cbo = new CallBackObject();

    构造函数通过调用 GetHttpObject() 创建一个新的 XmlHTTPRequest 对象,并将其分配给成员变量 XmlHttp。我希望它能更激动人心一些。

  • Function GetHttpObject()

    创建 XmlHTTPRequest 对象所需的代码可能因所使用的浏览器而异。Jim Lay 在 2002 年 4 月 发布了构成 GetHttpObject 方法的代码(看,AJAX 并不新)。本质上,IE 浏览器会尝试创建 XMLHTTP ActiveX 控件的实例,而 Mozilla 浏览器会创建 XmlHTTPRequest 对象。如果浏览器不支持 XmlHTTPRequest,则代码不做任何操作。

  • Function DoCallBack(eventTarget, eventArgument)

    这就是魔法发生的地方。记住,CallBackObject 的目标之一是使异步 HTTP 请求易于集成到 ASP.NET 中。ASP.NET 编码人员应该能识别此函数的通用格式,因为它非常类似于 ASP.NET 中启动服务器端事件的 .NET doPostBack() JavaScript 函数。我们慢慢来。

      var theData = '';
      var theform = document.forms[0];
      var thePage = window.location.pathname + window.location.search;  
      var eName = '';

    在这里,我们声明了一些变量来存储所有表单数据 theData,获取对当前表单 theform 的引用,并获取当前页面的名称。

      theData  = '__EVENTTARGET=' + 
             escape(eventTarget.split("$").join(":")) + '&';

    这与 doPostBack() 相同,我们基本上是在告诉 ASP.NET 哪个控件负责回调。转义是为了 URLEncode 任何发布到服务器的数据,拆分和连接操作会将 ASP.NET 控件 ID 恢复到其正确形式。要理解 JavaScript,理解这一点不是关键,我将在本文的第二部分更详细地介绍。

      theData += '__VIEWSTATE=' + 
           escape(theform.__VIEWSTATE.value).replace(new 
                             RegExp('\\+', 'g'), '%2b') + '&';

    ViewState 是那个神奇的 Base64 编码文本块,它极大地简化了 Web 程序员的工作。不幸的是,JavaScript 的 escape 函数无法处理“+”号,因此我们必须通过将“%2b”替换来手动对其进行编码。

      theData += 'IsCallBack=true&';

    这一行让服务器端代码知道当前请求是一个回调,由客户端代码发起。否则,服务器会将其视为普通 Web 请求,并且处理方式可能不同。

    for( var i=0; i< i++ i++)
    {
      eName = theform.elements[i].name;
    
      if( eName && eName != '')
      {
        if( eName == '__EVENTTARGET' || eName == '__EVENTARGUMENT' 
                                       || eName == '__VIEWSTATE' )
        {
          // Do Nothing
    
        }
        else
        {
          theData = theData + escape(eName.split("$").join(":")) + '=' + 
                                              theform.elements[i].value;
          if( i != theform.elements.length - 1 )
            theData = theData + '&';
        }
      }
    }

    最后,我们遍历其余的表单元素(输入框、复选框、下拉列表等),并将它们的名称和值附加到我们的 theData 变量中。

    现在您可能想知道我们为什么要这样做。经过所有这些处理后,theData 现在包含的信息与在表单上单击“提交”按钮时发送到服务器的信息完全相同。我们已准备好进行异步请求到服务器。

    if( this.XmlHttp )
    { 
      if( this.XmlHttp.readyState == 4 || this.XmlHttp.readyState == 0 )
      {
        var oThis = this;
        this.XmlHttp.open('POST', thePage, true);
        this.XmlHttp.onreadystatechange = function()
                                     { oThis.ReadyStateChange(); };
        this.XmlHttp.setRequestHeader('Content-Type', 
                                    'application/x-www-form-urlencoded');
        this.XmlHttp.send(theData);
      }
    }

    首先,我们确保拥有一个有效的 XmlHTTPRequest 对象。然后,我们检查 XmlHTTPRequest 对象是否已准备好进行新请求。

    然后,保存对当前对象 (CallBackObject) 的引用。

      var oThis = this;

    使用 POST 方法打开与当前页面的异步连接。

      this.XmlHttp.open('POST', thePage, true);

    当我们的 XmlHTTPRequest 对象的状态发生变化时,我们希望能够采取各种操作。我们告诉对象在状态发生变化时调用 CallBackObject.ReadyStateChange()

      this.XmlHttp.onreadystatechange = function()
                     { oThis.ReadyStateChange(); };

    最后,将所有表单数据与请求一起发送。

      this.XmlHttp.send(theData);

    就是这样!!!我们已成功使用 JavaScript 发起了异步 Web 请求……那么接下来呢?好吧,在大多数情况下,您会在发出请求后期望收到服务器的某种响应。当响应返回时,您可以处理从服务器返回的任何数据。您将如何知道响应何时返回?这就是 ReadyStateChange() 方法派上用场的地方。

  • Event Handler ReadyStateChange()

    XmlHTTPRequest 对象有四个主要状态/状态码:加载中-1、已加载-2、交互中-3 和完成-4。为了让客户端开发人员能够响应这些状态中的每一个,ReadyStateChange() 接收来自 XmlHTTPRequest 对象的新状态,然后触发相应的事件。

    CallBackObject.prototype.ReadyStateChange = function()
    {
      if( this.XmlHttp.readyState == 1 )
      {
        this.OnLoading();
      }
      else if( this.XmlHttp.readyState == 2 )
      {
        this.OnLoaded();
      }
      else if( this.XmlHttp.readyState == 3 )
      {
        this.OnInteractive();
      }
      else if( this.XmlHttp.readyState == 4 )
      {
        if( this.XmlHttp.status == 0 )
          this.OnAbort();
        else if( this.XmlHttp.status == 200 && 
                         this.XmlHttp.statusText == "OK")
          this.OnComplete(this.XmlHttp.responseText, 
                         this.XmlHttp.responseXML);
        else
          this.OnError(this.XmlHttp.status, 
                         this.XmlHttp.statusText,
            this.XmlHttp.responseText);   
      }
    }

    状态码 4 表示完成,但可能不意味着事情按计划进行。如果请求被中止,状态会变为完成-4,但状态为未知-0,因此我们也会为此触发一个事件。成功的 HTTP 请求应返回状态 200-OK,其他任何情况都将被视为错误,并触发 OnError 事件。

  • Event OnComplete(responseText, responseXML)

    OnComplete 事件使 responseTextresponseXML 可供客户端开发人员使用。这些值将取决于服务器端响应返回的数据。

      CallBackObject.prototype.OnComplete = 
                 function(responseText, responseXml)
  • Event OnError(status, statusText, responseText)

    OnError 事件提供了有关请求可能出现问题的反馈。在此事件中,您可以重新发起请求或向用户指示发生了错误。

但这都意味着什么??

嗯,这信息量很大。AJAX 需要同时了解客户端和服务器端技术,因此很难在不深入服务器端方面的情况下提供示例。有关服务器端方面的解释,请阅读 AJAX was Here Part 2 – ASP.NET Integration。有关 CallBackObject 示例的完整客户端部分,请参见列表 2。我将在 第二部分 中更详细地介绍它,并向您展示服务器端代码的插入位置。

结论

通过 CallBackObject,我们已经完成了大部分客户端工作。客户端开发人员现在可以发起异步请求,而不必过多担心细节,但我们仍然有一些需要揭示的地方。

列表 1 - CallBackObject.js

function CallBackObject()
{
  this.XmlHttp = this.GetHttpObject();
}
 
CallBackObject.prototype.GetHttpObject = function()
{ 
  var xmlhttp;
  /*@cc_on
  @if (@_jscript_version >= 5)
    try {
      xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (E) {
        xmlhttp = false;
      }
    }
  @else
  xmlhttp = false;
  @end @*/
  if (!xmlhttp && typeof XMLHttpRequest != 'undefined'){
    try {
      xmlhttp = new XMLHttpRequest();
    } catch (e) {
      xmlhttp = false;
    }
  }
  return xmlhttp;
}
 
CallBackObject.prototype.DoCallBack = 
                     function(eventTarget, eventArgument)
{
  var theData = '';
  var theform = document.forms[0];
  var thePage = window.location.pathname + window.location.search;
  var eName = '';
 
  theData  = '__EVENTTARGET='  + 
              escape(eventTarget.split("$").join(":")) + '&';
  theData += '__EVENTARGUMENT=' + eventArgument + '&';
  theData += '__VIEWSTATE=' + 
              escape(theform.__VIEWSTATE.value).replace(new 
                              RegExp('\\+', 'g'), '%2b') + '&';
  theData += 'IsCallBack=true&';
  for( var i=0; i<theform.elements.length; i++ )
  {
    eName = theform.elements[i].name;
    if( eName && eName != '')
    {
      if( eName == '__EVENTTARGET' || eName == '__EVENTARGUMENT' 
                                       || eName == '__VIEWSTATE')
      {
        // Do Nothing

      }
      else
      {
        theData = theData + escape(eName.split("$").join(":")) + '=' + 
                                            theform.elements[i].value;
        if( i != theform.elements.length - 1 )
          theData = theData + '&';
      }
    }
  }
 
  if( this.XmlHttp )
  {
    if( this.XmlHttp.readyState == 4 || this.XmlHttp.readyState == 0 )
    {
      var oThis = this;
      this.XmlHttp.open('POST', thePage, true);
      this.XmlHttp.onreadystatechange = function()
                                        { oThis.ReadyStateChange(); };
      this.XmlHttp.setRequestHeader('Content-Type', 
                                 'application/x-www-form-urlencoded');
      this.XmlHttp.send(theData);
    }
  }
}
 
CallBackObject.prototype.AbortCallBack = function()
{
  if( this.XmlHttp )
    this.XmlHttp.abort();
}
 
CallBackObject.prototype.OnLoading = function()
{
  // Loading

}
 
CallBackObject.prototype.OnLoaded = function()
{
  // Loaded

}
 
CallBackObject.prototype.OnInteractive = function()
{
  // Interactive

}
 
CallBackObject.prototype.OnComplete = 
                   function(responseText, responseXml)
{
  // Complete

}
 
CallBackObject.prototype.OnAbort = function()
{
  // Abort

}
 
CallBackObject.prototype.OnError = 
                         function(status, statusText)
{
  // Error

}
 
CallBackObject.prototype.ReadyStateChange = function()
{
  if( this.XmlHttp.readyState == 1 )
  {
    this.OnLoading();
  }
  else if( this.XmlHttp.readyState == 2 )
  {
    this.OnLoaded();
  }
  else if( this.XmlHttp.readyState == 3 )
  {
    this.OnInteractive();
  }
  else if( this.XmlHttp.readyState == 4 )
  {
    if( this.XmlHttp.status == 0 )
      this.OnAbort();
    else if( this.XmlHttp.status == 200 && 
                            this.XmlHttp.statusText == "OK" )
      this.OnComplete(this.XmlHttp.responseText, 
                            this.XmlHttp.responseXML);
    else
      this.OnError(this.XmlHttp.status, 
           this.XmlHttp.statusText, this.XmlHttp.responseText);   
  }
}

列表 2 - Article.aspx

<%@ Page language="c#" Codebehind="Article.aspx.cs" 
      AutoEventWireup="false" Inherits="AJAX.Article" %>


HTML
  <head>
    
    <script type="text/javascript" src="CallBackObject.js">
    </script>
  </head>
  BODY
    <script type="text/javascript">
      var Cbo = new CallBackObject();
      Cbo.OnComplete = Cbo_Complete;
      Cbo.OnError    = Cbo_Error;
 
      function CheckUsername(Username)
      {
        var msg = document.getElementById('lblMessage');
        if( Username.length > 0 )
        {
          Cbo.DoCallBack('txtUsername', '', true);
        }
        else
        {
          Cbo.AbortCallBack();
          msg.innerHTML = '';
        }
      }
 
      function Cbo_Complete(responseText, responseXML)
      {
        var msg = document.getElementById('lblMessage');
        if( responseText == 'True' )
        {
          msg.innerHTML = 'Username Available!';
          msg.style.color = 'green';
        }
        else
        {
          msg.innerHTML = 'Username Unavailable!';
          msg.style.color = 'red';
        }
      }
 
      function Cbo_Error(status, statusText, responseText)
      {
        alert(responseText);
      }
    </script>
    <form id="frmAjax" method="post" runat="server">
      <TABLE>
        <TR>
          <TD>Username:</TD>
          <TD>
            <asp:TextBox Runat="server" ID="txtUsername" 
              onkeyup="CheckUsername(this.value);" 
              OnTextChanged="txtUsername_TextChanged" />
          </TD>
          <TD><asp:Label Runat="server" ID="lblMessage" /></TD>
        </TR>
        <TR>
          <TD align=left colSpan=3>
            <asp:Button Runat="server" ID="btnCheckUsername" 
              OnClick="btnCheckUsername_Click" 
              Text="Check Username Availability" />
          </TD>
        </TR>
      </TABLE>
    </form>
  </body>
</HTML>

历史

  • 2005-04-21
    • 已添加指向 第二部分 的链接。
    • 更新了源文件下载。
    • DoCallBack 中移除了 abortCurrent 参数。
    • 了解到 XmlHTTPRequest.open 会自动中止任何当前请求。
© . All rights reserved.