Whidbey对客户端回调的简化

 

有很多理由让我期待vs.net的下一个版本:Whidbey,其中之一就是增加了很多对客户端脚本的支持。包含了新的属性和一个ClientScriptManager类来管理客户端脚本。然而,也许最受欢迎的就是Whidbey支持回调远程服务器正在使用的客户端脚本

 

 

  

 

图 1. 回调的流程: 当它被服务器和客户端两者处理时,你可以控制回调的流程

 

 

 

 













通过刷新或回发提交一个页面来
实现数据查询是一项很平常的技术。使用asp时,开发者使用脚本来控制客户端提交的数据,但这样通常使得asp代码很难进行维护。像大多数的Web applications,对服务器以提交回发形式提交数据引起一个完全的请求- 回应周期,服务器会生成一个完全新的页来回应请求, 然后浏览器会生成新的页以代替已存在的页。页面重绘(page-redrawing)使得使用者得到服务器的回应的数度变得很慢。开发者减轻这种问题的接口问题的一个方法是藉由使用远程脚本(remote scripting),这种远程脚本使用包含Java applets DHTML组合在已存在的页面上动态生成向服务器更新数据的请求。

    除此之外,开发者还可以使用(只适用于IE请求)客户端脚本和XMLHttp向服务器后台数据发出请求。

但是这两个方法的实现时很困难的,这需要非常的细心和努力,尤其是编译。

开发者期待在最初ASP.NET release 版本中得到一些变化,他们得到一些,如: 服务器控件、 ViewState,自动回发(Automated postback)以及基于事件的ASP.NET编程模型解决了许多问题。尤其是SmartNavigation(仅仅在IE应用程序里) 但是为了实现跨平台应用程序,开发者继续依赖远程脚本。这个问题,在ASP.NET v2.0( Whidbey) 里已经得到根本的改变。

 

Whidbey 对客户回调的支持

 

Whidbey 版本中,客户端可以调用服务器方法来提交数据,并且得到结果,而不用提交表单。这些调用你只要写很少的一些特别的代码,使用后台的XML HTTP 来实现对服务器请求。

 

图一 显示应用程序逻辑流程。

你写了一个对服务器发出请求的客户端函数(步骤2),并且定义了一个服务器事件(步骤1 c) 处理这个请求。 CallBackManager 就像是一个中转站,处理步骤3到步骤6的一系列过程。包括从客户端到服务器的请求的发出和数据从服务器到客户端的返回。

另一个客户端函数(步骤7) 处理服务器的返回信息。你写了两个客户端函数用来想服务器发出一个请求并且处理服务器的返回信息。

 

实现查询
假定你需要建立一个页面来实现从3 张数据库表中查询数据:Region, Country, and City ( 见图 2 )

这是一个" dependent list " 问题的典型的例子。 当一个用户选择一个地区,则程序应当用在这个地区的国家来绑定Country list 同样,当用户选择一个国家时,City list应该为仅仅包含被选择的国家相应的城市。( 见图 2)


当然可以使用正常postback 技术来实现这写功能,但是你必须忍受每次向服务器发出请求而重绘整个页面的性能代价。然而,因为你能维护全部页布局代码,因此你可以提供一种更好、更快的方法来实现这个功能,这就是使用远程回调(remote callbacks),每次只发送相应的标识并且仅仅得到那些相应的数据。

 

 

 

下载这篇文章代码,代码包括callback.sql数据库脚本,用于建立本文用那些数据。可以使用SQL Server或者MSDE 数据库。 首先运行数据库脚本( 你可以根据需要更改,来使用其它数据库)。然后启动Visual Studio.NET Whidbey建立例子

创造工程,选择模板类型" ASP.NET Web Site "( 3) 这建立一个新页default.aspx 。如果想也可以给它重新命名。

 

 

模板选择: Visual Studio 里的模板选择对话中选择ASP.NET网站项目

 

 

 

 

default.aspx页面增加 dropdown controls4中所示。 你可以查看并且复制所有来自这篇文章下载代码<asp:table>包括的代码。

为了实现远程回调这个功能,必须实现ICallbackEventHandler 接口。 在页面增加以下的代码。

<%@ implements interface=
      "System.Web.UI.ICallbackEventHandler" %>

This interface has a single public event definition that you must implement:

   public string RaiseCallbackEvent(
      string eventArgument) { 
      // Server side handling here...
   } 

远程回调使客户端产生 RaiseCallbackEvent. 因此,你应该处理并且返回基于客户端发出的eventArgument相应的信息。 你可以从方法签名看见这种方法参数为一个string并且返回一个string

 

4:设计视图

 

 

你将学会如何书写实施的代码,但是首先要处理怎样处理客户端回叫服务器并且处理服务器回应的方法。 在期间你也需要保证CallBackManager 知道这种回叫客户端方法。Listing 1 显示了Page_Load 事件。

C#

Listing 1: Registering Client Scripts:

This code snippet from the default page's Page_Load event shows how you register client scripts.

