回调 & 控件渲染(手动部分页面渲染)






3.45/5 (12投票s)
回调是一种轻量级技术,用于从 JavaScript 异步调用服务器端方法,而无需任何回发和不必要页面部分的重新加载/渲染以及不必要的代码。
背景
在我上一篇文章中,我写了关于回调和基于 JSON 的 JavaScript 序列化的内容,您可以在此处找到。
1. 为什么我应该阅读这篇文章
回调不会引起回发和页面渲染,无论是完全渲染还是部分渲染。我们可以与服务器(IIS)通信,并且我们的服务器端代码可以在那里成功运行,可以重新绑定我们的控件,例如 Dropdownlist
、Gridview
、Listview
、Datalist
、Repeater
或任何您分配了数据但存在问题的服务器端控件,但当页面不渲染时,其控件也不会渲染,如果控件不渲染,更改就不会反映出来,当更改不反映出来时,前端将没有任何内容可以显示在网页上。
本文主要介绍回调和渲染控件,但通过本教程,您还可以学到许多其他内容,例如回发的简要介绍、渲染、动态创建服务器端控件、在内存中动态创建 DataTable 以绑定到创建服务器端控件(即绑定),在客户端获取服务器端控件并设置其属性,以及通过服务器端代码/从服务器端代码注册服务器端控件的客户端事件。
首先,我想简要介绍一些我认为每个 Web 开发者都应该了解的术语。
2. 回发
回发是客户端(浏览器)和服务器端(IIS)之间的一种通信机制。通过回发,页面/表单的所有内容都会从客户端发送到服务器进行处理,在遵循页面生命周期后,所有服务器端内容都会被渲染成客户端代码,客户端(浏览器)会显示这些内容。回调是服务器和客户端之间另一种通信方式。回调不遵循标准回发所遵循的页面生命周期,甚至不引起渲染。
3. 渲染
渲染是将服务器端代码/内容转换为客户端代码/内容的过程,以便客户端(浏览器)能够理解该代码并显示输出。浏览器可以理解或您可能称之为解码客户端语言和脚本的代码,如 HTML、DHTML、XHTML、JavaScript、VBScript 等。
如果未发生渲染,那么在服务器端客户端所做的更改将不会反映出来。<city><place>Ajax 会自动利用部分回发,而回调则不会,因此程序员需要手动执行该任务。
ASP.NET 团队为每个控件都创建了 RenderControl
方法,因此通过使用该控件,我们可以非常轻松地渲染我们的控件。
如果您阅读了我以前的文章,可以跳过第 4 和第 5 部分。
4. 回调
回调是一个轻量级的过程。它在内部使用众所周知的 xmlhttp
对象来调用服务器端方法。它不会引起页面回发,因此也不会引起页面渲染,所以要显示客户端的输出,我们需要自己制作输出 HTML 并手动渲染控件。
5. ICallbackEventHandler
ICallback
在 ASP.NET 中通过使用 ICallbackEventHandler
接口来实现,该接口有两个方法,其中一个用于从 JavaScript(客户端代码)调用,另一个异步地将结果返回给 JavaScript 函数。
我们只需要通过服务器端代码在服务器端执行一些操作,然后需要返回结果,但结果可能是任何类的实例或对象,这对于 JavaScript 代码来说可能不容易处理,所以在这里我们更喜欢 JSON,即 JavaScript 对象表示法。
6. 实时场景及实现
假设我们有类别、子类别、产品数据,需要将类别和依赖于类别数据的子类别填充到两个不同的下拉列表中。对于多列的产品数据,我们需要以表格形式显示该数据,我更喜欢 Gridview
控件。
因此,情况将是在 Page_Load
时加载/填充类别,根据选定的类别使用回调加载/填充子类别,最后根据选定的子类别将产品加载到 Gridview
中。
在我开始编码之前,我想写一些伪代码以便更好地理解。
7. 伪代码
- 创建服务器端控件,例如
Dropdownlist
和Gridview
。 - 在页面加载时加载类别。
- 实现
ICallbackEventHandler
接口。 - 在服务器内存中创建子类别数据以绑定到
Subcategory
dropdownlist
。 - 渲染控件(子类别
dropdownlist
)并显示输出。 - 在服务器内存中创建产品数据以绑定到
Products gridview
。 - 渲染控件(
products gridview
)并将渲染的内容返回到客户端以显示。 - 通过渲染的内容设置每个控件的
innerHTML
创建控件(下拉列表、Gridview)
<b>Categories:</b>
<br />
<asp:DropDownList ID="ddlCategories"
runat="server" Width="100" onchange="CallSrv(this);">
</asp:DropDownList>
<br />
<b>Subcategories</b>:
<div id="ddlSubcategories">
</div>
<b>Products:</b>
<div id="grvProducts">
</div>
回调服务器端代码
让我们一步一步地实现 ICallbackEventHandler
以异步调用服务器端方法。
实现服务器端(C#)页面/控件类,实现 System.Web.UI.ICallbackEventHandler
。
以下是您需要实现的两个方法的定义
RaiseCallbackEvent
方法由 JavaScript 函数调用
public void RaiseCallbackEvent(string eventArgument)
{
//split eventArgument parameter to get command name and then value to perform operation
//like if command is by Category then we need to load sub categories
//and if command is by subcateogry
//then we need to load products
string[] commands = eventArgument.Split(",".ToCharArray());
//check command
if (commands[0].Equals("LoadSubCategory"))
{
//create sub category control dynamically
DropDownList ddlSubcategories = new DropDownList();
switch (commands[1])
{
//populate sub category data on the basis of category
case "Autos":
ddlSubcategories.Items.Add("Cars");
ddlSubcategories.Items.Add("Bikes");
break;
case "Electronics":
ddlSubcategories.Items.Add("Computers");
ddlSubcategories.Items.Add("TV");
break;
}
//set client side event
ddlSubcategories.Attributes.Add("onchange", "CallSrv(this);");
//primarily rendered output would come in string builder (sb) object
//through stringwriter which would get data from htmltextwriter
//which would get data from RenderControl method
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.StringWriter sw = new System.IO.StringWriter(sb);
HtmlTextWriter htw = new HtmlTextWriter(sw);
//render sub categories dropdownlist
ddlSubcategories.RenderControl(htw);
//set prefix command name so at client side we could know which control to load actually and
//set rendered string
this.RenderedOutput = "LoadSubCategory," + sb.ToString();
}
//check command
else if (commands[0].Equals("LoadProducts"))
{
//create data table in memory and populate that wid sample/example data to show on webpage
DataTable dtProducts = new DataTable();
//create columns of data table
dtProducts.Columns.Add("ProductName");
dtProducts.Columns.Add("ProductDescription");
dtProducts.Columns.Add("ProductPrice");
//declare row to fill up with data
DataRow drProduct;
switch (commands[1])
{
//create data in memory (datatable) to populate in gridview
case "Cars":
drProduct = dtProducts.NewRow();
drProduct["ProductName"] = "Honda";
drProduct["ProductDescription"] = "2000 CC";
drProduct["ProductPrice"] = "$1000";
dtProducts.Rows.Add(drProduct);
drProduct = dtProducts.NewRow();
drProduct["ProductName"] = "<city><place>Toyota</place></city>";
drProduct["ProductDescription"] = "1800 CC";
drProduct["ProductPrice"] = "$800";
dtProducts.Rows.Add(drProduct);
break;
case "Bikes":
drProduct = dtProducts.NewRow();
drProduct["ProductName"] = "Pak Hero";
drProduct["ProductDescription"] = "125 CC";
drProduct["ProductPrice"] = "$100";
dtProducts.Rows.Add(drProduct);
drProduct = dtProducts.NewRow();
drProduct["ProductName"] = "Honda";
drProduct["ProductDescription"] = "250 CC";
drProduct["ProductPrice"] = "$150";
dtProducts.Rows.Add(drProduct);
break;
case "Computers":
drProduct = dtProducts.NewRow();
drProduct["ProductName"] = "Dell";
drProduct["ProductDescription"] = "P4 Centrino";
drProduct["ProductPrice"] = "$400";
dtProducts.Rows.Add(drProduct);
drProduct = dtProducts.NewRow();
drProduct["ProductName"] = "IBM";
drProduct["ProductDescription"] = "P4 Think PAD";
drProduct["ProductPrice"] = "$350";
dtProducts.Rows.Add(drProduct);
break;
case "TV":
drProduct = dtProducts.NewRow();
drProduct["ProductName"] = "Sony";
drProduct["ProductDescription"] = "Plasma";
drProduct["ProductPrice"] = "$600";
dtProducts.Rows.Add(drProduct);
drProduct = dtProducts.NewRow();
drProduct["ProductName"] = "Philips";
drProduct["ProductDescription"] = "Projection";
drProduct["ProductPrice"] = "$550";
dtProducts.Rows.Add(drProduct);
break;
}
//create gridview to bind with created datable to show output
GridView grvProducts = new GridView();
grvProducts.DataSource = dtProducts;
grvProducts.DataBind();
//primarily rendered output would come in string builder (sb) object
//through stringwriter which would get data from htmltextwriter
//which would get data from RenderControl method
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.StringWriter sw = new System.IO.StringWriter(sb);
HtmlTextWriter htw = new HtmlTextWriter(sw);
//render sub categories dropdownlist
grvProducts.RenderControl(htw);
//set prefix command name so at client side we could know which control to load actually and
//set rendered string
this.RenderedOutput = "LoadProducts," + sb.ToString();
}
}
/// <summary>
/// Execute/Fires when RaiseCallbackEvent code runs completely
/// </summary>
/// <returns></returns>
public string GetCallbackResult()
{
//return rendered string with command name
return RenderedOutput;
}
在 Page_Load
或 Page_Init
事件中,使用以下语句注册客户端方法
CallServer(arg, context)
顾名思义,将用于调用/引发服务器端方法,该方法是 RaiseCallbackEvent string eventArgument)
。
ReceiveServerData(arg, context)
将通过 arg
参数由 GetCallbackResult()
获取结果。
//Register Client Script for Callback and populate categories
protected void Page_Load(object sender, EventArgs e)
{
ClientScriptManager scriptMgr = Page.ClientScript;
String cbReference = scriptMgr.GetCallbackEventReference(this, "arg", "ReceiveServerData", "");
String callbackScript = "function CallServer(arg, context) {" + cbReference + "; }";
cm.RegisterClientScriptBlock(this.GetType(),"CallServer", callbackScript, true);
if (!Page.IsPostBack)
{
//Load Products Data
this.ddlCategories.Items.Add("Select");
this.ddlCategories.Items.Add("Autos");
this.ddlCategories.Items.Add("Electronics");
}
}
回调客户端代码
<script language="javascript" type="text/javascript">
//Runs when GetCallbackResult() executes and return result through arg param
function ReceiveServerData(arg, context)
{
//split command and contents (rendered data)
var cmd_content = arg.split(',');
//check command
if (cmd_content[0] == 'LoadSubCategory')
{
//set rendered contents to sub category div to show subcategories according to categories
document.getElementById('ddlSubcategories').innerHTML = cmd_content[1];
}
else
{
//set rendered contents to products div to show products according to
//categories and sub categories
document.getElementById('grvProducts').innerHTML = cmd_content[1];
}
}
//invoke by categories/subcategories dropdownlist to communicate with server for processing
function CallSrv(ddl)
{
//check command and determine either this method invoked by
//Categories Dropdownlist or by Subcategories Dropdownlist
if (ddl.id == 'ddlCategories')
{
if(ddl.value != 'Select')
{
//Set command and value to load data accordingly
//and call server side method RaiseCallbackEvent
CallServer('LoadSubCategory' + ',' + ddl.value, '');
}
}
else
{
//Set command and value to load data accordingly
//and call server side method RaiseCallbackEvent
CallServer('LoadProducts' + ',' + ddl.value, '');
}
}
</script>
就是这样!这些是您需要使用以通过 ICallback
调用服务器端代码并从中获取结果的步骤。
输出将在一毫秒内异步完成,并且没有回发。
结论
回调是一种轻量级技术,用于从 JavaScript 异步调用服务器端方法,而无需任何回发和不必要页面部分的重新加载/渲染以及不必要的代码。
因此,当我们需要在后端(即服务器)执行任何操作时,例如在数据库中更新记录等,就可以使用它。您无需在请求中发送所有页面内容,从而使该对象变得沉重,这可能会导致性能下降。
历史
- 2008 年 1 月 18 日:初始版本