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

用户控件的增量页面显示模式

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.96/5 (8投票s)

2007年10月30日

CPOL

4分钟阅读

viewsIcon

61647

downloadIcon

498

无需自定义 JavaScript,即可通过 AJAX 异步加载用户控件的解决方案。

正在加载...

Screenshot - loading.gif

目录

介绍

我首先要承认,即使作为一名开发人员,我也对那些具有真正干净且可用 AJAX 实现的网站惊叹不已。我经常感到沮丧的是,大多数炫酷的类 AJAX 库都不是针对 ASP.NET 的。特别是,我最近遇到过几个网站使用增量页面显示模式在页面加载后显示内容。我知道 ASP.NET AJAX 和控件工具包支持调用 Web 服务来显示 HTML 输出,但我因为很多原因(在本篇文章中过多)不喜欢这种方法。此外,调用 Web 服务没有为开发人员提供渲染用户控件和其他真正动态内容的方法。这太有限了,用处不大。

目的

我在这篇文章中的目标是为动态加载内容,特别是用户控件(.ascx 控件)奠定基础。为了归功于应得的人,这篇文章的一些灵感来自Scott Guthrie 的 UI 模板文章。我喜欢 Scott 避免使用 UpdatePanel 以及使用模板化用户控件的方法。他的想法是一个不错的起点,但我对拥有一个可以拖到页面上就能完成工作的服务器控件感兴趣。我特别感兴趣的是一个允许我编写 JavaScript 的控件。和大多数开发人员一样,我宁愿花时间编写代码,而不是难以维护的客户端脚本。

工作示例

服务器控件

让我们直接进入示例代码。代码主要由两部分组成;一个服务器控件和一个 HTTP 请求处理程序。让我们从服务器控件开始。服务器控件的目的是生成一个客户端可调用的 JavaScript,用于调用服务器以获取用户控件的内容。服务器控件要渲染脚本,只需要用户控件的相对路径即可。代码工作原理如下:

public class IncrementalLoader : System.Web.UI.WebControls.CompositeControl, IScriptControl
{
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        // JavaScript registration - only happens for the first server control loaded

        if (!this.Page.ClientScript.IsClientScriptIncludeRegistered(
                              "__IncrementalLoaderScript"))
        {
            string resource = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), 
                "ControlLoading.IncrementalLoader.js");
            
            this.Page.ClientScript.RegisterClientScriptInclude(this.GetType(), 
                "__IncrementalLoaderScript", resource);
        }
    }
    // Gets a path for the javascript to query.  This points to the HTTP handler

    public string GetCallbackPath()
    {
        string path = this.Page.Request.Path;

        if (!string.IsNullOrEmpty(path))
        {
            path = path.Replace(".aspx", HandlerExtension);
        }

        string controlLocation = this.Page.ResolveUrl(this.ResourceToLoad);
        string query = Utility.EncryptString(controlLocation);

        path += "?" + ControlQuery + "=" + HttpUtility.UrlEncode(query);

        return path;
    }
    // Each server control instance renders a single line of JavaScript to load 

    // the user control when the browser is done loading the page.

    public string RenderLoadScript()
    {
        return "LoadContent('" + GetCallbackPath() + "', '" + 
               _contentPanel.ClientID + "', '" 
               + _loadingPanel.ClientID + "', '" + 
               _faultPanel.ClientID + "');";
    }
    // Registers the startup script 

    private void RetisterClientScript()
    {
        StringBuilder script = 
          new StringBuilder("<script type="\"text/javascript\""></script>\r\n");

        this.Page.ClientScript.RegisterStartupScript(this.GetType(), _
                               contentPanel.ClientID + 
                               "LoadingScript", script.ToString());
    }
}

没什么大不了的。服务器控件只是输出了一点 JavaScript。服务器控件还提供了模板来定义控件加载期间显示的标记,以及在遇到错误时显示的标记。请注意,服务器控件构建的查询字符串是加密的。您不希望窥探者看到用户控件的相对路径。

处理程序

让我们继续处理 HTTP 处理程序。处理程序负责渲染指定用户控件的输出。在本例中,处理程序接收 HTTP 请求(扩展名为 .ashx),并解析加密的查询字符串以确定要加载哪个用户控件。通过创建空的 Page 对象,将用户控件添加到 Page 的控件集合中,然后执行 Page 来加载用户控件。生成的 HTML 会被发送回浏览器进行渲染。出于示例目的,处理程序模拟高达 3 秒的延迟。

public void ProcessRequest(HttpContext context)
{
    // *** Simulate latency ***

    Random r = new Random();
    int wait = r.Next(500, 3000);
    System.Threading.Thread.Sleep(wait);

    if (context.Request.QueryString[IncrementalLoader.ControlQuery] != null)
    {
        try
        {
            // Create a page instance to host the user control

            Page page = new Page();

            string controlLocation = 
              Utility.DecryptString(
              context.Request.QueryString[IncrementalLoader.ControlQuery]);
            UserControl control = (UserControl)page.LoadControl(controlLocation);

            page.Controls.Add(control);

            // Execute the page and return the output
            // (only the control's output is returned)

            StringWriter output = new StringWriter();
            context.Server.Execute(page, output, false);

            context.Response.Clear();
            context.Response.Write(output.ToString());
        }
        catch { }
    }
}

一切似乎都很简单,对吧?这是一个清晰简单的方法,可以获取填充 UI 所需的输出 HTML,而且几乎不需要代码。

尽管如此,这段代码已经存在一些根本性的缺陷。

优点和缺点

优点是,页面加载速度极快,因为回发时只需要加载少量数据。我的观点是,当用户可以看到哪怕一点点内容时,用户体验也比等待整个页面加载要好。此外,根据我的初步指标,加载动态内容所需的时间(加载所有控件的总时长)与标准页面加载时间相同。

另外,如果您的页面包含一个耗时较长的用户控件,其他页面部分在处理耗时内容时仍然可用。

当然,这种方法也有其缺点。最明显的问题是,每个动态用户控件都会产生一次额外的服务器往返。虽然流量很少且是异步的,但服务器仍需要处理更高的总体吞吐量。显然,在采用这种模式之前,这是一个需要认真考虑的因素。

目前最令人沮丧的缺点是无法使用需要 form 标签的 ASP.NET 控件。例如,超链接不需要包含在 form 标签内,因为它们不一定会回发到当前页面。然而,按钮需要 form 标签才能工作。在有人找到一种方法来管理状态和事件注册之前,此解决方案在使用控件方面存在限制。

注意:示例下载确实包含了一种可能的 form 标签问题的解决方法。我让所有控件都渲染出来了,但视图状态和事件丢失了。

寻求帮助

我只是没有时间深入研究 ASP.NET 管道和页面架构,来处理如何在没有页面可供回发的情况下维护动态加载控件的视图状态和事件。我正式请求,对任何有兴趣的人,帮助完成这个项目,找到一种方法来维护动态加载控件的状态和事件!欢迎任何帮助或想法!

尽情享用!

© . All rights reserved.