//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ 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 _allUpdatePanels; private List _updatePanelsToRefresh; private List _childUpdatePanelsToRefresh; private List _asyncPostBackControls; private List _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 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 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(_allUpdatePanels.Count); _childUpdatePanelsToRefresh = new List(_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(); } // 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(); } // 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(); } 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 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: // // Writing directly to the writer is more performant than building // up a big string with formatting and then writing it out later. writer.Write(@" "); } private static void RenderUpdatePanelIDsFromList(HtmlTextWriter writer, List 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 { public bool ContainsControl(Control control) { foreach (ScriptDataItem dataItem in this) { if (dataItem.Control == control) { return true; } } return false; } } private enum IDType { UniqueID, Both } } }