多个更新面板阻止 Link 单击直到它们全部完成加载
Multiple Update Panels Prevent Link Click Until They All Finish Loading
我正在开发 ASP.Net 页面,其中一项要求是让主页上的多个小部件显示各种指标。我 运行 遇到的最初问题是小部件使页面在用户登录后加载时间太长。一位同事建议我使用他创建的用户控件来更新小部件。用户控件本身是一个带有额外功能的更新面板。我 运行 现在遇到的问题是用户在加载小部件时无法单击任何链接。用户控件中是否有可能阻止请求的内容?如果使用更新面板无法做到这一点,请提出更好的方法。用户控制代码如下。
用户控制
namespace test.usercontrols.GenericControls
{
/// <summary>
/// A control that will asyncronously refersh it's content after a delay.
/// </summary>
[ToolboxData("<{0}:DelayedLoadPanel runat=server></{0}:DelayedLoadPanel>")]
[PersistChildren(false)]
[ParseChildren(true)]
public class DelayedLoadPanel : PlaceHolder, INamingContainer
{
private UpdatePanel contentPanel = new UpdatePanel();
private PlaceHolder contentHolder = new PlaceHolder();
private HtmlGenericControl loadScriptControl = new HtmlGenericControl("script");
private PlaceHolder _unloadedContent = new PlaceHolder();
private PlaceHolder _loadedContent = new PlaceHolder();
/// <summary>
/// The initial content to be shown.
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty)]
public PlaceHolder UnloadedContent
{
get { return _unloadedContent; }
set { _unloadedContent = value; }
}
/// <summary>
/// The content to be shown after LoadDelay has expired and the panel refreshed.
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty)]
public PlaceHolder LoadedContent
{
get { return _loadedContent; }
set { _loadedContent = value; }
}
/// <summary>
/// Javascript to be run after LoadDelay has expired and the panel refreshed.
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty)]
public Literal LoadedScript { get; set; }
/// <summary>
/// The delay in milliseconds before the page will reload. Set to a negative value to prevent automatic reload.
/// </summary>
public int LoadDelay { get; set; }
public int MaxAsyncCallCount
{
get { return _MaxAsyncCallCount; }
set { _MaxAsyncCallCount = value; }
}
private int _MaxAsyncCallCount = 1;
/// <summary>
/// Sets or gets the value for if the LoadedContent is visibile. Setting to true will fire the DelayedLoad events.
/// </summary>
public bool IsLoaded
{
get
{
if (ViewState["IsLoaded"] == null)
ViewState["IsLoaded"] = false;
return (bool) ViewState["IsLoaded"];
}
set
{
ViewState["IsLoaded"] = value;
if (value)
OnDelayedLoad(new EventArgs());
}
}
protected override void OnInit(EventArgs e)
{
// We want to make sure our child controls are created so they can properly load view state. We need them before OnLoad so we can compare ID's.
EnsureChildControls();
base.OnInit(e);
}
/// <summary>
/// Check to see if this is the control's post back after LoadDelay has expired, if so set the value and fire the events.
/// </summary>
/// <param name="e"></param>
protected override void OnLoad(EventArgs e)
{
var scriptManager = ScriptManager.GetCurrent(Page);
if (scriptManager.IsInAsyncPostBack && (scriptManager.AsyncPostBackSourceElementID == contentPanel.ClientID))
{
// This also fire off the DelayedLoad and OnDelayedLoad events.
IsLoaded = true;
}
base.OnLoad(e);
}
/// <summary>
/// Set the visibility of the controls so only the proper ones render.
/// </summary>
/// <param name="e"></param>
protected override void OnPreRender(EventArgs e)
{
registerLoadScript();
// We should really set this in the render function so it's as late in the page life cycle as possible.
// However render does not seem to get called during partial post backs.
loadScriptControl.Attributes["type"] = "text/javascript";
loadScriptControl.InnerHtml = "$(document).ready(setTimeout(function() { test.doPostBack('" + contentPanel.ClientID + "', null, " + MaxAsyncCallCount + ")}, " + LoadDelay + "));";
if (IsLoaded)
{
if (LoadedScript != null)
{
var scriptTag = new HtmlGenericControl("script");
scriptTag.Attributes["type"] = "text/javascript";
scriptTag.Attributes["name"] = "test.DelayedLoadPanel.OnLoaded";
scriptTag.InnerHtml = LoadedScript.Text;
contentHolder.Controls.Add(scriptTag);
}
// We're loaded so we want to show the loaded content items but not the unloaded, and we really don't want to repost the automatic post back scripts.
LoadedContent.Visible = true;
loadScriptControl.Visible = false;
UnloadedContent.Visible = false;
}
else
{
// Show the unloaded content and add some javascript that will cause the page to post back after the LoadDelay timeout
LoadedContent.Visible = false;
loadScriptControl.Visible = (LoadDelay >= 0); // If LoadDelay is a negative number we're not going to automatically refresh.
UnloadedContent.Visible = true;
}
base.OnPreRender(e);
}
/// <summary>
/// Causes the panel to update it's content to the browser.
/// </summary>
public virtual void Update()
{
contentPanel.Update();
}
public delegate void DelayedLoadHandler(object sender, EventArgs e);
/// <summary>
/// Occurs when the control is reloaded after DelayedLoad has expired.
/// </summary>
public event DelayedLoadHandler DelayedLoad;
/// <summary>
/// Raises the DelayedLoad event.
/// </summary>
/// <param name="e"></param>
protected virtual void OnDelayedLoad(EventArgs e)
{
if ((IsLoaded) && (DelayedLoad != null))
DelayedLoad(this, null);
Update();
}
/// <summary>
/// Create all the sub controls for this control.
/// </summary>
protected override void CreateChildControls()
{
Controls.Clear();
contentPanel.ID = "contentPanel";
contentPanel.ChildrenAsTriggers = false;
contentPanel.UpdateMode = UpdatePanelUpdateMode.Conditional;
contentPanel.ContentTemplateContainer.Controls.Add(contentHolder);
Controls.Add(contentPanel);
contentHolder.Controls.Add(LoadedContent);
contentHolder.Controls.Add(loadScriptControl);
contentHolder.Controls.Add(UnloadedContent);
base.CreateChildControls();
}
private void registerLoadScript()
{
ScriptManager.RegisterClientScriptInclude(this, typeof(DelayedLoadPanel), "DelayedLoadPanelScript", "/javascript/DelayedLoadPanel.js");
}
}
}
DelayedLoadPanel.js
Test.DelayedLoadPanel = new Object();
Test.DelayedLoadPanel.AddNamespace = function (obj) {
if (obj.Test == undefined)
obj.Test = new Object();
if (obj.Test.DelayedLoadPanel == undefined)
obj.Test.DelayedLoadPanel = new Object();
}
Test.DelayedLoadPanel.parseOnLoadScripts = function () {
var scripts = $("script[name='Test.DelayedLoadPanel.OnLoaded']");
for (var i = 0; i < scripts.length; i++) {
eval(scripts[i].text);
}
scripts.remove();
}
Test.DelayedLoadPanel.saveScrollPosition = function() {
Test.DelayedLoadPanel.currentScrollX = $(window).scrollLeft();
Test.DelayedLoadPanel.currentScrollY = $(window).scrollTop();
}
Test.DelayedLoadPanel.restoreScrollPosition = function() {
$(window).scrollLeft(Test.DelayedLoadPanel.currentScrollX);
$(window).scrollTop(Test.DelayedLoadPanel.currentScrollY);
}
$(document).ready(function() {
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_pageLoading(Test.DelayedLoadPanel.saveScrollPosition);
prm.add_endRequest(Test.DelayedLoadPanel.restoreScrollPosition);
prm.add_endRequest(Test.DelayedLoadPanel.parseOnLoadScripts);
});
Base.js
var Test = new Object();
Test.postBackQueue = new Array();
Test.doPostBack = function (target, parameter) {
var prm = Sys.WebForms.PageRequestManager.getInstance();
if ((typeof target != "undefined") && (typeof parameter != "undefined"))
Test.postBackQueue.push({ target: target, parameter: parameter });
// If we're not in the middle of doing a post back then go ahead and fire the first item in the queue.
if ((!prm.get_isInAsyncPostBack()) && (Test.postBackQueue.length > 0)) {
var postbackInfo = Test.postBackQueue.shift();
__doPostBack(postbackInfo.target, postbackInfo.parameter);
}
}
Test.postBackEndRequestHandler = function () {
Test.doPostBack();
};
示例用法
<a href="cat-management.aspx?view=cats&TimeFrame=3" class="metricWidget">
<div class="widgetHeader">
<img src="/images/cat.png" class="widgetIcon" />
<div class="widgetHeaderText">proactive tickets</div>
</div>
<Test:DelayedLoadPanel runat="server" ID="catPanel" OnDelayedLoad="catDelayedLoad" MaxAsyncCallCount="2">
<LoadedContent>
<div class="widgetSubHeader">Type</div>
<div class="widgetSubPanelLeft">Fluffy <span class="widgetMetric widgetMetricPadding" runat="server" id="fluffyCats"></span></div>
<div class="widgetSubPanelRight">Orange <span class="widgetMetric widgetMetricPadding" runat="server" id="orangeCats"></span></div>
</LoadedContent>
<UnloadedContent>
<img class="widgetLoader" />
</UnloadedContent>
</Test:DelayedLoadPanel>
</a>
代码隐藏
protected void catDelayedLoad(object sender, EventArgs eventArgs)
{
var catCount = Utility.catService.GetCatCount(currentOrg, from,
to);
fluffyCats.InnerText = catCount.FluffyCats.ToString();
orangeCats.InnerText = catCount.OrangeCats.ToString();
}
问题是由 session locking 引起的,它阻止了两个请求同时访问会话状态。我能够通过将 Page 指令的 EnableSessionState 属性设置为 ReadOnly 来解决这个问题。万岁!
我正在开发 ASP.Net 页面,其中一项要求是让主页上的多个小部件显示各种指标。我 运行 遇到的最初问题是小部件使页面在用户登录后加载时间太长。一位同事建议我使用他创建的用户控件来更新小部件。用户控件本身是一个带有额外功能的更新面板。我 运行 现在遇到的问题是用户在加载小部件时无法单击任何链接。用户控件中是否有可能阻止请求的内容?如果使用更新面板无法做到这一点,请提出更好的方法。用户控制代码如下。
用户控制
namespace test.usercontrols.GenericControls
{
/// <summary>
/// A control that will asyncronously refersh it's content after a delay.
/// </summary>
[ToolboxData("<{0}:DelayedLoadPanel runat=server></{0}:DelayedLoadPanel>")]
[PersistChildren(false)]
[ParseChildren(true)]
public class DelayedLoadPanel : PlaceHolder, INamingContainer
{
private UpdatePanel contentPanel = new UpdatePanel();
private PlaceHolder contentHolder = new PlaceHolder();
private HtmlGenericControl loadScriptControl = new HtmlGenericControl("script");
private PlaceHolder _unloadedContent = new PlaceHolder();
private PlaceHolder _loadedContent = new PlaceHolder();
/// <summary>
/// The initial content to be shown.
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty)]
public PlaceHolder UnloadedContent
{
get { return _unloadedContent; }
set { _unloadedContent = value; }
}
/// <summary>
/// The content to be shown after LoadDelay has expired and the panel refreshed.
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty)]
public PlaceHolder LoadedContent
{
get { return _loadedContent; }
set { _loadedContent = value; }
}
/// <summary>
/// Javascript to be run after LoadDelay has expired and the panel refreshed.
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty)]
public Literal LoadedScript { get; set; }
/// <summary>
/// The delay in milliseconds before the page will reload. Set to a negative value to prevent automatic reload.
/// </summary>
public int LoadDelay { get; set; }
public int MaxAsyncCallCount
{
get { return _MaxAsyncCallCount; }
set { _MaxAsyncCallCount = value; }
}
private int _MaxAsyncCallCount = 1;
/// <summary>
/// Sets or gets the value for if the LoadedContent is visibile. Setting to true will fire the DelayedLoad events.
/// </summary>
public bool IsLoaded
{
get
{
if (ViewState["IsLoaded"] == null)
ViewState["IsLoaded"] = false;
return (bool) ViewState["IsLoaded"];
}
set
{
ViewState["IsLoaded"] = value;
if (value)
OnDelayedLoad(new EventArgs());
}
}
protected override void OnInit(EventArgs e)
{
// We want to make sure our child controls are created so they can properly load view state. We need them before OnLoad so we can compare ID's.
EnsureChildControls();
base.OnInit(e);
}
/// <summary>
/// Check to see if this is the control's post back after LoadDelay has expired, if so set the value and fire the events.
/// </summary>
/// <param name="e"></param>
protected override void OnLoad(EventArgs e)
{
var scriptManager = ScriptManager.GetCurrent(Page);
if (scriptManager.IsInAsyncPostBack && (scriptManager.AsyncPostBackSourceElementID == contentPanel.ClientID))
{
// This also fire off the DelayedLoad and OnDelayedLoad events.
IsLoaded = true;
}
base.OnLoad(e);
}
/// <summary>
/// Set the visibility of the controls so only the proper ones render.
/// </summary>
/// <param name="e"></param>
protected override void OnPreRender(EventArgs e)
{
registerLoadScript();
// We should really set this in the render function so it's as late in the page life cycle as possible.
// However render does not seem to get called during partial post backs.
loadScriptControl.Attributes["type"] = "text/javascript";
loadScriptControl.InnerHtml = "$(document).ready(setTimeout(function() { test.doPostBack('" + contentPanel.ClientID + "', null, " + MaxAsyncCallCount + ")}, " + LoadDelay + "));";
if (IsLoaded)
{
if (LoadedScript != null)
{
var scriptTag = new HtmlGenericControl("script");
scriptTag.Attributes["type"] = "text/javascript";
scriptTag.Attributes["name"] = "test.DelayedLoadPanel.OnLoaded";
scriptTag.InnerHtml = LoadedScript.Text;
contentHolder.Controls.Add(scriptTag);
}
// We're loaded so we want to show the loaded content items but not the unloaded, and we really don't want to repost the automatic post back scripts.
LoadedContent.Visible = true;
loadScriptControl.Visible = false;
UnloadedContent.Visible = false;
}
else
{
// Show the unloaded content and add some javascript that will cause the page to post back after the LoadDelay timeout
LoadedContent.Visible = false;
loadScriptControl.Visible = (LoadDelay >= 0); // If LoadDelay is a negative number we're not going to automatically refresh.
UnloadedContent.Visible = true;
}
base.OnPreRender(e);
}
/// <summary>
/// Causes the panel to update it's content to the browser.
/// </summary>
public virtual void Update()
{
contentPanel.Update();
}
public delegate void DelayedLoadHandler(object sender, EventArgs e);
/// <summary>
/// Occurs when the control is reloaded after DelayedLoad has expired.
/// </summary>
public event DelayedLoadHandler DelayedLoad;
/// <summary>
/// Raises the DelayedLoad event.
/// </summary>
/// <param name="e"></param>
protected virtual void OnDelayedLoad(EventArgs e)
{
if ((IsLoaded) && (DelayedLoad != null))
DelayedLoad(this, null);
Update();
}
/// <summary>
/// Create all the sub controls for this control.
/// </summary>
protected override void CreateChildControls()
{
Controls.Clear();
contentPanel.ID = "contentPanel";
contentPanel.ChildrenAsTriggers = false;
contentPanel.UpdateMode = UpdatePanelUpdateMode.Conditional;
contentPanel.ContentTemplateContainer.Controls.Add(contentHolder);
Controls.Add(contentPanel);
contentHolder.Controls.Add(LoadedContent);
contentHolder.Controls.Add(loadScriptControl);
contentHolder.Controls.Add(UnloadedContent);
base.CreateChildControls();
}
private void registerLoadScript()
{
ScriptManager.RegisterClientScriptInclude(this, typeof(DelayedLoadPanel), "DelayedLoadPanelScript", "/javascript/DelayedLoadPanel.js");
}
}
}
DelayedLoadPanel.js
Test.DelayedLoadPanel = new Object();
Test.DelayedLoadPanel.AddNamespace = function (obj) {
if (obj.Test == undefined)
obj.Test = new Object();
if (obj.Test.DelayedLoadPanel == undefined)
obj.Test.DelayedLoadPanel = new Object();
}
Test.DelayedLoadPanel.parseOnLoadScripts = function () {
var scripts = $("script[name='Test.DelayedLoadPanel.OnLoaded']");
for (var i = 0; i < scripts.length; i++) {
eval(scripts[i].text);
}
scripts.remove();
}
Test.DelayedLoadPanel.saveScrollPosition = function() {
Test.DelayedLoadPanel.currentScrollX = $(window).scrollLeft();
Test.DelayedLoadPanel.currentScrollY = $(window).scrollTop();
}
Test.DelayedLoadPanel.restoreScrollPosition = function() {
$(window).scrollLeft(Test.DelayedLoadPanel.currentScrollX);
$(window).scrollTop(Test.DelayedLoadPanel.currentScrollY);
}
$(document).ready(function() {
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_pageLoading(Test.DelayedLoadPanel.saveScrollPosition);
prm.add_endRequest(Test.DelayedLoadPanel.restoreScrollPosition);
prm.add_endRequest(Test.DelayedLoadPanel.parseOnLoadScripts);
});
Base.js
var Test = new Object();
Test.postBackQueue = new Array();
Test.doPostBack = function (target, parameter) {
var prm = Sys.WebForms.PageRequestManager.getInstance();
if ((typeof target != "undefined") && (typeof parameter != "undefined"))
Test.postBackQueue.push({ target: target, parameter: parameter });
// If we're not in the middle of doing a post back then go ahead and fire the first item in the queue.
if ((!prm.get_isInAsyncPostBack()) && (Test.postBackQueue.length > 0)) {
var postbackInfo = Test.postBackQueue.shift();
__doPostBack(postbackInfo.target, postbackInfo.parameter);
}
}
Test.postBackEndRequestHandler = function () {
Test.doPostBack();
};
示例用法
<a href="cat-management.aspx?view=cats&TimeFrame=3" class="metricWidget">
<div class="widgetHeader">
<img src="/images/cat.png" class="widgetIcon" />
<div class="widgetHeaderText">proactive tickets</div>
</div>
<Test:DelayedLoadPanel runat="server" ID="catPanel" OnDelayedLoad="catDelayedLoad" MaxAsyncCallCount="2">
<LoadedContent>
<div class="widgetSubHeader">Type</div>
<div class="widgetSubPanelLeft">Fluffy <span class="widgetMetric widgetMetricPadding" runat="server" id="fluffyCats"></span></div>
<div class="widgetSubPanelRight">Orange <span class="widgetMetric widgetMetricPadding" runat="server" id="orangeCats"></span></div>
</LoadedContent>
<UnloadedContent>
<img class="widgetLoader" />
</UnloadedContent>
</Test:DelayedLoadPanel>
</a>
代码隐藏
protected void catDelayedLoad(object sender, EventArgs eventArgs)
{
var catCount = Utility.catService.GetCatCount(currentOrg, from,
to);
fluffyCats.InnerText = catCount.FluffyCats.ToString();
orangeCats.InnerText = catCount.OrangeCats.ToString();
}
问题是由 session locking 引起的,它阻止了两个请求同时访问会话状态。我能够通过将 Page 指令的 EnableSessionState 属性设置为 ReadOnly 来解决这个问题。万岁!