FireBug graph of net requests
When rolled into the page life cycle of an ASP.NET WebForm, that red bar is one of your application’s greatest enemies. No matter how well you optimize the rest of the page, even one slow task can become the sole factor determining a user’s perception of the entire page’s performance.
当进入一个asp.net网页的生命周期中时,上面的那个红色条条会是你的应用程序中的最大敌人之一。不管你怎样优化页面的其他部分,页面中的一个很慢的任务甚至会成为影响用户对整个页面性能认知的唯一因素。

In this post, I’m going to show you one way to circumvent that problem. By placing ancillary content in user controls and delaying their load until the core page content has been displayed, you can drastically improve perceived performance.
在这篇文章中,我会给大家展示一个能避免这种问题的方法。通过在用户控件中放置辅助内容,等整个页面显示完之后再加载这些用户控件,你就能彻底地提高感知性能。

When broken down into digestible chunks, the technique is easy to implement and lends your application a level of polish that your users are sure to appreciate. The four steps required to accomplish this will be: building the user control, statelessly rendering the control as HTML, providing progress indication, and using ASP.NET AJAX to request and inject that HTML.
当分解成可消化的块时,这样技术就很容易实现,并且会让你的应用程序提高到一个非常好的层次,你的用户都会表示感激。要达到这种效果你需要完成以下四步:创建用户控件,把用户控件生成html字符串,提供进度提示,使用AJAX请求和展示html信息。

Building the user control

First, we need some slow-loading, auxiliary content to encapsulate in a user control. For this example, that’s going to be a minimal RSS feed reader widget that displays the most recent posts from this site.
首先,我们要把一些加载缓慢的,不是很重要的内容封装到一个用户控件中。比如这个例子:显示这个网站中最新发布的内容的Rss内容阅读器就是一个很小的附加件。

The key activity here is retrieving my RSS feed and querying it for some basic information. To expedite this, I’m going to use one of ASP.NET 3.5’s great new features: LINQ to XML. LINQ really makes short work of this normally tedious task. It still blows me away every time I use it.
这里关键的活动就是恢复Rss的内容并查询一些基本的信息。为了迅速处理它,我将要使用ASP.NET3.5中一个非常棒的新特性:Ling toXML。LING真的能够简化这些普通又枯燥的工作。不过每次使用它都会打击我一下。

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();
}

To display this, I’m going to use another of ASP.NET 3.5’s new features, the ListView control:
为了显示它,我使用了ASP.NET3.5的另外一个新特性,ListView 控件:

<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>

Example of the RSS feed widget controlWith a little bit of CSS (included in the source download later), this results in something resembling the screenshot to the right.
使用很少的CSS,就能达到类似右边截屏的效果。

The ListView comes in especially handy for our purposes here, because it gives you such effortless control over the rendered HTML. When injecting generated HTML into a page, you really appreciate knowing exactly what markup to expect.
ListView控件真的很方便地满足了我们的需求,因为它提供了一个很不费劲的控件来生成HTML。你将会非常地感激它当它把产生的HTML代码注入到一个页面并且展现的正像你期望的那样的时候。

Rendering the user control as HTML

The next step is to create a web service that statelessly renders our user control as an HTML string. By statelessly, I mean that we need to render this user control outside the context of an active ASP.NET Page instance, where user controls are normally intended to be used.
下一步就是创建一个web service来把我们的用户控件生成一个html字符串来展现。敞开来说,我的意思就是我们需要把用户控件呈现在一个动态的aspx页面实例的内容之外,而不是像往常那样直接在aspx页面中使用。

To solve that problem, you can create a temporary instance of the ASP.NET Page class, dynamically add the user control to it, and then execute it within the web service’s context. Doing this turns out to be easier than it is to accurately describe:
为了解决那个问题,你可以创建一个临时的aspx类实体,动态加载用户控件,然后在web server内容中执行它。这样做就跳出了它的精确的描述的困境而变的容易很多:

[WebMethod]
public string GetRSSReader()
{
// Create a new Page and add the control to it.
Page page = new Page();
UserControl ctl =
(UserControl)page.LoadControl("~/RSSReaderControl.ascx");
page.Controls.Add(ctl);
 
// Render the page and capture the resulting HTML.
StringWriter writer = new StringWriter();
HttpContext.Current.Server.Execute(page, writer, false);
 
// Return that HTML, as a string.
return writer.ToString();
}

You might worry about creating an entire Page instance for what is supposed to be a performance enhancing technique. At first, I shared the same concern.
你可能会担忧为了这个设想中的性能提高技术创建一个整个页面的实例,会不会能达到性能提高的效果。刚开始的时候,我有同样的担忧。

