Daily Archives: February 22, 2008

通过异步页面内容装载提升ASP.NET页面反应速度

原文:Boost ASP.NET performance with deferred content loading

翻译:南卓铜 ([email protected])

clip_image002[4]

在设计一个ASP.NET Web页面时,其反应速度取决于各组成部分的性能,比如在上图,红色条所在的GetRSSReader(从其它网站上读取RSS)功能会导致整个页面反应速度很慢。不管你如何去优化该页面的其它部分,只有其中有一部分反应慢就会导致用户感觉整个Web页面速度呆滞。

在本文,我将给大家演示一种规避这个问题的方法。通过将内容放置到用户控件(User control),直到整个核心内容都显示完后才去装载用户控件内容,这样就可以大幅提升可观察的性能。

我们可以将页面内容细致的分解,通过上述方法可以方便的提升应用程序反应速度,这是用户最关心的。包括下面四个步骤:建立用户控件,将用户控件内容输出成HTML(通过Webservice),提供进程指示条,以及应用ASP.NET AJAX请求在客户端插入此HTML。

1. 建立用户控件

首先,我们需要将装载速度慢但不是核心的内容包装在用户控件(User control)。在这个例子里,我们实现一个简单的RSS聚合阅读器来显示最新的用户帖子。

这里的关键是获取RSS聚合的基本信息。我在这里使用ASP.NET 3.5的一个新功能:LINQ to XML。LINQ使得读取关系数据库、XML等数据源的繁杂工作大为简化。我每次使用它都心动不已。(译者注,不懂LINQ或者ASP.NET 3.5并不会妨碍理解这里讲述的方法)

protected void Page_Load(object sender, EventArgs e)
{
  XDocument feedXML = 
    XDocument.Load("http://feeds.encosia.com/Encosia");
  var feeds = from feed in feedXML.Descendants("item")
              select new
              {
                Title = feed.Element("title").Value,
                Link = feed.Element("link").Value,
                Description = feed.Element("description").Value
              };
  PostList.DataSource = feeds;
  PostList.DataBind();
}

使用ASP.NET 3.5的另一个新控件 ListView来显示聚合的内容。(译者注,可以不管ASP.NET 3.5,这里仅是一个User control的例子。)

<asp:ListView runat="server" ID="PostList">
  <LayoutTemplate>
    <ul>
      <asp:PlaceHolder runat="server" ID="itemPlaceholder" />
    </ul>
  </LayoutTemplate>
  <ItemTemplate>
    <li><a href='<%# Eval("Link") %>'><%# Eval("Title") %></a><br />
      <%# Eval("Description") %>         
    </li>
  </ItemTemplate>
</asp:ListView>

通过使用一点CSS(包括在源代码,在下面可以下载),聚合的结果可以很好的显示出来,如下图。

clip_image004[4]

ListView可以很方便的演示下面步骤,因为通过它,我们基本不需要太多的代码就可以生成HTML,最后将这些生成的HTML代码插入到页面。

2. 将用户控件润色成HTML

本步我们创建一个Web service将用户控件生成HTML字符串。这里,我们需要在ASP.NET页面实例之外生成HTML字符串。一般情况下,用户控件都是被装载到Page实例上。

为解决这个问题,我们可以创建一个临时的ASP.NET Page类,动态加载用户控件,然后在Web service里执行它。实际实现比起描述要简单易懂的多。

[WebMethod]
public string GetRSSReader()
{
  // 创建新Page类,并加载用户控件
  Page page = new Page();
  UserControl ctl =
    (UserControl)page.LoadControl("~/RSSReaderControl.ascx");
  page.Controls.Add(ctl);
 
  //将动态页生成HTML
  StringWriter writer = new StringWriter();
  HttpContext.Current.Server.Execute(page, writer, false);
 
  //生成的HTML返回为字符串
  return writer.ToString();
}

你可能在担心创建一个新的页面实例是否会有性能上的影响。一开始,我也是这么考虑的。但,因为我们的Page实例是创建在Web service上,而不是在ASP.NET HTTP管道(pipeline)上,而且只包括一个用户控件,此额外的开支可以被忽略不计。Page实例本身的开支相对于整个HTTP进程来讲是微不足道的。

3. 创建演示页面

我们需要一个演示页面来快速显示核心内容。在该页上,定义一个DIV标签。该标签在核心内容显示完后再异步从服务器端装载内容。

<asp:ScriptManager runat="server">
  <Services>
    <asp:ServiceReference Path="~/RSSReader.asmx" />
  </Services>
  <Scripts>
    <asp:ScriptReference Path="~/Default.js" />
  </Scripts>
</asp:ScriptManager>
<div id="Container">
  <div id="RSSBlock" class="loading"></div>
  <div id="Content">
    <p>Lorem ipsum dolor sit amet, consectetuer adipiscing...</p>
  </div>
</div>

如你看着的,RSSBlock定义的这个DIV标签分配有一个CSS类“loading”。这个CSS类显示进度指示条,一直到异步加载的内容被下载到客户端。

.loading { 
  background: url('progress-indicator.gif') no-repeat center; 
}

clip_image006[4]

所以,这里其实不是一个真实的指示条,无法计算进程进度。但用户是无法知道这是不是真实的。你不说没人会知道。

4. 从Javascript里调用Web service

现在我们已经有了一个空位置,我们需要将真实的HTML填充这个位置。ASP.NET AJAX为我们做好了大部分的事情。

Sys.Application.add_init(AppInit);
 
function AppInit() {
  RSSReader.GetRSSReader(OnSuccess, OnFailure);
}
 
function OnSuccess(result) {
  // .loading CSS类从div上移去,效果上看是移去了进程条
  Sys.UI.DomElement.removeCssClass($get('RSSBlock'), 'loading');
 
  // 将生成的HTML填充这个div位置
  $get('RSSBlock').innerHTML = result;
}
 
function OnFailure() {
  // 如果失败了,要做点什么。比如重试,等
}

因为这里我们用了CSS类来实现进度条,只要简单移去该loading类就可以了。这里我们使用removeCssClass方法。更多关于该方法的信息,可以见这里

动画的背景移去后,是时候在div中插入HTML内容了。最终的结果正是我们期待的,前面我们设计的用户控件已经显示在div指定的位置上了,而且不会妨碍到整个页面的反应速度。

5. 结论

我想大家可能发现这个方法是很有用的。它可以让你利用现有的ASP.NET知识,结合服务器控件,实现一个轻量级的基于AJAX的模板方案。同时,它使一些繁琐、不易维护的工作变得简单。

注意我们这里没有使用UpdatePanel。使用以上描述的技术,不需要使用Partial Postback。它不仅可以提升性能,而且即使在内容延后被装载的时候,也不影响页面其它使用了UpdatePanel的正常工作。

源码