e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1000 lines
45 KiB
C#
1000 lines
45 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="PageRequestManager.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.UI {
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Security;
|
|
using System.Text;
|
|
using System.Web;
|
|
using System.Web.Configuration;
|
|
using System.Web.UI;
|
|
using System.Web.UI.HtmlControls;
|
|
using System.Web.Resources;
|
|
using System.Web.Script.Serialization;
|
|
using System.Reflection;
|
|
using System.Security.Permissions;
|
|
|
|
internal sealed class PageRequestManager {
|
|
|
|
// Type tokens for partial rendering format
|
|
internal const string UpdatePanelVersionToken = "#";
|
|
internal const string UpdatePanelVersionNumber = "4";
|
|
internal const string PageRedirectToken = "pageRedirect";
|
|
internal const string HiddenFieldToken = "hiddenField";
|
|
private const string AsyncPostBackControlIDsToken = "asyncPostBackControlIDs";
|
|
private const string PostBackControlIDsToken = "postBackControlIDs";
|
|
private const string UpdatePanelIDsToken = "updatePanelIDs";
|
|
private const string AsyncPostBackTimeoutToken = "asyncPostBackTimeout";
|
|
private const string ChildUpdatePanelIDsToken = "childUpdatePanelIDs";
|
|
private const string UpdatePanelsToRefreshToken = "panelsToRefreshIDs";
|
|
private const string FormActionToken = "formAction";
|
|
private const string DataItemToken = "dataItem";
|
|
private const string DataItemJsonToken = "dataItemJson";
|
|
internal const string ArrayDeclarationToken = "arrayDeclaration";
|
|
internal const string ExpandoToken = "expando";
|
|
internal const string OnSubmitToken = "onSubmit";
|
|
internal const string ScriptBlockToken = "scriptBlock";
|
|
internal const string ScriptStartupBlockToken = "scriptStartupBlock";
|
|
internal const string ScriptDisposeToken = "scriptDispose";
|
|
internal const string ErrorToken = "error";
|
|
internal const string AsyncPostBackErrorKey = "System.Web.UI.PageRequestManager:AsyncPostBackError";
|
|
internal const string AsyncPostBackErrorMessageKey = "System.Web.UI.PageRequestManager:AsyncPostBackErrorMessage";
|
|
internal const string AsyncPostBackErrorHttpCodeKey = "System.Web.UI.PageRequestManager:AsyncPostBackErrorHttpCode";
|
|
internal const string AsyncPostBackRedirectLocationKey = "System.Web.UI.PageRequestManager:AsyncPostBackRedirectLocation";
|
|
private const string PageTitleToken = "pageTitle";
|
|
private const string FocusToken = "focus";
|
|
private const string AsyncPostFormField = "__ASYNCPOST";
|
|
|
|
private const char LengthEncodeDelimiter = '|';
|
|
private static readonly Version MinimumW3CDomVersion = new Version(1, 0);
|
|
private static readonly Version MinimumEcmaScriptVersion = new Version(1, 0);
|
|
|
|
private ScriptManager _owner;
|
|
|
|
private List<UpdatePanel> _allUpdatePanels;
|
|
private List<UpdatePanel> _updatePanelsToRefresh;
|
|
private List<UpdatePanel> _childUpdatePanelsToRefresh;
|
|
private List<Control> _asyncPostBackControls;
|
|
private List<Control> _postBackControls;
|
|
private ScriptDataItemCollection _scriptDataItems;
|
|
private string _updatePanelRequiresUpdate;
|
|
private string[] _updatePanelsRequireUpdate;
|
|
private HtmlTextWriter _updatePanelWriter;
|
|
private bool _panelsInitialized;
|
|
private string _asyncPostBackSourceElementID;
|
|
|
|
// Stolen from Whidbey Page.cs for focus support
|
|
private static readonly Version FocusMinimumEcmaVersion = new Version("1.4");
|
|
private static readonly Version FocusMinimumJScriptVersion = new Version("3.0");
|
|
private string _focusedControlID;
|
|
private Control _focusedControl;
|
|
private bool _requireFocusScript;
|
|
|
|
public PageRequestManager(ScriptManager owner) {
|
|
Debug.Assert(owner != null);
|
|
_owner = owner;
|
|
}
|
|
|
|
|
|
public string AsyncPostBackSourceElementID {
|
|
get {
|
|
if (_asyncPostBackSourceElementID == null) {
|
|
return String.Empty;
|
|
}
|
|
return _asyncPostBackSourceElementID;
|
|
}
|
|
}
|
|
|
|
// Stolen from Whidbey Page.cs
|
|
private bool ClientSupportsFocus {
|
|
get {
|
|
HttpBrowserCapabilitiesBase browser = _owner.IPage.Request.Browser;
|
|
return
|
|
(browser.EcmaScriptVersion >= FocusMinimumEcmaVersion) ||
|
|
(browser.JScriptVersion >= FocusMinimumJScriptVersion);
|
|
}
|
|
}
|
|
|
|
private bool EnableLegacyRendering {
|
|
get {
|
|
return _owner.EnableLegacyRendering;
|
|
}
|
|
}
|
|
|
|
[SecuritySafeCritical()]
|
|
private bool CustomErrorsSectionHasRedirect(int httpCode) {
|
|
bool hasRedirect = (_owner.CustomErrorsSection.DefaultRedirect != null);
|
|
if (!hasRedirect) {
|
|
if (_owner.CustomErrorsSection.Errors != null) {
|
|
foreach (CustomError error in _owner.CustomErrorsSection.Errors) {
|
|
if (error.StatusCode == httpCode) {
|
|
hasRedirect = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return hasRedirect;
|
|
}
|
|
|
|
// Optimized version of EncodeString that writes directly to a writer. This
|
|
// eliminates the need to create several copies of the same string as well
|
|
// as a StringBuilder.
|
|
internal static void EncodeString(TextWriter writer, string type, string id, string content) {
|
|
Debug.Assert(!String.IsNullOrEmpty(type), "Type should always be specified");
|
|
if (id == null) {
|
|
id = String.Empty;
|
|
}
|
|
if (content == null) {
|
|
content = String.Empty;
|
|
}
|
|
Debug.Assert(type.IndexOf(LengthEncodeDelimiter) == -1, "Should not be a " + LengthEncodeDelimiter + " in type");
|
|
Debug.Assert(id.IndexOf(LengthEncodeDelimiter) == -1, "Should not be a " + LengthEncodeDelimiter + " in id");
|
|
|
|
// len|type|id|content|
|
|
// ------- len
|
|
|
|
writer.Write(content.Length.ToString(CultureInfo.InvariantCulture));
|
|
writer.Write(LengthEncodeDelimiter);
|
|
writer.Write(type);
|
|
writer.Write(LengthEncodeDelimiter);
|
|
writer.Write(id);
|
|
writer.Write(LengthEncodeDelimiter);
|
|
// DevDiv 75383: We used to escape null characters from the content, but this had a non trivial hit on perf
|
|
// They were escaped because XMLHttpRequest in IE truncates content after a null character.
|
|
// However, when HTML contains a null character, subsequent content is truncated anyway, so the value of escaping nulls
|
|
// in the first place is not clear and it was decided it is not worth the perf hit.
|
|
writer.Write(content);
|
|
writer.Write(LengthEncodeDelimiter);
|
|
}
|
|
|
|
private string GetAllUpdatePanelIDs() {
|
|
return GetUpdatePanelIDsFromList(_allUpdatePanels, IDType.Both, true);
|
|
}
|
|
|
|
private string GetAsyncPostBackControlIDs(bool includeQuotes) {
|
|
return GetControlIDsFromList(_asyncPostBackControls, includeQuotes);
|
|
}
|
|
|
|
private string GetChildUpdatePanelIDs() {
|
|
return GetUpdatePanelIDsFromList(_childUpdatePanelsToRefresh, IDType.UniqueID, false);
|
|
}
|
|
|
|
private static string GetControlIDsFromList(List<Control> list, bool includeQuotes) {
|
|
if (list != null && list.Count > 0) {
|
|
StringBuilder idList = new StringBuilder();
|
|
bool first = true;
|
|
for (int i = 0; i < list.Count; i++) {
|
|
var control = list[i];
|
|
if (!control.Visible) {
|
|
// If the panel isn't visible, the client doesn't need to know about it
|
|
continue;
|
|
}
|
|
if (!first) {
|
|
idList.Append(',');
|
|
}
|
|
first = false;
|
|
if (includeQuotes) {
|
|
idList.Append('\'');
|
|
}
|
|
idList.Append(control.UniqueID);
|
|
if (includeQuotes) {
|
|
idList.Append('\'');
|
|
}
|
|
if (control.EffectiveClientIDMode == ClientIDMode.AutoID) {
|
|
if (includeQuotes) {
|
|
idList.Append(",''");
|
|
}
|
|
else {
|
|
idList.Append(',');
|
|
}
|
|
}
|
|
else {
|
|
if (includeQuotes) {
|
|
idList.Append(",'");
|
|
idList.Append(control.ClientID);
|
|
idList.Append('\'');
|
|
}
|
|
else {
|
|
idList.Append(',');
|
|
idList.Append(control.ClientID);
|
|
}
|
|
}
|
|
}
|
|
return idList.ToString();
|
|
}
|
|
return String.Empty;
|
|
}
|
|
|
|
private static Exception GetControlRegistrationException(Control control) {
|
|
// DevDiv Bugs 145573: It is ok to register the Page as an async/postback control
|
|
if (control == null) {
|
|
return new ArgumentNullException("control");
|
|
}
|
|
if (!(control is INamingContainer) &&
|
|
!(control is IPostBackDataHandler) &&
|
|
!(control is IPostBackEventHandler)) {
|
|
return new ArgumentException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.ScriptManager_InvalidControlRegistration, control.ID));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// This code is roughly stolen from HttpException.GetHttpCodeForException()
|
|
private static int GetHttpCodeForException(Exception e) {
|
|
HttpException he = e as HttpException;
|
|
if (he != null) {
|
|
return he.GetHttpCode();
|
|
}
|
|
else if (e is UnauthorizedAccessException) {
|
|
return 401;
|
|
}
|
|
else if (e is PathTooLongException) {
|
|
return 414;
|
|
}
|
|
|
|
// If there is an inner exception, try to get the code from it
|
|
if (e.InnerException != null)
|
|
return GetHttpCodeForException(e.InnerException);
|
|
|
|
// If all else fails, use 500
|
|
return 500;
|
|
}
|
|
|
|
private static string GetMasterPageUniqueID(Page page) {
|
|
// return the UniqueID of the root master page, if any.
|
|
// The root master page has the full UniqueID prefix that
|
|
// all controls will have at the start of their 'UniqueID',
|
|
// counter intuitively it is not the last Master Page with this
|
|
// full uniqueID.
|
|
MasterPage m = page.Master;
|
|
if (m != null) {
|
|
while (m.Master != null) {
|
|
m = m.Master;
|
|
}
|
|
return m.UniqueID;
|
|
}
|
|
return String.Empty;
|
|
}
|
|
|
|
private string GetPostBackControlIDs(bool includeQuotes) {
|
|
return GetControlIDsFromList(_postBackControls, includeQuotes);
|
|
}
|
|
|
|
private string GetRefreshingUpdatePanelIDs() {
|
|
return GetUpdatePanelIDsFromList(_updatePanelsToRefresh, IDType.Both, false);
|
|
}
|
|
|
|
private static string GetUpdatePanelIDsFromList(List<UpdatePanel> list, IDType idType, bool includeChildrenAsTriggersPrefix) {
|
|
if (list != null && list.Count > 0) {
|
|
StringBuilder updatePanelIDs = new StringBuilder();
|
|
bool first = true;
|
|
for (int i = 0; i < list.Count; i++) {
|
|
var up = list[i];
|
|
if (!up.Visible) {
|
|
// If the panel isn't visible, the client doesn't need to know about it
|
|
continue;
|
|
}
|
|
if (!first) {
|
|
updatePanelIDs.Append(',');
|
|
}
|
|
first = false;
|
|
// We send down the UniqueID instead of the ClientID because
|
|
// we need both versions on the client. You can convert from
|
|
// UniqueID to ClientID, but not back.
|
|
|
|
// If the UpdatePanel has its ClientID set, we cannot convert
|
|
// it to UniqueID, so we send both.
|
|
|
|
// We also send down a bool indicating whether the children of
|
|
// the panel count as triggers or not.
|
|
if (includeChildrenAsTriggersPrefix) {
|
|
updatePanelIDs.Append(up.ChildrenAsTriggers ? 't' : 'f');
|
|
}
|
|
updatePanelIDs.Append(up.UniqueID);
|
|
if (idType == IDType.Both) {
|
|
updatePanelIDs.Append(',');
|
|
if (up.EffectiveClientIDMode != ClientIDMode.AutoID) {
|
|
updatePanelIDs.Append(up.ClientID);
|
|
}
|
|
}
|
|
}
|
|
return updatePanelIDs.ToString();
|
|
}
|
|
return String.Empty;
|
|
}
|
|
|
|
internal static bool IsAsyncPostBackRequest(HttpRequestBase request) {
|
|
// Detect the header for async postbacks. A header can appear
|
|
// multiple times, and each header entry can contain a comma-separated
|
|
// list of values. ASP.NET doesn't split the comma-separated values for
|
|
// us so we have to do it.
|
|
|
|
// We used to use the Pragma header but some browsers, such as Opera,
|
|
// do not support sending it through XMLHttpRequest. Instead we use a
|
|
// custom header, X-MicrosoftAjax.
|
|
string[] headerValues = request.Headers.GetValues("X-MicrosoftAjax");
|
|
if (headerValues != null) {
|
|
for (int i = 0; i < headerValues.Length; i++) {
|
|
string[] headerContents = headerValues[i].Split(',');
|
|
for (int j = 0; j < headerContents.Length; j++) {
|
|
if (headerContents[j].Trim() == "Delta=true") {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// DevDiv Bugs 188713: X-MicrosoftAjax header is stripped by some firewalls
|
|
string asyncPost = request.Form[AsyncPostFormField];
|
|
return !String.IsNullOrEmpty(asyncPost) &&
|
|
(asyncPost.Trim() == "true");
|
|
}
|
|
|
|
internal void LoadPostData(string postDataKey, NameValueCollection postCollection) {
|
|
// Check if the async postback was caused by a specific panel, and if so, force
|
|
// that panel to update, regardless of whether it had any explicit triggers, etc.
|
|
// If the post back data starts with the ScriptManager's UniqueID that means the
|
|
// async postback was caused by a control outside of an UpdatePanel, and the rest
|
|
// of the string is the UniqueID of that control.
|
|
string postBackSourceInfo = postCollection[postDataKey];
|
|
if (postBackSourceInfo != null) {
|
|
string postBackTarget; // The target of the postback - either the ScriptManager or an UpdatePanel
|
|
|
|
int indexOfPipe = postBackSourceInfo.IndexOf('|');
|
|
if (indexOfPipe != -1) {
|
|
// We have a target and source element
|
|
postBackTarget = postBackSourceInfo.Substring(0, indexOfPipe);
|
|
_asyncPostBackSourceElementID = postBackSourceInfo.Substring(indexOfPipe + 1);
|
|
}
|
|
else {
|
|
// We only have a target
|
|
postBackTarget = postBackSourceInfo;
|
|
_asyncPostBackSourceElementID = String.Empty;
|
|
}
|
|
|
|
if (postBackTarget != _owner.UniqueID) {
|
|
if (postBackTarget.IndexOf(',') != -1) {
|
|
_updatePanelRequiresUpdate = null;
|
|
_updatePanelsRequireUpdate = postBackTarget.Split(',');
|
|
}
|
|
else {
|
|
_updatePanelRequiresUpdate = postBackTarget;
|
|
_updatePanelsRequireUpdate = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize all UpdatePanels (and their triggers, specifically) so that
|
|
// they can hook events, etc. before other controls can process their
|
|
// own post data.
|
|
// LoadPostData on ScriptManager only gets called during async posts, and
|
|
// is guaranteed to be called before any other controls have a chance to
|
|
// process their post data.
|
|
// During regular posts the UpdatePanel initializes itself in OnLoad.
|
|
if ((_allUpdatePanels != null) && (_allUpdatePanels.Count != 0)) {
|
|
foreach (UpdatePanel panel in _allUpdatePanels) {
|
|
panel.Initialize();
|
|
}
|
|
}
|
|
|
|
_panelsInitialized = true;
|
|
}
|
|
|
|
internal void OnInit() {
|
|
// Check if the browser supports partial rendering. We only do the check
|
|
// if the user hasn't already forced the feature to be on or off explicitly.
|
|
if (_owner.EnablePartialRendering && !_owner._supportsPartialRenderingSetByUser) {
|
|
HttpBrowserCapabilitiesBase browser = _owner.IPage.Request.Browser;
|
|
// There is no browser cap that directly tells us whether the browser
|
|
// supports XmlHttpRequest so we use the next best capability, which is
|
|
// the SupportsCallback property.
|
|
// Checking the other properties helps exclude agents such as crawlers.
|
|
bool supportsPartialRendering =
|
|
(browser.W3CDomVersion >= MinimumW3CDomVersion) &&
|
|
(browser.EcmaScriptVersion >= MinimumEcmaScriptVersion) &&
|
|
browser.SupportsCallback;
|
|
if (supportsPartialRendering) {
|
|
// If we still think we want to support it, now do a more expensive
|
|
// check for XHTML legacy rendering support.
|
|
supportsPartialRendering = !EnableLegacyRendering;
|
|
}
|
|
_owner.SupportsPartialRendering = supportsPartialRendering;
|
|
}
|
|
|
|
if (_owner.IsInAsyncPostBack) {
|
|
_owner.IPage.Error += OnPageError;
|
|
}
|
|
}
|
|
|
|
private void OnPageError(object sender, EventArgs e) {
|
|
Exception ex = _owner.IPage.Server.GetLastError();
|
|
_owner.OnAsyncPostBackError(new AsyncPostBackErrorEventArgs(ex));
|
|
|
|
string errorMessage = _owner.AsyncPostBackErrorMessage;
|
|
if (String.IsNullOrEmpty(errorMessage) && !_owner.Control.Context.IsCustomErrorEnabled) {
|
|
// Only use the exception's message if we're not doing custom errors
|
|
errorMessage = ex.Message;
|
|
}
|
|
|
|
int httpCode = GetHttpCodeForException(ex);
|
|
|
|
bool showAsyncErrorMessage = false;
|
|
|
|
if (_owner.AllowCustomErrorsRedirect && _owner.Control.Context.IsCustomErrorEnabled) {
|
|
// Figure out if there's going to be a redirect for this error
|
|
bool hasRedirect = CustomErrorsSectionHasRedirect(httpCode);
|
|
if (!hasRedirect) {
|
|
// If there's no redirect, we need to send back the error message
|
|
showAsyncErrorMessage = true;
|
|
}
|
|
// If there was a redirect we do nothing since ASP.NET will automatically
|
|
// redirect the user to the error page anyway. This way we don't have to
|
|
// worry about how to resolve the paths from config.
|
|
}
|
|
else {
|
|
// If we're not going to use custom errors, just send back the error message
|
|
showAsyncErrorMessage = true;
|
|
}
|
|
|
|
if (showAsyncErrorMessage) {
|
|
IDictionary items = _owner.Control.Context.Items;
|
|
items[AsyncPostBackErrorKey] = true;
|
|
items[AsyncPostBackErrorMessageKey] = errorMessage;
|
|
items[AsyncPostBackErrorHttpCodeKey] = httpCode;
|
|
}
|
|
}
|
|
|
|
internal void OnPreRender() {
|
|
_owner.IPage.SetRenderMethodDelegate(RenderPageCallback);
|
|
}
|
|
|
|
private void ProcessFocus(HtmlTextWriter writer) {
|
|
// Roughly stolen from Whidbey Page.cs
|
|
if (_requireFocusScript) {
|
|
Debug.Assert(ClientSupportsFocus, "If ClientSupportsFocus is false then we never should have set _requireFocusScript to true.");
|
|
string focusedControlId = String.Empty;
|
|
|
|
// Someone calling SetFocus(controlId) has the most precedent
|
|
if (!String.IsNullOrEmpty(_focusedControlID)) {
|
|
focusedControlId = _focusedControlID;
|
|
}
|
|
else {
|
|
if (_focusedControl != null && _focusedControl.Visible) {
|
|
focusedControlId = _focusedControl.ClientID;
|
|
}
|
|
}
|
|
if (focusedControlId.Length > 0) {
|
|
// Register focus script library
|
|
string focusResourceUrl = _owner.GetScriptResourceUrl("Focus.js", typeof(HtmlForm).Assembly);
|
|
EncodeString(writer, ScriptBlockToken, "ScriptPath", focusResourceUrl);
|
|
|
|
// Send the target control ID to the client
|
|
EncodeString(writer, FocusToken, String.Empty, focusedControlId);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ProcessScriptRegistration(HtmlTextWriter writer) {
|
|
_owner.ScriptRegistration.RenderActiveArrayDeclarations(_updatePanelsToRefresh, writer);
|
|
_owner.ScriptRegistration.RenderActiveScripts(_updatePanelsToRefresh, writer);
|
|
_owner.ScriptRegistration.RenderActiveSubmitStatements(_updatePanelsToRefresh, writer);
|
|
_owner.ScriptRegistration.RenderActiveExpandos(_updatePanelsToRefresh, writer);
|
|
_owner.ScriptRegistration.RenderActiveHiddenFields(_updatePanelsToRefresh, writer);
|
|
_owner.ScriptRegistration.RenderActiveScriptDisposes(_updatePanelsToRefresh, writer);
|
|
}
|
|
|
|
private void ProcessUpdatePanels() {
|
|
Debug.Assert(_owner.IsInAsyncPostBack);
|
|
Debug.Assert(_updatePanelsToRefresh == null);
|
|
|
|
if (_allUpdatePanels != null) {
|
|
_updatePanelsToRefresh = new List<UpdatePanel>(_allUpdatePanels.Count);
|
|
_childUpdatePanelsToRefresh = new List<UpdatePanel>(_allUpdatePanels.Count);
|
|
|
|
// Process the UpdatePanels to determine which are to be set in
|
|
// partial rendering mode.
|
|
|
|
// We need to process the list such that parent UpdatePanels are
|
|
// evaluated first. A child UpdatePanel inside a parent that is being
|
|
// updated should not be considered in partial rendering mode.
|
|
// Ordinarily child controls get initialized first before their parents
|
|
// so you'd expect the list to be in reverse order, but this isn't the case.
|
|
// UpdatePanels instantiate their templates in their OnInit, so a child
|
|
// UpdatePanel only exists in the control tree after the parent has been
|
|
// initialized.
|
|
|
|
HtmlForm form = _owner.Page.Form;
|
|
|
|
for (int i = 0; i < _allUpdatePanels.Count; i++) {
|
|
UpdatePanel panel = _allUpdatePanels[i];
|
|
|
|
// Check whether the panel thinks it wants to update. Possible reasons
|
|
// a panel might be updating:
|
|
// - Postback data indicates the postback came from within the panel
|
|
// - Postback data indicates the postbacks was caused by PageRequestManager.beginAsyncPost
|
|
// and the update panel was explicitly requested to update
|
|
// - Explicit call to panel.Update()
|
|
// - Panel UpdateMode set to Always
|
|
// - Trigger fired (not yet implemented)
|
|
|
|
bool requiresUpdate = panel.RequiresUpdate ||
|
|
(_updatePanelRequiresUpdate != null && String.Equals(panel.UniqueID, _updatePanelRequiresUpdate, StringComparison.Ordinal)) ||
|
|
(_updatePanelsRequireUpdate != null && Array.IndexOf(_updatePanelsRequireUpdate, panel.UniqueID) != -1);
|
|
|
|
// Check and see if a parent panel will take update this panel, whether
|
|
// this panel wants to update or not. If so, then this panel doesn't need
|
|
// to be in update mode since it will get included in the rendering
|
|
// by its parent anyway.
|
|
// If this parent doesn't want to update then we don't need to do any
|
|
// additional checks because whether it renders depends entirely on
|
|
// whether the parent wants to render.
|
|
Control parent = panel.Parent;
|
|
while (parent != form) {
|
|
UpdatePanel parentUpdatePanel = parent as UpdatePanel;
|
|
if ((parentUpdatePanel != null) &&
|
|
(_updatePanelsToRefresh.Contains(parentUpdatePanel) || _childUpdatePanelsToRefresh.Contains(parentUpdatePanel))) {
|
|
// This panel is inside another UpdatePanel that is being
|
|
// rendered, so it should render in normal mode.
|
|
requiresUpdate = false;
|
|
_childUpdatePanelsToRefresh.Add(panel);
|
|
break;
|
|
}
|
|
|
|
parent = parent.Parent;
|
|
|
|
if (parent == null) {
|
|
// This UpdatePanel was not inside an HtmlForm
|
|
// This really shouldn't happen, because the UpdatePanel would have thrown
|
|
// an exception on the initial GET request that it should be inside a form,
|
|
// so we'll just ignore it now...
|
|
requiresUpdate = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (requiresUpdate) {
|
|
panel.SetAsyncPostBackMode(true);
|
|
_updatePanelsToRefresh.Add(panel);
|
|
}
|
|
else {
|
|
panel.SetAsyncPostBackMode(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RegisterAsyncPostBackControl(Control control) {
|
|
Exception ex = GetControlRegistrationException(control);
|
|
if (ex != null) {
|
|
throw ex;
|
|
}
|
|
if (_postBackControls != null && _postBackControls.Contains(control)) {
|
|
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.ScriptManager_CannotRegisterBothPostBacks, control.ID));
|
|
}
|
|
if (_asyncPostBackControls == null) {
|
|
_asyncPostBackControls = new List<Control>();
|
|
}
|
|
// It is acceptable to register the same control twice since the same
|
|
// control might be referred to by more than one trigger.
|
|
if (!_asyncPostBackControls.Contains(control)) {
|
|
_asyncPostBackControls.Add(control);
|
|
}
|
|
}
|
|
|
|
public void RegisterDataItem(Control control, string dataItem, bool isJsonSerialized) {
|
|
if (control == null) {
|
|
throw new ArgumentNullException("control");
|
|
}
|
|
if (!_owner.IsInAsyncPostBack) {
|
|
throw new InvalidOperationException(AtlasWeb.PageRequestManager_RegisterDataItemInNonAsyncRequest);
|
|
}
|
|
if (_scriptDataItems == null) {
|
|
_scriptDataItems = new ScriptDataItemCollection();
|
|
}
|
|
else {
|
|
if (_scriptDataItems.ContainsControl(control)) {
|
|
throw new ArgumentException(
|
|
String.Format(
|
|
CultureInfo.InvariantCulture,
|
|
AtlasWeb.PageRequestManager_RegisterDataItemTwice,
|
|
control.ID),
|
|
"control");
|
|
}
|
|
}
|
|
_scriptDataItems.Add(new ScriptDataItem(control, dataItem, isJsonSerialized));
|
|
}
|
|
|
|
private void RegisterFocusScript() {
|
|
if (ClientSupportsFocus && !_requireFocusScript) {
|
|
_requireFocusScript = true;
|
|
}
|
|
}
|
|
|
|
public void RegisterPostBackControl(Control control) {
|
|
Exception ex = GetControlRegistrationException(control);
|
|
if (ex != null) {
|
|
throw ex;
|
|
}
|
|
if (_asyncPostBackControls != null && _asyncPostBackControls.Contains(control)) {
|
|
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.ScriptManager_CannotRegisterBothPostBacks, control.ID));
|
|
}
|
|
if (_postBackControls == null) {
|
|
_postBackControls = new List<Control>();
|
|
}
|
|
// It is acceptable to register the same control twice since the same
|
|
// control might be referred to by more than one trigger.
|
|
if (!_postBackControls.Contains(control)) {
|
|
_postBackControls.Add(control);
|
|
}
|
|
}
|
|
|
|
internal void RegisterUpdatePanel(UpdatePanel updatePanel) {
|
|
Debug.Assert(updatePanel != null);
|
|
if (_allUpdatePanels == null) {
|
|
_allUpdatePanels = new List<UpdatePanel>();
|
|
}
|
|
Debug.Assert(!_allUpdatePanels.Contains(updatePanel),
|
|
String.Format(CultureInfo.InvariantCulture, "The UpdatePanel with ID '{0}' has already been registered with the ScriptManager.", updatePanel.ID));
|
|
_allUpdatePanels.Add(updatePanel);
|
|
|
|
if (_panelsInitialized) {
|
|
// Do catch-up for panels that may have been added later in
|
|
// the lifecycle during an async post.
|
|
Debug.Assert(_owner.IsInAsyncPostBack, "Catch-up initialization should only be done in async posts.");
|
|
updatePanel.Initialize();
|
|
}
|
|
}
|
|
|
|
// Only call this method when these condictions are met:
|
|
// if (!((IControl)_owner).DesignMode && !_owner.IsInAsyncPostBack && _owner.SupportsPartialRendering
|
|
// && (_owner.MicrosoftAjaxMode != MicrosoftAjaxMode.Disabled))
|
|
internal void Render(HtmlTextWriter writer) {
|
|
_owner.IPage.VerifyRenderingInServerForm(_owner);
|
|
RenderPageRequestManagerScript(writer);
|
|
}
|
|
|
|
private void RenderFormCallback(HtmlTextWriter writer, Control containerControl) {
|
|
|
|
Debug.Assert(_updatePanelWriter != null, "_updatePanelWriter should be set by RenderPageCallback before RenderFormCallback is called.");
|
|
|
|
// Suppress rendering of default content; instead just render out
|
|
// update panels
|
|
if (_updatePanelsToRefresh != null) {
|
|
foreach (UpdatePanel panel in _updatePanelsToRefresh) {
|
|
if (panel.Visible) {
|
|
// Write UpdatePanels to the response's writer; the writer passed in is a
|
|
// dummy parserWriter. It will contain hidden fields that are written to
|
|
// the response's writer later in RenderPageCallback.
|
|
panel.RenderControl(_updatePanelWriter);
|
|
}
|
|
}
|
|
}
|
|
|
|
IPage page = _owner.IPage;
|
|
if (page.EnableEventValidation) {
|
|
// If the page has event validation turned on, we need to run through
|
|
// the render logic for the rest of the page as well. However, the
|
|
// rendering is essentially ignored.
|
|
// UpdatePanels that were already rendered above do not re-render by checking
|
|
// a flag whether they already rendered.
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// DevDiv 55447: Do not use Response.Flush and a NullStream to prevent Response.Writes
|
|
// from being written to the output stream, as calling Response.Flush causes headers to
|
|
// be written. This prevents cookies from being issued after this, for example.
|
|
// Instead, use a NullWriter that ignores Writes. We can change the writer used by HttpResponse
|
|
// using the internal SwitchWriter method.
|
|
// We must do this since data written via Response.Write will make the partial update
|
|
// response invalid.
|
|
|
|
TextWriter oldWriter = null;
|
|
bool writerSwitched = false;
|
|
try {
|
|
// beginning of possible direct Response.Writes
|
|
oldWriter = page.Response.SwitchWriter(TextWriter.Null);
|
|
// if we cant switch the writer for some reason we need to know not to switch it back again in the finally block
|
|
// writerSwitched will be false
|
|
writerSwitched = true;
|
|
|
|
// nullHtmlWriter captures any writes by controls to the textwriter they are passed.
|
|
// Note that we could possibly just let null TextWriter we switched catch this data, but this
|
|
// is more efficient.
|
|
HtmlTextWriter nullHtmlWriter = new HtmlTextWriter(TextWriter.Null);
|
|
foreach (Control control in containerControl.Controls) {
|
|
control.RenderControl(nullHtmlWriter);
|
|
}
|
|
}
|
|
finally {
|
|
// end of possible direct Response.Writes
|
|
if (writerSwitched) {
|
|
page.Response.SwitchWriter(oldWriter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenderPageCallback(HtmlTextWriter writer, Control pageControl) {
|
|
ProcessUpdatePanels();
|
|
|
|
// Although we could use the pageControl parameter it's hard to write
|
|
// unit tests for it. Instead we just use our own page, which is the
|
|
// same instance anyway (but easier to test with).
|
|
|
|
HttpResponseBase response = _owner.IPage.Response;
|
|
|
|
response.ContentType = "text/plain";
|
|
response.Cache.SetNoServerCaching();
|
|
|
|
// Write out the version identifier, which helps the client-side deal with the response
|
|
// in a back-compatible way when there are changes made server-side.
|
|
EncodeString(writer, UpdatePanelVersionToken, String.Empty, UpdatePanelVersionNumber);
|
|
|
|
// Render the form. It will render its tag, hidden fields, etc.
|
|
// and then call our render method delegate, which will in turn render
|
|
// all the UpdatePanels
|
|
IHtmlForm formControl = _owner.IPage.Form;
|
|
formControl.SetRenderMethodDelegate(RenderFormCallback);
|
|
|
|
// Let updatePanels write directly to Response
|
|
_updatePanelWriter = writer;
|
|
|
|
// Let form header/footer write to a parser
|
|
ParserHtmlTextWriter formWriter = new ParserHtmlTextWriter();
|
|
formControl.RenderControl(formWriter);
|
|
|
|
// Write out built-in ASP.NET hidden fields that were rendered directly by the page
|
|
// or registered through RegisterHiddenField
|
|
var hiddenFields = _owner.IPage.HiddenFieldsToRender;
|
|
if (hiddenFields != null) {
|
|
foreach (KeyValuePair<String, String> entry in hiddenFields) {
|
|
if (ControlUtil.IsBuiltInHiddenField(entry.Key)) {
|
|
EncodeString(writer, HiddenFieldToken, entry.Key, entry.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write out PageRequestManager settings that can change during an async postback.
|
|
// This is required for dynamic UpdatePanels since the list of panels could
|
|
// change.
|
|
EncodeString(writer, AsyncPostBackControlIDsToken, String.Empty, GetAsyncPostBackControlIDs(false));
|
|
EncodeString(writer, PostBackControlIDsToken, String.Empty, GetPostBackControlIDs(false));
|
|
EncodeString(writer, UpdatePanelIDsToken, String.Empty, GetAllUpdatePanelIDs());
|
|
EncodeString(writer, ChildUpdatePanelIDsToken, String.Empty, GetChildUpdatePanelIDs());
|
|
EncodeString(writer, UpdatePanelsToRefreshToken, String.Empty, GetRefreshingUpdatePanelIDs());
|
|
EncodeString(writer, AsyncPostBackTimeoutToken, String.Empty, _owner.AsyncPostBackTimeout.ToString(CultureInfo.InvariantCulture));
|
|
if (formWriter.FormAction != null) {
|
|
EncodeString(writer, FormActionToken, String.Empty, formWriter.FormAction);
|
|
}
|
|
if (_owner.IPage.Header != null) {
|
|
string pageTitle = _owner.IPage.Title;
|
|
if (!String.IsNullOrEmpty(pageTitle)) {
|
|
EncodeString(writer, PageTitleToken, String.Empty, pageTitle);
|
|
}
|
|
}
|
|
RenderDataItems(writer);
|
|
|
|
ProcessScriptRegistration(writer);
|
|
|
|
// We process the focus after regular script registrations to
|
|
// make sure that if it ends up including some script that it
|
|
// executes last.
|
|
ProcessFocus(writer);
|
|
}
|
|
|
|
private void RenderDataItems(HtmlTextWriter writer) {
|
|
if (_scriptDataItems != null) {
|
|
foreach (ScriptDataItem dataItem in _scriptDataItems) {
|
|
EncodeString(
|
|
writer,
|
|
dataItem.IsJsonSerialized ? DataItemJsonToken : DataItemToken,
|
|
dataItem.Control.ClientID,
|
|
dataItem.DataItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void RenderPageRequestManagerScript(HtmlTextWriter writer) {
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Script format:
|
|
// <script type=""text/javascript"">
|
|
// //<![CDATA[
|
|
// Sys.WebForms.PageRequestManager._initialize('{0}', '{1}', [{2}], [{3}], [{4}], {5}, {6});
|
|
// //]]>
|
|
// </script>
|
|
|
|
// Writing directly to the writer is more performant than building
|
|
// up a big string with formatting and then writing it out later.
|
|
|
|
writer.Write(@"<script type=""text/javascript"">
|
|
//<![CDATA[
|
|
Sys.WebForms.PageRequestManager._initialize('");
|
|
writer.Write(_owner.UniqueID);
|
|
writer.Write(@"', '");
|
|
writer.Write(_owner.IPage.Form.ClientID);
|
|
writer.Write(@"', [");
|
|
RenderUpdatePanelIDsFromList(writer, _allUpdatePanels);
|
|
writer.Write("], [");
|
|
writer.Write(GetAsyncPostBackControlIDs(true));
|
|
writer.Write("], [");
|
|
writer.Write(GetPostBackControlIDs(true));
|
|
writer.Write("], ");
|
|
writer.Write(_owner.AsyncPostBackTimeout.ToString(CultureInfo.InvariantCulture));
|
|
writer.Write(", '");
|
|
writer.Write(GetMasterPageUniqueID(_owner.Page));
|
|
writer.WriteLine("');");
|
|
writer.Write(@"//]]>
|
|
</script>
|
|
");
|
|
}
|
|
|
|
private static void RenderUpdatePanelIDsFromList(HtmlTextWriter writer, List<UpdatePanel> list) {
|
|
// Writing directly to the writer is more performant than building
|
|
// up a big string with formatting and then writing it out later.
|
|
if (list != null && list.Count > 0) {
|
|
bool first = true;
|
|
for (int i = 0; i < list.Count; i++) {
|
|
UpdatePanel up = list[i];
|
|
if (!up.Visible) {
|
|
// If the panel isn't visible, the client doesn't need to know about it
|
|
continue;
|
|
}
|
|
if (!first) {
|
|
writer.Write(',');
|
|
}
|
|
first = false;
|
|
|
|
// Due to the settable ClientID feature, UpdatePanel
|
|
// needs both the clientID and uniqueID
|
|
// We also send down a bool indicating whether the children of
|
|
// the panel count as triggers or not.
|
|
// ['[t|f]uniqueid1','clientid1','[t|f]uniqueid2','clientid2',...]
|
|
writer.Write("'");
|
|
writer.Write(up.ChildrenAsTriggers ? 't' : 'f');
|
|
writer.Write(up.UniqueID);
|
|
writer.Write("',");
|
|
if (up.EffectiveClientIDMode == ClientIDMode.AutoID) {
|
|
writer.Write("''");
|
|
}
|
|
else {
|
|
writer.Write("'");
|
|
writer.Write(up.ClientID);
|
|
writer.Write("'");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetFocus(Control control) {
|
|
// We always call the real Page's method at least to do parameter validation
|
|
_owner.IPage.SetFocus(control);
|
|
|
|
// If it's not async, just let the page do whatever it wants. If we are
|
|
// in an async post, we need to keep track of what to focus later on.
|
|
if (_owner.IsInAsyncPostBack) {
|
|
_focusedControl = control;
|
|
_focusedControlID = null;
|
|
RegisterFocusScript();
|
|
}
|
|
}
|
|
|
|
public void SetFocus(string clientID) {
|
|
// We always call the real Page's method at least to do parameter validation
|
|
_owner.IPage.SetFocus(clientID);
|
|
SetFocusInternal(clientID);
|
|
}
|
|
|
|
internal void SetFocusInternal(string clientID) {
|
|
// If it's not async, just let the page do whatever it wants. If we are
|
|
// in an async post, we need to keep track of what to focus later on.
|
|
if (_owner.IsInAsyncPostBack) {
|
|
_focusedControlID = clientID.Trim();
|
|
_focusedControl = null;
|
|
RegisterFocusScript();
|
|
}
|
|
}
|
|
|
|
internal void UnregisterUpdatePanel(UpdatePanel updatePanel) {
|
|
Debug.Assert(updatePanel != null);
|
|
if ((_allUpdatePanels == null) || !_allUpdatePanels.Contains(updatePanel)) {
|
|
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.ScriptManager_UpdatePanelNotRegistered, updatePanel.ID), "updatePanel");
|
|
}
|
|
_allUpdatePanels.Remove(updatePanel);
|
|
}
|
|
|
|
private sealed class ParserHtmlTextWriter : HtmlTextWriter {
|
|
private bool _writingForm;
|
|
private string _formAction;
|
|
|
|
public ParserHtmlTextWriter() : base(TextWriter.Null) {
|
|
}
|
|
|
|
public string FormAction {
|
|
get {
|
|
return _formAction;
|
|
}
|
|
}
|
|
|
|
public override void WriteBeginTag(string tagName) {
|
|
base.WriteBeginTag(tagName);
|
|
|
|
_writingForm = (tagName == "form");
|
|
}
|
|
|
|
public override void WriteAttribute(string name, string value, bool fEncode) {
|
|
base.WriteAttribute(name, value, fEncode);
|
|
|
|
if (_writingForm) {
|
|
if (name == "action") {
|
|
_formAction = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class ScriptDataItem {
|
|
private Control _control;
|
|
private string _dataItem;
|
|
private bool _isJsonSerialized;
|
|
|
|
public ScriptDataItem(Control control, string dataItem, bool isJsonSerialized) {
|
|
_control = control;
|
|
_dataItem = (dataItem == null) ? String.Empty : dataItem;
|
|
_isJsonSerialized = isJsonSerialized;
|
|
}
|
|
|
|
public Control Control {
|
|
get {
|
|
return _control;
|
|
}
|
|
}
|
|
|
|
public string DataItem {
|
|
get {
|
|
return _dataItem;
|
|
}
|
|
}
|
|
|
|
public bool IsJsonSerialized {
|
|
get {
|
|
return _isJsonSerialized;
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class ScriptDataItemCollection : List<ScriptDataItem> {
|
|
public bool ContainsControl(Control control) {
|
|
foreach (ScriptDataItem dataItem in this) {
|
|
if (dataItem.Control == control) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private enum IDType {
|
|
UniqueID,
|
|
Both
|
|
}
|
|
}
|
|
}
|