However, since our Page instance is created outside of the normal ASP.NET HTTP pipeline and only contains one control, the overhead is negligible. The Page itself is fairly performant compared to all of the other work involved in a typical HTTP round-trip.
不过,因为我们的实体页面是在正常的asp.net http管道之外创建的并且只包含一个控件,这点额外的开销是可以忽略不计的。这个页面自身和一个正常http通讯中的工作相比以及是十分优化了。

Setting up the demonstration page

设置演示页面

To demonstrate, we’ll need a page with some fast-loading content to provide contrast. Inside that, we can embed an empty DIV which will be used to accurately inject the user control’s rendered HTML.
为了演示,我们需要一个有一些加载迅速的内容的页面来提供对照。在这个页面里面,我们可以嵌套一个空的DIV来准确地装载用户控件呈现的html内容。

<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>

As you can see, the RSSBlock DIV is initially assigned a CSS class of “loading”. This is the CSS for the loading class:
你可以看到,这个RSSBlock DIV 默认就被分配一个CSS 样式“loading”.下面就是这个loading class的样式:

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

Progress indicator displayed while the user control loadsWhat this does is give us a bit of rudimentary progress indication. Until we update it later, it will display an empty placeholder with an indicator.
这种处理方式给我们提供了一个尚未完成的进度提示。它会一直用一个指示器显示一个空的占位符,直到我们后来更新它。

You and I know that it’s a bit of a scam, but your users will never realize the progress indicator isn’t real. I won’t tell if you won’t!
我们都知道这不是太准确,但是你的用户是永远不会知道这个进度指示是不真实的。如果你不说出,我也不会。

Calling the web service from JavaScript

利用javascript调用web service

Now that we’ve got a place to inject it, the final step is to retrieve the HTML rendering of our user control and insert it into the page. ASP.NET AJAX takes all of the hard work out of this step:
现在我们有一个展现它的地方,最后一步就是重现获得我们用户控件产生的html代码并把它插入到页面中。ajax技术在这一步中为我们节省了大量的工作。

Sys.Application.add_init(AppInit);
 
function AppInit() {
RSSReader.GetRSSReader(OnSuccess, OnFailure);
}
 
function OnSuccess(result) {
// Remove the .loading CSS from the div, to remove the 
//  progress indicator background.
Sys.UI.DomElement.removeCssClass($get('RSSBlock'), 'loading');
 
// Fill the div with the HTML generated from the user control.
$get('RSSBlock').innerHTML = result;
}
 
function OnFailure() {
// Do something if our callback fails.  Retry it, perhaps.
}

Since it’s only a CSS class, disabling the progress indication is as simple as using removeCssClass to remove it from the DIV. For more on removeCssClass, check out my recent article about ASP.NET AJAX’s client side UI methods.
因为它只是一个css样式,禁用这个进度指示就像我们利用romoveCssClassdiv从DIV中移除它一样简单。了解更多关于removeCssClass的信息,请查阅我最近写的一篇关于ajax客户端UI方法的文章。

With the animated background removed, we are now free to insert the rendered HTML into the DIV’s innerHTML. The end result is exactly the same as if we had placed the user control inside that DIV, without unnecessarily delaying the entire page load.
当这个动态的背景被移除之后,我们就很容易把产生的html代码插入到DIV的innerHTML中。最终结果完全像我们直接把用户控件放到这个DIV中一样,并且避免了影响整个页面的加载速度。

Conclusion

I think you’ll find that this technique is very powerful. It allows you to leverage your existing knowledge of ASP.NET and its server controls as a robust templating solution for lightweight AJAX. At the same time, it exudes the kind of professional usability that typically requires more tedious and less maintainable client side coding.
我想你会发现这个技术是十分的强大。对比轻量级的AJAX,它会让你重新衡量一下你现有的asp.net技术以及asp.net笨重的服务器控件模板解决方法。同时,它分散了这种专业的可用性,代表性地需要更多的单调的和更少的可维持的客户端代码。

Note that there is not an UpdatePanel anywhere on this page. Using this technique does not require relying on partial postbacks. Not only does that improve performance, but also allows UpdatePanels elsewhere on the page to operate normally, while the deferred content loads.
提醒一下,在这个页面中还没有一个更新Panel。用这种技术不需要依赖局部的刷新。这样不但提高了性能,而且允许updatePanels放到页面的其他地方进行正常的操作,当延迟的内容加载的时候。

Download source: uc-delay-load.zip (36kb)

Check out the full demonstration by downloading the source and running it yourself. You really have to see it in action to truly appreciate it. The download also includes at least one improvement that I didn’t have room to write about in this post. Give it a try and let me know what you think.
下载源代码,自己运行一下,看一看整个的演示效果。你真的要看一看那样你会真的喜欢上它。下载的附件中也包含了我在这篇文章中没有提到的不只一项改进的地方。试一下吧并让我知道你的想法。