改进 ASP.Net 中冗长的 Page_Loads





5.00/5 (3投票s)
使用 UpdatePanel 和异步数据加载替换缓慢的 Page_Load 函数
引言
最近又发生在我身上了。
我的一位同事给我看了一个遗留的 ASP.Net 应用程序,并想知道如何修复其中的一个棘手问题。他们的网页开始显示,但下拉菜单会出现——半绘制,一团糟——并在那里冻结 6-7 秒,直到页面突然自行整理好,并正确显示。
原因?
开发人员决定将他们所有耗时的加载 SQL Server 数据代码放入他们的 Page_Load
函数中。一旦开始运行,浏览器就会停滞不前,直到数据加载完成。
在本文中,我将通过将耗时代码移至单独的线程,然后获取网页以便在数据准备好后自行更新,为您介绍解决此问题的简单步骤。
免责声明
毋庸置疑,使用 Angular 等现代技术加载和显示数据要高效得多,也更易于维护,正如我在我的其他文章中所述,但本文是一篇简单的分步指南,可快速改进遗留页面,而无需重写所有内容。
遵循这些步骤,只需很少的努力,您就可以使缓慢、痛苦的网页更加响应,并在下次年度评估时获得加薪。也许。
遗留代码
让我们从代码以前的样子开始。
基本上,我们的开发人员将从 SQL Server 加载数据的代码放在 Page_Load
函数中。
我们将通过创建 10 秒的延迟来模拟此问题,然后生成一个示例 DataTable
,并将 DataGrid
设置为使用 DataTable
作为其数据源。
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
return;
// Wait for 10 seconds...
System.Threading.Thread.Sleep(10000);
// .. then create a DataTable containing some sample data...
System.Data.DataTable dt = new System.Data.DataTable("Drivers");
dt.Columns.Add("UserID", Type.GetType("System.Int64"));
dt.Columns.Add("Surname", Type.GetType("System.String"));
dt.Columns.Add("Forename", Type.GetType("System.String"));
dt.Columns.Add("Sex", Type.GetType("System.String"));
dt.Columns.Add("Date of Birth", Type.GetType("System.DateTime"));
dt.Rows.Add(new object[] { 1, "James", "Spencer", "M", new DateTime(1962, 3, 19) });
dt.Rows.Add(new object[] { 2, "Edward", "Jones", "M", new DateTime(1939, 7, 12) });
dt.Rows.Add(new object[] { 3, "Janet", "Spender", "F", new DateTime(1996, 1, 7) });
dt.Rows.Add(new object[] { 4, "Maria", "Percy", "F", null });
dt.Rows.Add(new object[] { 5, "Malcolm", "Marvelous", "M", new DateTime(1973, 5, 7) });
// ...and bind it to our ASP.Net GridView control.
this.grid.DataSource = dt;
this.grid.DataBind();
}
这段代码的结果是网页显示速度很慢。通常浏览器在显示页面时会进行到一半,然后冻结几秒钟,最后才设法显示完整的网页。
与此同时,用户一直盯着他们的浏览器,想知道它是否崩溃了,或者他们的笔记本电脑是否卡住了。
这不是愉快的体验。
让我们改为异步!
好的,让我们做得更好。
第一步是进入您的 .aspx 文件,并找到您网页中包含将在耗时任务完成时更新的控件的部分。
在我们的例子中,这很简单,只有我们的 DataGrid
在数据加载完成后需要更新。
<asp:DataGrid ID="grid" runat="server"></asp:DataGrid>
我们需要做的是将此控件(或一组控件)包装在 UpdatePanel
和 ContentTemplate
中,并且我们需要在页面中添加一个 Timer
控件。
<asp:UpdatePanel ID="panel" runat="server" UpdateMode="Conditional"> <ContentTemplate> <asp:Timer ID="MyTimer" OnTick="timer_tick" Interval="1000" runat="server" /> <!-- Put your controls that you will need updating, here.. --> <asp:DataGrid ID="grid" runat="server"></asp:DataGrid> </ContentTemplate> </asp:UpdatePanel>
我们的目标是尽快将网页显示到屏幕上,一旦数据加载完成,我们就可以返回并更新(仅)网页的这一部分。
下一步是将数据加载代码从 Page_Load
函数中分离出来,放入自己的函数中,并使其填充一个变量(在本例中为 DataTable
),该变量存储在 Session 变量中。
您将需要这些 Session
变量之一来加载您将要异步加载的每个数据块。
我们还需要一个布尔 Session
变量 bReadyToDisplayData
,当我们的数据加载完成后,我们会将其设置为“true”。
System.Data.DataTable dt { get { return (System.Data.DataTable)Session["table1"]; } set { Session["table1"] = value; } } bool bReadyToDisplayData { get { return (bool)Session["bReadyToDisplayData"]; } set { Session["bReadyToDisplayData"] = value; } } protected void Page_Load(object sender, EventArgs e) { if (IsPostBack) return; bReadyToDisplayData = false; LoadDataFromWebService(); } private void LoadDataFromWebService() { // (This would be our time-consuming function which loads data from some web service.) // Wait for 10 seconds... System.Threading.Thread.Sleep(10000); // .. then create a DataTable containing some sample data... dt = new System.Data.DataTable("Drivers"); // ... etc ... // Note: We no longer bind our DataTable to our control in this function. // Once all of our data has been loaded, we set this boolean variable, which will // trigger our DataGrid controls to be displayed, showing our freshly-loaded data. bReadyToDisplayData = true; }
这样看起来更好,但我们仍然同步调用我们的数据加载函数,所以接下来,让我们更改 Page_Load
以异步调用它。
System.Threading.Thread thread = new System.Threading.Thread(LoadDataFromWebService); thread.Start();
最后只需要添加一件事来将所有这些联系起来。
您会注意到我们已经在 .aspx 文件中添加了一个 Timer
控件。这是为了让我们能够定期检查数据是否已加载完成,并在加载完成后填充我们的网格控件。
为此,我们需要一个 Timer
“tick”处理程序。
(没有这个 Timer
变量,我们就没有更新 UpdatePanel
的方法。)
protected void timer_tick(object sender, EventArgs e)
{
// Every second, our webpage will call this function.
// If our background thread to load some JSON data has finished running, then
// we'll want to display the data in a grid, and can then stop the timer.
//
if (bReadyToDisplayData == false)
{
return; // Our background thread is still running.
}
// Our JSON data has finished loading !
// Populate our Grid with the JSON data
this.grid.DataSource = dt;
this.grid.DataBind();
// We can now update our UpdatePanel, and stop the timer. Our webpage is now complete!
this.panel.Update();
MyTimer.Enabled = false;
}
就是这样!
现在,当您打开这个 aspx 网页时,它将非常快速地显示,然后开始加载数据,并在完成后,我们将 DataGrid
的 DataSource
设置为指向该数据,并让它显示它。
Session变量
关于使用 Session 变量的快速说明。
如果您发现此代码对您不起作用,请检查您的 .aspx
页面是否能够保存 Session 变量值。 要做到这一点,请在您的 Page_Load
函数中设置 bReadyToDisplayData
变量后设置一个断点。
protected void Page_Load(object sender, EventArgs e) { if (IsPostBack) return; bReadyToDisplayData = false; // Put a breakpoint on the following line... System.Threading.Thread thread = new System.Threading.Thread(LoadSomeData);
如果您随后检查 Session
变量,其“Count
”值应至少为 1 (因为我们刚刚添加了一个 Session 变量来存储 bReadyToDisplayData
变量)。
如果此 Count
值为 0,则表示您的网页未存储 Session 变量,此代码将无法正常工作。
两件事需要检查
1.您的服务器名称是否包含下划线字符?Apparently, this can cause problems with IIS.
2.尝试将以下几行添加到您的 Global.asax
文件中
protected void Session_Load(object sender, EventArgs e) { Session["info"] = 1; }
此 Session 变量问题似乎主要在 Internet Explorer 11 上被注意到。
摘要
好了,就是这样。这是一个简单的 walkthrough,演示了如何将一些非常糟糕的代码变得更用户友好。
我见过许多内部网页出现此问题,尤其是在处理大量数据时。用户通常会单击一个链接来打开网页,在等待旋转的沙漏 30 秒后,会放弃并转向其他地方。
现在,我们可以轻松地修改这些代码,使其更具响应性。
当然,您可以进一步扩展,例如在 UpdatePanel
中添加一个“请稍候”消息,并在数据加载完成后将其隐藏。
就我个人而言,当我知道我的网页需要几秒钟来准备所需数据时,我喜欢在屏幕上显示一个计时器。当用户能够实际看到一个计时器计数时,他们倾向于更信任网页。这比一个看起来“卡住”的浏览器窗口要友好得多。