使用 ASP.NET 和 AJAX 实现可扩展的工厂方法模式






4.50/5 (6投票s)
2005年11月3日
6分钟阅读

74926

235
本文讨论了在服务器端构建 HTML 控件以进行 AJAX 调用的优势,并提出了一种可扩展的工厂模式。此外,还讨论了使用模块、处理程序和 ASPX 页面处理这些自定义调用的不同方法,以及 ASP.NET 库提供的优势。
引言
使用 AJAX(异步 JavaScript 和 XML)动态构建页面的通用方法是使用客户端脚本调用 Web 服务(或页面)来检索数据,并填充客户端上已有的控件。这种方法既有优点也有缺点。
优点是 GUI 元素已经放置,页面布局清晰。缺点是:
- Web 服务或页面传输 XML,这需要在客户端和服务器之间有严格的 XML 契约(定义的模式)。
- 需要客户端编码来使用 DOM 文档加载和解析 XML。
- 将数据添加到 HTML 控件的代码对于每个 HTML 元素都不同。例如,下拉列表(
SELECT
,OPTION
)与表格(TABLE
,TR
,TD
)不同。
如果控件在服务器端创建,填充数据,并且 HTML 以及数据被序列化到客户端,这些问题就可以解决。客户端唯一的负担是为返回的 HTML 元素提供一个占位符。
ASP.NET Web 类库提供了出色的支持,可以将 HTML 元素创建为支持渲染方法的类,这为解决此问题提供了极好的机会。
为了实现工厂方法模式,我将每个调用分为两个部分:
- 在服务器上要调用的方法作为名为“method”的键在查询字符串中发送。此方法名称由服务器端的处理器类检索,并使用反射动态调用(您也可以有一个类名)。
- 方法所需的实际参数(为了简化设计)作为键值对在查询字符串中发送。这样,服务器端的方法就可以从
Request
对象中检索所需的参数。
为了在客户端容纳和渲染处理器返回的 HTML 元素,创建了一个 DIV
标签作为目标元素。异步调用返回后,脚本将获取 responseText
并使用 innerHTML
属性将此代码添加到 DIV
标签内,这将渲染页面上的控件。如果此动态创建的元素关联了客户端脚本,则可以选择在服务器端设置创建元素的 ID(这需要将其作为另一个参数在查询字符串中传递,如演示程序中的 setID
)。
解决方案的代码可以分为以下几个部分:
- 初始 AJAX 调用序列。
- 在 ASP.NET 中处理代码(服务器端代码)。
- 在浏览器端处理响应。
初始客户端调用序列
- 所有必需的参数都添加到查询字符串中(在我的示例中包括连接字符串)。这是方法特定的。例如,参见
GetTables()
方法。它只需要一个连接字符串。因此,客户端 JavaScript 将它们添加到查询字符串中,服务器端方法检索它们。 - 查询字符串附加到 URL。URL 和扩展名取决于模块、处理程序或简单 ASPX 页面对服务器端代码的实现。
- 调用者提供一个目标
DIV
标签来显示返回的结果。如果没有提供,则假定为默认DIV
。我在此演示中添加了一个“results”DIV
。 - 使用带有回调函数的
XMLHTTP
对象进行异步调用。请务必在所有浏览器上验证这一点。DataBrowser.js 文件中的以下代码进行了此调用:function SendRequest(target) { AppendConnectionParameters(); if (ajaxObj != null) { target_div.innerHTML = ""<B>Already in call. Please wait ..."</B>"; return; // object is busy } target_div = (target == null) ? dataholder : target; target_div.innerHTML = "<B><I>Getting data. Please wait ..."</I>"<B>"; // for Mozilla, Firefox, Safari, and Netscape if (window.XMLHttpRequest) ajaxobj = new XMLHttpRequest(); // for Internet Explorer if (window.ActiveXObject) ajaxObj = new ActiveXObject("microsoft.xmlhttp"); ajaxObj.onreadystatechange = ProcessResponse; // set the call back // You can make the call in any of the following 3 ways. // (1) opening a aspx call, the default asp.net page // handler will be called to process aspx page // ajaxObj.open("GET", "AjaxCallPage.aspx?" + querystring); // (2) use a custom ASP.NET extension handler // ajaxObj.open("GET", "Data.acall?" + querystring); // (3) use a custome extension with a ASP.NET module installed, // no change in call from above // ajaxObj.open("GET", "Data.acall?" + querystring); ajaxObj.open("GET", "Data.acall?" + querystring); ajaxObj.send(null); // send the request }
在 ASP.NET 内部处理代码(服务器端代码)
ASP.NET 提供的模块、过滤器、页面模型非常灵活,为扩展 ASP.NET 功能提供了绝佳的机会。
来自客户端的自定义调用可以通过三种方式处理:
- 使用 ASPX 页面 - 这是最简单的方式,涉及的开发工作最少。在提供的示例代码中,调用在
Init()
方法中处理,并关闭响应流。这避免了页面级别加载、渲染等其他事件的开销。 - 提供自定义处理程序 - 这绕过了默认的 ASP.NET 页面处理程序。一旦 machine.config 或 web.config 中配置的所有模块完成处理,调用将交给我们的处理程序。这种方法具有自定义扩展的优点,并绕过了默认页面处理程序的任何开销。这涉及在 IIS 中添加扩展,并在 CONFIG 文件中添加处理程序(见下文)。
- 编写自定义模块 - 自定义模块可以在 ASP.NET 内部的事件序列中非常早地处理调用,并且还提供了绕过某些模块和处理程序的优势。编写自定义模块有两种情况。
BeginRequest
是Request
对象可用于模块的最早事件。如果您不希望进行任何身份验证或授权,这是将数据返回给客户端的最快方式。但是,如果您的目标是安全性,请订阅Authenticated
或Authorized
事件并处理请求。这种方法与处理程序一样,需要向 IIS 注册扩展,并将模块添加到 CONFIG 文件中(如下所示)。<configuration> <system.web> <!-- registering a module --> <httpModules> <ADD type="DataBrowser.AjaxCallModule,DataBrowser" name="ajaxcalls" /> </httpModules> <!-- registering a handler --> <httpHandlers> <ADD type="DataBrowser.AjaxCallHandler,DataBrowser" path="*.acall" verb="*"/> </httpHandlers> ....... <system.web> <configuration>
为了使上述所有情况成为可能,提供了 AjaxCallProcessor
类来处理所有逻辑。此类使用反射动态调用方法,该方法返回动态创建并填充数据(根据从 Request
对象读取的客户端参数)的 GUI 元素。
下面的代码显示了 AjaxCallProcessor
类上的 ProcessAjaxCall()
方法。这也可以是一个 static
方法。我将其设为实例方法以简化代码。
internal void ProcessAjaxCall()
{
// clear any headers added to the response
// header by ASP.NET HTTP pipe
_response.Clear();
try
{
// prepare connection string, part of business
// logic in this demo
PrepareConnectionString();
// invoke the method requested by the client
// dynamically using reflection
typeof(AjaxCallProcessor).GetMethod(_request["method"],
BindingFlags.NonPublic|BindingFlags.Instance).Invoke(this, null);
// create a HTMLTextWriter for the response stream
// and let the dynamic control to create it
HtmlTextWriter hw = new HtmlTextWriter(_response.Output);
// let the asp.net control render itself
dynamicControl.RenderControl(hw);
hw.Close();
if (sqlConn.State == ConnectionState.Open) // just in case :-)
sqlConn.Close();
}
catch (Exception ex)
{
// writing back an error message as HTML to response stream
_response.Write("<STRONG><FONT color=red>Error : ");
_response.Write(ex.Message);
_response.Write("</FONT></STRONG>");
}
// close the response stream to end the call immediately
// this will bypass any delay in ASP.NET HTTP pipe
_response.End();
}
在浏览器端处理响应
- 在回调函数内部,检查异步调用的响应。
- 如果
readyState
为 4,则调用已完成,对象的responseText
返回服务器的回复。 - 响应预计是完整的 HTML(如设计)。获取
responseText
并使用innerText
属性将其添加到提供的DIV
标签内。注意:有时您可能不希望在服务器端发生异常时丢失实际的控件。您可以在
responseText
中检查前缀或其他内容,并相应地处理它。例如,不是传递 HTML,而是在<CUSTOM_ERRROR>
标签中传递错误消息,并在客户端检查此标签。function ProcessResponse() { // target_div.innerHTML += ajaxObj.readyState; // for testing only if (ajaxObj.readyState == 4) // request completed { target_div.innerHTML = ajaxObj.responseText; ajaxObj = null; target_div = null; } }
随附的演示程序演示了此技术的使用。请参见下面的屏幕截图,它们显示了如何在客户端替换控件。