原文:Boost ASP.NET performance with deferred content loading
翻译:南卓铜 ([email protected])
在设计一个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(包括在源代码,在下面可以下载),聚合的结果可以很好的显示出来,如下图。
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;
}
所以,这里其实不是一个真实的指示条,无法计算进程进度。但用户是无法知道这是不是真实的。你不说没人会知道。
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的正常工作。
源码