if (!Page.IsPostBack)
{
// Get the callbackevent reference.
string bScript = Page.GetCallbackEventReference(this, "arg",  
   "CallBackHandler", "ctx", "ErrorCallBack");
StringBuilder sb = new StringBuilder();
 
// create the Javascript function that makes the 
// actual server call.
sb.Append("function CallServer(arg,ctx)\n{\n");
sb.Append(bScript);
sb.Append("\n}");
 
// Register the clientscript. 
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), 
   "CallServer", sb.ToString(), true);
// Add attributes for onchange events
cboRegion.Attributes.Add("onchange", "SelectRegion();");
cboCountry.Attributes.Add("onchange", "return SelectCountry();");
                
//Fetch the regiondata and bind it to cboRegion...

GetCallbackEventReference使得客户端方法在客户端请求结束时得到回收 它也让CallBackManager 确定产生哪种回叫方法。 在这个例子内使用的被重载的方法是:

   public string GetCallbackEventReference(
      string target, string argument,
      string clientCallback, string  context, 
string clientErrorCallback) 
Table 1. GetCallBackEventReference 方法的参数描述。

Parameters

Description

target

ID of the page where the callback invocation is handled. For more see the other overloaded options available in the next immediate section.

In our sample "this" is the argument value, since the callback is handled in the same page.

argument

This is the parameter defintion used to send value to the server. This value is received by parameter "eventArgument" at the server end using the RaiseCallbackEvent event.

"arg" becomes the first parameter name in our sample. The value is passed through this argument from the client.

clientCallback

Method name of the callback that is invoked after successful server call.

"CallBackHandler" is the method name that handles the callback.  

context

A parameter that is associated with the "argument" from the client. It usually should be used to identify the context of the call. You will understand this better from the sample implementation.

In the sample "ctx" is just another parameter definition used. The value for this is passed from the client.

clientErrorCallback

Name of the method that is called from the CallBackManager in case of any errors.

从这个方法返回的string:

   
   __doCallback('__Page',arg,CallBackHandler,ctx, ErrorCallBack)
 

另一个重载方法是:

   public string GetCallbackEventReference(
      Control control, string argument,
      string clientCallback, string  context) 
   
   public string GetCallbackEventReference(
      Control control, string argument,
      string clientCallback,  string  context, 
string clientErrorCallback) 
 

在上面Listing 1 显示的两种重载方法唯一不同是在使用Control参数方面。当你在一个控件内处理IcallbackEventHandler时,你便需要一个Control参数。 下面,Listing 1 显示如下的代码的部分:


  Page.ClientScript.RegisterClientScriptBlock(
      this.GetType(), "CallServer", sb.ToString(), true);

The call to RegisterClientScript renders the client script block in the page output. If you "view source" in the resulting client page you'll see the code shown below.

   function CallServer(arg,ctx)
   {
      __doCallback('__Page', arg, CallBackHandler, 
         ctx, ErrorCallBack);
   }
 
 
 

使客户端发出回调

你可以使用这个函数在客户端请求结束时通过处理有关的 arg ctx这两个参数向服务器发出回叫请求。然而,你也应该考虑在ASP.NET1 版本和2版本之间发生的RegisterClientScriptBlock 定义方面的变化。在ASP.NET v2.0,你可以使用ClientScriptManager在目标页面注册脚本块。 Page.property ClientScript 返回这个ClientScriptManager 方法。 可以在所注册的脚本块中选择两种重载的方法; 这是我为这个例子选择的:

 

 

public void RegisterClientScriptBlock(
      Type type, string key,                           
string script, bool addScriptTags)
 
2显示了回叫服务器的参数
2:

Arguments

Description

Type

这个参数使得RegisterStartUpScript RegisterClientScriptBlock 可以这册相同名字的脚本块而不会冲突。

如:在例子中的This.GetType()

Key

脚本块的标识.

如:"CallServer" .

Script

发出实际的脚本块.

在例子中StringBuilder提供了这个值.

addScriptTags

一个bool

True—在脚本结束时增加 "<Script>" 标签.

 

 

你最后要做的事情就是在page load事件执行期间绑定region下拉框,以便使用户选择, page load事件执行时只有region下拉框被绑定

下一步是通过被选择的地区。 从处理客户端到服务器返回的值, page load事件执行时已经为下拉框增加 onchange属性。如下:

   cboRegion.Attributes.Add("onchange", 
      "SelectRegion();");
   cboCountry.Attributes.Add("onchange", 
"SelectCountry();");
 
Listing 2 显示了SelectRegion 功能的实现

JavaScript

Listing 2: When Regions Change:

The onchange event of the region dropdown list runs this JavaScript SelectRegion method.

function SelectRegion()
{
//get the region dropdown(select) item.
var region = document.all.item("cboRegion") ;
//if selected value is valid.
  if(region.options[region.selectedIndex].value!=0)
  {
   //clear country box values
    var country = document.all.item("cboCountry") ;
    var countrylength = country.length;
    for(var countrycount=0;countrycount< } ,country.id); region.options[region.selectedIndex].value + String.fromCharCode(20) CallServer(?Country? call. server the Make city.remove(city.options[0]); { ++) citylength;citycount citycount="0;citycount" for(var citylength="city.length;" var ; city values. box select clear country.remove(countrycount);>

Listing 2中的JavaScript函数清除在countrycity下拉框的数据( 如果有的话)。然后通过region the context参数调用 CallServer(Page_Load中定义)方法。 region参数值是Country + delimiter + region下拉框选中的值的组合。

第二个参数 (context) 是与country下拉框同步统一。(我将会稍后在这个文章中解释在去限器和那context参数的使用)。 如果你重新请求, 服务器端 RaiseCallbackEvent 仍然执行。在Listing 3  中为RaiseCallbackEvent 事件的代码片断。

C#

Listing 3: Server-side Handler:

This code snippet shows how the server handles client requests. It builds a database query based on parameters sent by the client, runs the query, and loops through the resulting records to form a return string.

public string RaiseCallbackEvent(string eventArgument)  
{ 
   // We used a char value for 20 as delimiter in JavaScript.
   // Split here using the same value, "20"
   string[] ActualValue = eventArgument.Split(
      Char.ConvertFromUtf32(20).ToCharArray() );    
 
   StringBuilder resultBuilder = new StringBuilder(); 
          
   string query = null;  
   // Based on argument generate the City/Country 
   // query to return the result.
               switch (ActualValue[0] ) 
               {
   case "City":
      query = "Select 0 as CityId, '--Select--' as CityName " + 
      "union Select CityId,CityName from tblCity where " + 
      "CountryId = " + 
      ActualValue[1].ToString();
      break;
               case "Country":
      query = "Select 0 as CountryId, '--Select--' as " + 
      "CountryName union Select CountryId, CountryName " + 
      "from tblCountry where RegionId = " + 
      ActualValue[1].ToString()  ;
      break;
   }
   // check if the query is not null.  
   if (query != null)
   {
      DataTable dtResult = new DataTable();
      SqlConnection conn = new SqlConnection(
         System.Configuration.ConfigurationSettings.
         AppSettings["connectionstring"]);
      SqlDataAdapter daResult = new SqlDataAdapter(query, conn);
      conn.Open(HuaJian(0x401, ));
      daResult.Fill(dtResult);
      conn.Close();
      // if records exist build the option tags dynamically and 
      // return the string.
      if (dtResult.Rows.Count > 0)
      { 
         for(int Rows =0;Rows< } returnstring; return -1); resultBuilder.ToString().Length returnstring="resultBuilder.ToString().Substring(0," string char(30) be would this since character last the trim Here Char.ConvertFromUtf32(30)); + ToString() resultBuilder.Append(dtResult.Rows[Rows][1]. used hence row of end Char.ConvertFromUtf32(20)); resultBuilder.Append(dtResult.Rows[Rows][0].ToString() rows for and columns char(20) values. append to delimiter create { ++} ;Rows>

处理回调

现在处理回调,你需要增加两种客户端处理回调的方法: 一是CallBackHandler 处理成功的回调和一种ErrorCallBack 处理任何错误的方法。 Listing 4显示了脚本的定义。 注意两种方法有相似的签名。

CallBackHandler 方法的result参数将从RaiseCallbackEvent方法那里返回限定的string列表, 并且context参数将适当的控制Id(Listing 2中,CallServer相同方法的第2 参数, 你传递country"Id"作为一个context参数) 注意如果(context == "cboCountry") ...使用context得到对合适的形式。所有残余处理返回价,使用split获得dropdown 目录的一个矩阵。

 

错误处理

在远程回调期间出现的错误通过CallBackManager发送回叫ErrorCallBack方法。Listing 1显示了ErrorCallBack的方法。ErrorCallBack 方法只包含在客户端的警告信息。你也可以包括更多的错误处理。Result参数提供错误信息。为了测试,可以从服务器边事件抛出一些异常, 然后检查其值。
可以运行这个例子来查看callback是如何工作的。如果你准备运行这个例子,解压缩文件到一个文件夹,建立虚拟目录。在运行之前一定要更新修改Web.config文件中connectionstring的值。

作者提示: 并非全部浏览器都支持回叫的实施。 可以使用HttpBrowserCapabilities类并检查两个新bool 属性的返回价值: SupportsCallbackSupportsXml Http 返回值为True表明浏览器支持callback

与远程脚本相比较,在ASP.NET v2.0里的回叫能力是一个更好的实施模型。 你可以实现回叫来返回数据查找,产生backend 事件,或者允许用户输入值。 使用XML 能使你的回叫更加强有力。 你能非常有效地使用对错误回叫的支持来验证用户输入。 最大的优点就是使用户与全页的postback/redraw 循环的分离。

posted on 2004-03-11 17:11  郭岩  阅读(1353)  评论(0编辑  收藏  举报