1 //------------------------------------------------------------------------------ 2 // <copyright file="PageRequestManager.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 //------------------------------------------------------------------------------ 6 7 namespace System.Web.UI { 8 using System; 9 using System.Collections; 10 using System.Collections.Generic; 11 using System.Collections.Specialized; 12 using System.Diagnostics; 13 using System.Globalization; 14 using System.IO; 15 using System.Security; 16 using System.Text; 17 using System.Web; 18 using System.Web.Configuration; 19 using System.Web.UI; 20 using System.Web.UI.HtmlControls; 21 using System.Web.Resources; 22 using System.Web.Script.Serialization; 23 using System.Reflection; 24 using System.Security.Permissions; 25 26 internal sealed class PageRequestManager { 27 28 // Type tokens for partial rendering format 29 internal const string UpdatePanelVersionToken = "#"; 30 internal const string UpdatePanelVersionNumber = "4"; 31 internal const string PageRedirectToken = "pageRedirect"; 32 internal const string HiddenFieldToken = "hiddenField"; 33 private const string AsyncPostBackControlIDsToken = "asyncPostBackControlIDs"; 34 private const string PostBackControlIDsToken = "postBackControlIDs"; 35 private const string UpdatePanelIDsToken = "updatePanelIDs"; 36 private const string AsyncPostBackTimeoutToken = "asyncPostBackTimeout"; 37 private const string ChildUpdatePanelIDsToken = "childUpdatePanelIDs"; 38 private const string UpdatePanelsToRefreshToken = "panelsToRefreshIDs"; 39 private const string FormActionToken = "formAction"; 40 private const string DataItemToken = "dataItem"; 41 private const string DataItemJsonToken = "dataItemJson"; 42 internal const string ArrayDeclarationToken = "arrayDeclaration"; 43 internal const string ExpandoToken = "expando"; 44 internal const string OnSubmitToken = "onSubmit"; 45 internal const string ScriptBlockToken = "scriptBlock"; 46 internal const string ScriptStartupBlockToken = "scriptStartupBlock"; 47 internal const string ScriptDisposeToken = "scriptDispose"; 48 internal const string ErrorToken = "error"; 49 internal const string AsyncPostBackErrorKey = "System.Web.UI.PageRequestManager:AsyncPostBackError"; 50 internal const string AsyncPostBackErrorMessageKey = "System.Web.UI.PageRequestManager:AsyncPostBackErrorMessage"; 51 internal const string AsyncPostBackErrorHttpCodeKey = "System.Web.UI.PageRequestManager:AsyncPostBackErrorHttpCode"; 52 internal const string AsyncPostBackRedirectLocationKey = "System.Web.UI.PageRequestManager:AsyncPostBackRedirectLocation"; 53 private const string PageTitleToken = "pageTitle"; 54 private const string FocusToken = "focus"; 55 private const string AsyncPostFormField = "__ASYNCPOST"; 56 57 private const char LengthEncodeDelimiter = '|'; 58 private static readonly Version MinimumW3CDomVersion = new Version(1, 0); 59 private static readonly Version MinimumEcmaScriptVersion = new Version(1, 0); 60 61 private ScriptManager _owner; 62 63 private List<UpdatePanel> _allUpdatePanels; 64 private List<UpdatePanel> _updatePanelsToRefresh; 65 private List<UpdatePanel> _childUpdatePanelsToRefresh; 66 private List<Control> _asyncPostBackControls; 67 private List<Control> _postBackControls; 68 private ScriptDataItemCollection _scriptDataItems; 69 private string _updatePanelRequiresUpdate; 70 private string[] _updatePanelsRequireUpdate; 71 private HtmlTextWriter _updatePanelWriter; 72 private bool _panelsInitialized; 73 private string _asyncPostBackSourceElementID; 74 75 // Stolen from Whidbey Page.cs for focus support 76 private static readonly Version FocusMinimumEcmaVersion = new Version("1.4"); 77 private static readonly Version FocusMinimumJScriptVersion = new Version("3.0"); 78 private string _focusedControlID; 79 private Control _focusedControl; 80 private bool _requireFocusScript; 81 PageRequestManager(ScriptManager owner)82 public PageRequestManager(ScriptManager owner) { 83 Debug.Assert(owner != null); 84 _owner = owner; 85 } 86 87 88 public string AsyncPostBackSourceElementID { 89 get { 90 if (_asyncPostBackSourceElementID == null) { 91 return String.Empty; 92 } 93 return _asyncPostBackSourceElementID; 94 } 95 } 96 97 // Stolen from Whidbey Page.cs 98 private bool ClientSupportsFocus { 99 get { 100 HttpBrowserCapabilitiesBase browser = _owner.IPage.Request.Browser; 101 return 102 (browser.EcmaScriptVersion >= FocusMinimumEcmaVersion) || 103 (browser.JScriptVersion >= FocusMinimumJScriptVersion); 104 } 105 } 106 107 private bool EnableLegacyRendering { 108 get { 109 return _owner.EnableLegacyRendering; 110 } 111 } 112 113 [SecuritySafeCritical()] CustomErrorsSectionHasRedirect(int httpCode)114 private bool CustomErrorsSectionHasRedirect(int httpCode) { 115 bool hasRedirect = (_owner.CustomErrorsSection.DefaultRedirect != null); 116 if (!hasRedirect) { 117 if (_owner.CustomErrorsSection.Errors != null) { 118 foreach (CustomError error in _owner.CustomErrorsSection.Errors) { 119 if (error.StatusCode == httpCode) { 120 hasRedirect = true; 121 break; 122 } 123 } 124 } 125 } 126 return hasRedirect; 127 } 128 129 // Optimized version of EncodeString that writes directly to a writer. This 130 // eliminates the need to create several copies of the same string as well 131 // as a StringBuilder. EncodeString(TextWriter writer, string type, string id, string content)132 internal static void EncodeString(TextWriter writer, string type, string id, string content) { 133 Debug.Assert(!String.IsNullOrEmpty(type), "Type should always be specified"); 134 if (id == null) { 135 id = String.Empty; 136 } 137 if (content == null) { 138 content = String.Empty; 139 } 140 Debug.Assert(type.IndexOf(LengthEncodeDelimiter) == -1, "Should not be a " + LengthEncodeDelimiter + " in type"); 141 Debug.Assert(id.IndexOf(LengthEncodeDelimiter) == -1, "Should not be a " + LengthEncodeDelimiter + " in id"); 142 143 // len|type|id|content| 144 // ------- len 145 146 writer.Write(content.Length.ToString(CultureInfo.InvariantCulture)); 147 writer.Write(LengthEncodeDelimiter); 148 writer.Write(type); 149 writer.Write(LengthEncodeDelimiter); 150 writer.Write(id); 151 writer.Write(LengthEncodeDelimiter); 152 // DevDiv 75383: We used to escape null characters from the content, but this had a non trivial hit on perf 153 // They were escaped because XMLHttpRequest in IE truncates content after a null character. 154 // However, when HTML contains a null character, subsequent content is truncated anyway, so the value of escaping nulls 155 // in the first place is not clear and it was decided it is not worth the perf hit. 156 writer.Write(content); 157 writer.Write(LengthEncodeDelimiter); 158 } 159 GetAllUpdatePanelIDs()160 private string GetAllUpdatePanelIDs() { 161 return GetUpdatePanelIDsFromList(_allUpdatePanels, IDType.Both, true); 162 } 163 GetAsyncPostBackControlIDs(bool includeQuotes)164 private string GetAsyncPostBackControlIDs(bool includeQuotes) { 165 return GetControlIDsFromList(_asyncPostBackControls, includeQuotes); 166 } 167 GetChildUpdatePanelIDs()168 private string GetChildUpdatePanelIDs() { 169 return GetUpdatePanelIDsFromList(_childUpdatePanelsToRefresh, IDType.UniqueID, false); 170 } 171 GetControlIDsFromList(List<Control> list, bool includeQuotes)172 private static string GetControlIDsFromList(List<Control> list, bool includeQuotes) { 173 if (list != null && list.Count > 0) { 174 StringBuilder idList = new StringBuilder(); 175 bool first = true; 176 for (int i = 0; i < list.Count; i++) { 177 var control = list[i]; 178 if (!control.Visible) { 179 // If the panel isn't visible, the client doesn't need to know about it 180 continue; 181 } 182 if (!first) { 183 idList.Append(','); 184 } 185 first = false; 186 if (includeQuotes) { 187 idList.Append('\''); 188 } 189 idList.Append(control.UniqueID); 190 if (includeQuotes) { 191 idList.Append('\''); 192 } 193 if (control.EffectiveClientIDMode == ClientIDMode.AutoID) { 194 if (includeQuotes) { 195 idList.Append(",''"); 196 } 197 else { 198 idList.Append(','); 199 } 200 } 201 else { 202 if (includeQuotes) { 203 idList.Append(",'"); 204 idList.Append(control.ClientID); 205 idList.Append('\''); 206 } 207 else { 208 idList.Append(','); 209 idList.Append(control.ClientID); 210 } 211 } 212 } 213 return idList.ToString(); 214 } 215 return String.Empty; 216 } 217 GetControlRegistrationException(Control control)218 private static Exception GetControlRegistrationException(Control control) { 219 // DevDiv Bugs 145573: It is ok to register the Page as an async/postback control 220 if (control == null) { 221 return new ArgumentNullException("control"); 222 } 223 if (!(control is INamingContainer) && 224 !(control is IPostBackDataHandler) && 225 !(control is IPostBackEventHandler)) { 226 return new ArgumentException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.ScriptManager_InvalidControlRegistration, control.ID)); 227 } 228 return null; 229 } 230 231 // This code is roughly stolen from HttpException.GetHttpCodeForException() GetHttpCodeForException(Exception e)232 private static int GetHttpCodeForException(Exception e) { 233 HttpException he = e as HttpException; 234 if (he != null) { 235 return he.GetHttpCode(); 236 } 237 else if (e is UnauthorizedAccessException) { 238 return 401; 239 } 240 else if (e is PathTooLongException) { 241 return 414; 242 } 243 244 // If there is an inner exception, try to get the code from it 245 if (e.InnerException != null) 246 return GetHttpCodeForException(e.InnerException); 247 248 // If all else fails, use 500 249 return 500; 250 } 251 GetMasterPageUniqueID(Page page)252 private static string GetMasterPageUniqueID(Page page) { 253 // return the UniqueID of the root master page, if any. 254 // The root master page has the full UniqueID prefix that 255 // all controls will have at the start of their 'UniqueID', 256 // counter intuitively it is not the last Master Page with this 257 // full uniqueID. 258 MasterPage m = page.Master; 259 if (m != null) { 260 while (m.Master != null) { 261 m = m.Master; 262 } 263 return m.UniqueID; 264 } 265 return String.Empty; 266 } 267 GetPostBackControlIDs(bool includeQuotes)268 private string GetPostBackControlIDs(bool includeQuotes) { 269 return GetControlIDsFromList(_postBackControls, includeQuotes); 270 } 271 GetRefreshingUpdatePanelIDs()272 private string GetRefreshingUpdatePanelIDs() { 273 return GetUpdatePanelIDsFromList(_updatePanelsToRefresh, IDType.Both, false); 274 } 275 GetUpdatePanelIDsFromList(List<UpdatePanel> list, IDType idType, bool includeChildrenAsTriggersPrefix)276 private static string GetUpdatePanelIDsFromList(List<UpdatePanel> list, IDType idType, bool includeChildrenAsTriggersPrefix) { 277 if (list != null && list.Count > 0) { 278 StringBuilder updatePanelIDs = new StringBuilder(); 279 bool first = true; 280 for (int i = 0; i < list.Count; i++) { 281 var up = list[i]; 282 if (!up.Visible) { 283 // If the panel isn't visible, the client doesn't need to know about it 284 continue; 285 } 286 if (!first) { 287 updatePanelIDs.Append(','); 288 } 289 first = false; 290 // We send down the UniqueID instead of the ClientID because 291 // we need both versions on the client. You can convert from 292 // UniqueID to ClientID, but not back. 293 294 // If the UpdatePanel has its ClientID set, we cannot convert 295 // it to UniqueID, so we send both. 296 297 // We also send down a bool indicating whether the children of 298 // the panel count as triggers or not. 299 if (includeChildrenAsTriggersPrefix) { 300 updatePanelIDs.Append(up.ChildrenAsTriggers ? 't' : 'f'); 301 } 302 updatePanelIDs.Append(up.UniqueID); 303 if (idType == IDType.Both) { 304 updatePanelIDs.Append(','); 305 if (up.EffectiveClientIDMode != ClientIDMode.AutoID) { 306 updatePanelIDs.Append(up.ClientID); 307 } 308 } 309 } 310 return updatePanelIDs.ToString(); 311 } 312 return String.Empty; 313 } 314 IsAsyncPostBackRequest(HttpRequestBase request)315 internal static bool IsAsyncPostBackRequest(HttpRequestBase request) { 316 // Detect the header for async postbacks. A header can appear 317 // multiple times, and each header entry can contain a comma-separated 318 // list of values. ASP.NET doesn't split the comma-separated values for 319 // us so we have to do it. 320 321 // We used to use the Pragma header but some browsers, such as Opera, 322 // do not support sending it through XMLHttpRequest. Instead we use a 323 // custom header, X-MicrosoftAjax. 324 string[] headerValues = request.Headers.GetValues("X-MicrosoftAjax"); 325 if (headerValues != null) { 326 for (int i = 0; i < headerValues.Length; i++) { 327 string[] headerContents = headerValues[i].Split(','); 328 for (int j = 0; j < headerContents.Length; j++) { 329 if (headerContents[j].Trim() == "Delta=true") { 330 return true; 331 } 332 } 333 } 334 } 335 // DevDiv Bugs 188713: X-MicrosoftAjax header is stripped by some firewalls 336 string asyncPost = request.Form[AsyncPostFormField]; 337 return !String.IsNullOrEmpty(asyncPost) && 338 (asyncPost.Trim() == "true"); 339 } 340 LoadPostData(string postDataKey, NameValueCollection postCollection)341 internal void LoadPostData(string postDataKey, NameValueCollection postCollection) { 342 // Check if the async postback was caused by a specific panel, and if so, force 343 // that panel to update, regardless of whether it had any explicit triggers, etc. 344 // If the post back data starts with the ScriptManager's UniqueID that means the 345 // async postback was caused by a control outside of an UpdatePanel, and the rest 346 // of the string is the UniqueID of that control. 347 string postBackSourceInfo = postCollection[postDataKey]; 348 if (postBackSourceInfo != null) { 349 string postBackTarget; // The target of the postback - either the ScriptManager or an UpdatePanel 350 351 int indexOfPipe = postBackSourceInfo.IndexOf('|'); 352 if (indexOfPipe != -1) { 353 // We have a target and source element 354 postBackTarget = postBackSourceInfo.Substring(0, indexOfPipe); 355 _asyncPostBackSourceElementID = postBackSourceInfo.Substring(indexOfPipe + 1); 356 } 357 else { 358 // We only have a target 359 postBackTarget = postBackSourceInfo; 360 _asyncPostBackSourceElementID = String.Empty; 361 } 362 363 if (postBackTarget != _owner.UniqueID) { 364 if (postBackTarget.IndexOf(',') != -1) { 365 _updatePanelRequiresUpdate = null; 366 _updatePanelsRequireUpdate = postBackTarget.Split(','); 367 } 368 else { 369 _updatePanelRequiresUpdate = postBackTarget; 370 _updatePanelsRequireUpdate = null; 371 } 372 } 373 } 374 375 // Initialize all UpdatePanels (and their triggers, specifically) so that 376 // they can hook events, etc. before other controls can process their 377 // own post data. 378 // LoadPostData on ScriptManager only gets called during async posts, and 379 // is guaranteed to be called before any other controls have a chance to 380 // process their post data. 381 // During regular posts the UpdatePanel initializes itself in OnLoad. 382 if ((_allUpdatePanels != null) && (_allUpdatePanels.Count != 0)) { 383 foreach (UpdatePanel panel in _allUpdatePanels) { 384 panel.Initialize(); 385 } 386 } 387 388 _panelsInitialized = true; 389 } 390 OnInit()391 internal void OnInit() { 392 // Check if the browser supports partial rendering. We only do the check 393 // if the user hasn't already forced the feature to be on or off explicitly. 394 if (_owner.EnablePartialRendering && !_owner._supportsPartialRenderingSetByUser) { 395 HttpBrowserCapabilitiesBase browser = _owner.IPage.Request.Browser; 396 // There is no browser cap that directly tells us whether the browser 397 // supports XmlHttpRequest so we use the next best capability, which is 398 // the SupportsCallback property. 399 // Checking the other properties helps exclude agents such as crawlers. 400 bool supportsPartialRendering = 401 (browser.W3CDomVersion >= MinimumW3CDomVersion) && 402 (browser.EcmaScriptVersion >= MinimumEcmaScriptVersion) && 403 browser.SupportsCallback; 404 if (supportsPartialRendering) { 405 // If we still think we want to support it, now do a more expensive 406 // check for XHTML legacy rendering support. 407 supportsPartialRendering = !EnableLegacyRendering; 408 } 409 _owner.SupportsPartialRendering = supportsPartialRendering; 410 } 411 412 if (_owner.IsInAsyncPostBack) { 413 _owner.IPage.Error += OnPageError; 414 } 415 } 416 OnPageError(object sender, EventArgs e)417 private void OnPageError(object sender, EventArgs e) { 418 Exception ex = _owner.IPage.Server.GetLastError(); 419 _owner.OnAsyncPostBackError(new AsyncPostBackErrorEventArgs(ex)); 420 421 string errorMessage = _owner.AsyncPostBackErrorMessage; 422 if (String.IsNullOrEmpty(errorMessage) && !_owner.Control.Context.IsCustomErrorEnabled) { 423 // Only use the exception's message if we're not doing custom errors 424 errorMessage = ex.Message; 425 } 426 427 int httpCode = GetHttpCodeForException(ex); 428 429 bool showAsyncErrorMessage = false; 430 431 if (_owner.AllowCustomErrorsRedirect && _owner.Control.Context.IsCustomErrorEnabled) { 432 // Figure out if there's going to be a redirect for this error 433 bool hasRedirect = CustomErrorsSectionHasRedirect(httpCode); 434 if (!hasRedirect) { 435 // If there's no redirect, we need to send back the error message 436 showAsyncErrorMessage = true; 437 } 438 // If there was a redirect we do nothing since ASP.NET will automatically 439 // redirect the user to the error page anyway. This way we don't have to 440 // worry about how to resolve the paths from config. 441 } 442 else { 443 // If we're not going to use custom errors, just send back the error message 444 showAsyncErrorMessage = true; 445 } 446 447 if (showAsyncErrorMessage) { 448 IDictionary items = _owner.Control.Context.Items; 449 items[AsyncPostBackErrorKey] = true; 450 items[AsyncPostBackErrorMessageKey] = errorMessage; 451 items[AsyncPostBackErrorHttpCodeKey] = httpCode; 452 } 453 } 454 OnPreRender()455 internal void OnPreRender() { 456 _owner.IPage.SetRenderMethodDelegate(RenderPageCallback); 457 } 458 ProcessFocus(HtmlTextWriter writer)459 private void ProcessFocus(HtmlTextWriter writer) { 460 // Roughly stolen from Whidbey Page.cs 461 if (_requireFocusScript) { 462 Debug.Assert(ClientSupportsFocus, "If ClientSupportsFocus is false then we never should have set _requireFocusScript to true."); 463 string focusedControlId = String.Empty; 464 465 // Someone calling SetFocus(controlId) has the most precedent 466 if (!String.IsNullOrEmpty(_focusedControlID)) { 467 focusedControlId = _focusedControlID; 468 } 469 else { 470 if (_focusedControl != null && _focusedControl.Visible) { 471 focusedControlId = _focusedControl.ClientID; 472 } 473 } 474 if (focusedControlId.Length > 0) { 475 // Register focus script library 476 string focusResourceUrl = _owner.GetScriptResourceUrl("Focus.js", typeof(HtmlForm).Assembly); 477 EncodeString(writer, ScriptBlockToken, "ScriptPath", focusResourceUrl); 478 479 // Send the target control ID to the client 480 EncodeString(writer, FocusToken, String.Empty, focusedControlId); 481 } 482 } 483 } 484 ProcessScriptRegistration(HtmlTextWriter writer)485 private void ProcessScriptRegistration(HtmlTextWriter writer) { 486 _owner.ScriptRegistration.RenderActiveArrayDeclarations(_updatePanelsToRefresh, writer); 487 _owner.ScriptRegistration.RenderActiveScripts(_updatePanelsToRefresh, writer); 488 _owner.ScriptRegistration.RenderActiveSubmitStatements(_updatePanelsToRefresh, writer); 489 _owner.ScriptRegistration.RenderActiveExpandos(_updatePanelsToRefresh, writer); 490 _owner.ScriptRegistration.RenderActiveHiddenFields(_updatePanelsToRefresh, writer); 491 _owner.ScriptRegistration.RenderActiveScriptDisposes(_updatePanelsToRefresh, writer); 492 } 493 ProcessUpdatePanels()494 private void ProcessUpdatePanels() { 495 Debug.Assert(_owner.IsInAsyncPostBack); 496 Debug.Assert(_updatePanelsToRefresh == null); 497 498 if (_allUpdatePanels != null) { 499 _updatePanelsToRefresh = new List<UpdatePanel>(_allUpdatePanels.Count); 500 _childUpdatePanelsToRefresh = new List<UpdatePanel>(_allUpdatePanels.Count); 501 502 // Process the UpdatePanels to determine which are to be set in 503 // partial rendering mode. 504 505 // We need to process the list such that parent UpdatePanels are 506 // evaluated first. A child UpdatePanel inside a parent that is being 507 // updated should not be considered in partial rendering mode. 508 // Ordinarily child controls get initialized first before their parents 509 // so you'd expect the list to be in reverse order, but this isn't the case. 510 // UpdatePanels instantiate their templates in their OnInit, so a child 511 // UpdatePanel only exists in the control tree after the parent has been 512 // initialized. 513 514 HtmlForm form = _owner.Page.Form; 515 516 for (int i = 0; i < _allUpdatePanels.Count; i++) { 517 UpdatePanel panel = _allUpdatePanels[i]; 518 519 // Check whether the panel thinks it wants to update. Possible reasons 520 // a panel might be updating: 521 // - Postback data indicates the postback came from within the panel 522 // - Postback data indicates the postbacks was caused by PageRequestManager.beginAsyncPost 523 // and the update panel was explicitly requested to update 524 // - Explicit call to panel.Update() 525 // - Panel UpdateMode set to Always 526 // - Trigger fired (not yet implemented) 527 528 bool requiresUpdate = panel.RequiresUpdate || 529 (_updatePanelRequiresUpdate != null && String.Equals(panel.UniqueID, _updatePanelRequiresUpdate, StringComparison.Ordinal)) || 530 (_updatePanelsRequireUpdate != null && Array.IndexOf(_updatePanelsRequireUpdate, panel.UniqueID) != -1); 531 532 // Check and see if a parent panel will take update this panel, whether 533 // this panel wants to update or not. If so, then this panel doesn't need 534 // to be in update mode since it will get included in the rendering 535 // by its parent anyway. 536 // If this parent doesn't want to update then we don't need to do any 537 // additional checks because whether it renders depends entirely on 538 // whether the parent wants to render. 539 Control parent = panel.Parent; 540 while (parent != form) { 541 UpdatePanel parentUpdatePanel = parent as UpdatePanel; 542 if ((parentUpdatePanel != null) && 543 (_updatePanelsToRefresh.Contains(parentUpdatePanel) || _childUpdatePanelsToRefresh.Contains(parentUpdatePanel))) { 544 // This panel is inside another UpdatePanel that is being 545 // rendered, so it should render in normal mode. 546 requiresUpdate = false; 547 _childUpdatePanelsToRefresh.Add(panel); 548 break; 549 } 550 551 parent = parent.Parent; 552 553 if (parent == null) { 554 // This UpdatePanel was not inside an HtmlForm 555 // This really shouldn't happen, because the UpdatePanel would have thrown 556 // an exception on the initial GET request that it should be inside a form, 557 // so we'll just ignore it now... 558 requiresUpdate = false; 559 break; 560 } 561 } 562 563 if (requiresUpdate) { 564 panel.SetAsyncPostBackMode(true); 565 _updatePanelsToRefresh.Add(panel); 566 } 567 else { 568 panel.SetAsyncPostBackMode(false); 569 } 570 } 571 } 572 } 573 RegisterAsyncPostBackControl(Control control)574 public void RegisterAsyncPostBackControl(Control control) { 575 Exception ex = GetControlRegistrationException(control); 576 if (ex != null) { 577 throw ex; 578 } 579 if (_postBackControls != null && _postBackControls.Contains(control)) { 580 throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.ScriptManager_CannotRegisterBothPostBacks, control.ID)); 581 } 582 if (_asyncPostBackControls == null) { 583 _asyncPostBackControls = new List<Control>(); 584 } 585 // It is acceptable to register the same control twice since the same 586 // control might be referred to by more than one trigger. 587 if (!_asyncPostBackControls.Contains(control)) { 588 _asyncPostBackControls.Add(control); 589 } 590 } 591 RegisterDataItem(Control control, string dataItem, bool isJsonSerialized)592 public void RegisterDataItem(Control control, string dataItem, bool isJsonSerialized) { 593 if (control == null) { 594 throw new ArgumentNullException("control"); 595 } 596 if (!_owner.IsInAsyncPostBack) { 597 throw new InvalidOperationException(AtlasWeb.PageRequestManager_RegisterDataItemInNonAsyncRequest); 598 } 599 if (_scriptDataItems == null) { 600 _scriptDataItems = new ScriptDataItemCollection(); 601 } 602 else { 603 if (_scriptDataItems.ContainsControl(control)) { 604 throw new ArgumentException( 605 String.Format( 606 CultureInfo.InvariantCulture, 607 AtlasWeb.PageRequestManager_RegisterDataItemTwice, 608 control.ID), 609 "control"); 610 } 611 } 612 _scriptDataItems.Add(new ScriptDataItem(control, dataItem, isJsonSerialized)); 613 } 614 RegisterFocusScript()615 private void RegisterFocusScript() { 616 if (ClientSupportsFocus && !_requireFocusScript) { 617 _requireFocusScript = true; 618 } 619 } 620 RegisterPostBackControl(Control control)621 public void RegisterPostBackControl(Control control) { 622 Exception ex = GetControlRegistrationException(control); 623 if (ex != null) { 624 throw ex; 625 } 626 if (_asyncPostBackControls != null && _asyncPostBackControls.Contains(control)) { 627 throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.ScriptManager_CannotRegisterBothPostBacks, control.ID)); 628 } 629 if (_postBackControls == null) { 630 _postBackControls = new List<Control>(); 631 } 632 // It is acceptable to register the same control twice since the same 633 // control might be referred to by more than one trigger. 634 if (!_postBackControls.Contains(control)) { 635 _postBackControls.Add(control); 636 } 637 } 638 RegisterUpdatePanel(UpdatePanel updatePanel)639 internal void RegisterUpdatePanel(UpdatePanel updatePanel) { 640 Debug.Assert(updatePanel != null); 641 if (_allUpdatePanels == null) { 642 _allUpdatePanels = new List<UpdatePanel>(); 643 } 644 Debug.Assert(!_allUpdatePanels.Contains(updatePanel), 645 String.Format(CultureInfo.InvariantCulture, "The UpdatePanel with ID '{0}' has already been registered with the ScriptManager.", updatePanel.ID)); 646 _allUpdatePanels.Add(updatePanel); 647 648 if (_panelsInitialized) { 649 // Do catch-up for panels that may have been added later in 650 // the lifecycle during an async post. 651 Debug.Assert(_owner.IsInAsyncPostBack, "Catch-up initialization should only be done in async posts."); 652 updatePanel.Initialize(); 653 } 654 } 655 656 // Only call this method when these condictions are met: 657 // if (!((IControl)_owner).DesignMode && !_owner.IsInAsyncPostBack && _owner.SupportsPartialRendering 658 // && (_owner.MicrosoftAjaxMode != MicrosoftAjaxMode.Disabled)) Render(HtmlTextWriter writer)659 internal void Render(HtmlTextWriter writer) { 660 _owner.IPage.VerifyRenderingInServerForm(_owner); 661 RenderPageRequestManagerScript(writer); 662 } 663 RenderFormCallback(HtmlTextWriter writer, Control containerControl)664 private void RenderFormCallback(HtmlTextWriter writer, Control containerControl) { 665 666 Debug.Assert(_updatePanelWriter != null, "_updatePanelWriter should be set by RenderPageCallback before RenderFormCallback is called."); 667 668 // Suppress rendering of default content; instead just render out 669 // update panels 670 if (_updatePanelsToRefresh != null) { 671 foreach (UpdatePanel panel in _updatePanelsToRefresh) { 672 if (panel.Visible) { 673 // Write UpdatePanels to the response's writer; the writer passed in is a 674 // dummy parserWriter. It will contain hidden fields that are written to 675 // the response's writer later in RenderPageCallback. 676 panel.RenderControl(_updatePanelWriter); 677 } 678 } 679 } 680 681 IPage page = _owner.IPage; 682 if (page.EnableEventValidation) { 683 // If the page has event validation turned on, we need to run through 684 // the render logic for the rest of the page as well. However, the 685 // rendering is essentially ignored. 686 // UpdatePanels that were already rendered above do not re-render by checking 687 // a flag whether they already rendered. 688 689 // 690 691 692 693 694 695 // DevDiv 55447: Do not use Response.Flush and a NullStream to prevent Response.Writes 696 // from being written to the output stream, as calling Response.Flush causes headers to 697 // be written. This prevents cookies from being issued after this, for example. 698 // Instead, use a NullWriter that ignores Writes. We can change the writer used by HttpResponse 699 // using the internal SwitchWriter method. 700 // We must do this since data written via Response.Write will make the partial update 701 // response invalid. 702 703 TextWriter oldWriter = null; 704 bool writerSwitched = false; 705 try { 706 // beginning of possible direct Response.Writes 707 oldWriter = page.Response.SwitchWriter(TextWriter.Null); 708 // if we cant switch the writer for some reason we need to know not to switch it back again in the finally block 709 // writerSwitched will be false 710 writerSwitched = true; 711 712 // nullHtmlWriter captures any writes by controls to the textwriter they are passed. 713 // Note that we could possibly just let null TextWriter we switched catch this data, but this 714 // is more efficient. 715 HtmlTextWriter nullHtmlWriter = new HtmlTextWriter(TextWriter.Null); 716 foreach (Control control in containerControl.Controls) { 717 control.RenderControl(nullHtmlWriter); 718 } 719 } 720 finally { 721 // end of possible direct Response.Writes 722 if (writerSwitched) { 723 page.Response.SwitchWriter(oldWriter); 724 } 725 } 726 } 727 } 728 RenderPageCallback(HtmlTextWriter writer, Control pageControl)729 private void RenderPageCallback(HtmlTextWriter writer, Control pageControl) { 730 ProcessUpdatePanels(); 731 732 // Although we could use the pageControl parameter it's hard to write 733 // unit tests for it. Instead we just use our own page, which is the 734 // same instance anyway (but easier to test with). 735 736 HttpResponseBase response = _owner.IPage.Response; 737 738 response.ContentType = "text/plain"; 739 response.Cache.SetNoServerCaching(); 740 741 // Write out the version identifier, which helps the client-side deal with the response 742 // in a back-compatible way when there are changes made server-side. 743 EncodeString(writer, UpdatePanelVersionToken, String.Empty, UpdatePanelVersionNumber); 744 745 // Render the form. It will render its tag, hidden fields, etc. 746 // and then call our render method delegate, which will in turn render 747 // all the UpdatePanels 748 IHtmlForm formControl = _owner.IPage.Form; 749 formControl.SetRenderMethodDelegate(RenderFormCallback); 750 751 // Let updatePanels write directly to Response 752 _updatePanelWriter = writer; 753 754 // Let form header/footer write to a parser 755 ParserHtmlTextWriter formWriter = new ParserHtmlTextWriter(); 756 formControl.RenderControl(formWriter); 757 758 // Write out built-in ASP.NET hidden fields that were rendered directly by the page 759 // or registered through RegisterHiddenField 760 var hiddenFields = _owner.IPage.HiddenFieldsToRender; 761 if (hiddenFields != null) { 762 foreach (KeyValuePair<String, String> entry in hiddenFields) { 763 if (ControlUtil.IsBuiltInHiddenField(entry.Key)) { 764 EncodeString(writer, HiddenFieldToken, entry.Key, entry.Value); 765 } 766 } 767 } 768 769 // Write out PageRequestManager settings that can change during an async postback. 770 // This is required for dynamic UpdatePanels since the list of panels could 771 // change. 772 EncodeString(writer, AsyncPostBackControlIDsToken, String.Empty, GetAsyncPostBackControlIDs(false)); 773 EncodeString(writer, PostBackControlIDsToken, String.Empty, GetPostBackControlIDs(false)); 774 EncodeString(writer, UpdatePanelIDsToken, String.Empty, GetAllUpdatePanelIDs()); 775 EncodeString(writer, ChildUpdatePanelIDsToken, String.Empty, GetChildUpdatePanelIDs()); 776 EncodeString(writer, UpdatePanelsToRefreshToken, String.Empty, GetRefreshingUpdatePanelIDs()); 777 EncodeString(writer, AsyncPostBackTimeoutToken, String.Empty, _owner.AsyncPostBackTimeout.ToString(CultureInfo.InvariantCulture)); 778 if (formWriter.FormAction != null) { 779 EncodeString(writer, FormActionToken, String.Empty, formWriter.FormAction); 780 } 781 if (_owner.IPage.Header != null) { 782 string pageTitle = _owner.IPage.Title; 783 if (!String.IsNullOrEmpty(pageTitle)) { 784 EncodeString(writer, PageTitleToken, String.Empty, pageTitle); 785 } 786 } 787 RenderDataItems(writer); 788 789 ProcessScriptRegistration(writer); 790 791 // We process the focus after regular script registrations to 792 // make sure that if it ends up including some script that it 793 // executes last. 794 ProcessFocus(writer); 795 } 796 RenderDataItems(HtmlTextWriter writer)797 private void RenderDataItems(HtmlTextWriter writer) { 798 if (_scriptDataItems != null) { 799 foreach (ScriptDataItem dataItem in _scriptDataItems) { 800 EncodeString( 801 writer, 802 dataItem.IsJsonSerialized ? DataItemJsonToken : DataItemToken, 803 dataItem.Control.ClientID, 804 dataItem.DataItem); 805 } 806 } 807 } 808 RenderPageRequestManagerScript(HtmlTextWriter writer)809 internal void RenderPageRequestManagerScript(HtmlTextWriter writer) { 810 // 811 812 813 814 815 816 817 // Script format: 818 // <script type=""text/javascript""> 819 // //<![CDATA[ 820 // Sys.WebForms.PageRequestManager._initialize('{0}', '{1}', [{2}], [{3}], [{4}], {5}, {6}); 821 // //]]> 822 // </script> 823 824 // Writing directly to the writer is more performant than building 825 // up a big string with formatting and then writing it out later. 826 827 writer.Write(@"<script type=""text/javascript""> 828 //<![CDATA[ 829 Sys.WebForms.PageRequestManager._initialize('"); 830 writer.Write(_owner.UniqueID); 831 writer.Write(@"', '"); 832 writer.Write(_owner.IPage.Form.ClientID); 833 writer.Write(@"', ["); 834 RenderUpdatePanelIDsFromList(writer, _allUpdatePanels); 835 writer.Write("], ["); 836 writer.Write(GetAsyncPostBackControlIDs(true)); 837 writer.Write("], ["); 838 writer.Write(GetPostBackControlIDs(true)); 839 writer.Write("], "); 840 writer.Write(_owner.AsyncPostBackTimeout.ToString(CultureInfo.InvariantCulture)); 841 writer.Write(", '"); 842 writer.Write(GetMasterPageUniqueID(_owner.Page)); 843 writer.WriteLine("');"); 844 writer.Write(@"//]]> 845 </script> 846 "); 847 } 848 RenderUpdatePanelIDsFromList(HtmlTextWriter writer, List<UpdatePanel> list)849 private static void RenderUpdatePanelIDsFromList(HtmlTextWriter writer, List<UpdatePanel> list) { 850 // Writing directly to the writer is more performant than building 851 // up a big string with formatting and then writing it out later. 852 if (list != null && list.Count > 0) { 853 bool first = true; 854 for (int i = 0; i < list.Count; i++) { 855 UpdatePanel up = list[i]; 856 if (!up.Visible) { 857 // If the panel isn't visible, the client doesn't need to know about it 858 continue; 859 } 860 if (!first) { 861 writer.Write(','); 862 } 863 first = false; 864 865 // Due to the settable ClientID feature, UpdatePanel 866 // needs both the clientID and uniqueID 867 // We also send down a bool indicating whether the children of 868 // the panel count as triggers or not. 869 // ['[t|f]uniqueid1','clientid1','[t|f]uniqueid2','clientid2',...] 870 writer.Write("'"); 871 writer.Write(up.ChildrenAsTriggers ? 't' : 'f'); 872 writer.Write(up.UniqueID); 873 writer.Write("',"); 874 if (up.EffectiveClientIDMode == ClientIDMode.AutoID) { 875 writer.Write("''"); 876 } 877 else { 878 writer.Write("'"); 879 writer.Write(up.ClientID); 880 writer.Write("'"); 881 } 882 } 883 } 884 } 885 SetFocus(Control control)886 public void SetFocus(Control control) { 887 // We always call the real Page's method at least to do parameter validation 888 _owner.IPage.SetFocus(control); 889 890 // If it's not async, just let the page do whatever it wants. If we are 891 // in an async post, we need to keep track of what to focus later on. 892 if (_owner.IsInAsyncPostBack) { 893 _focusedControl = control; 894 _focusedControlID = null; 895 RegisterFocusScript(); 896 } 897 } 898 SetFocus(string clientID)899 public void SetFocus(string clientID) { 900 // We always call the real Page's method at least to do parameter validation 901 _owner.IPage.SetFocus(clientID); 902 SetFocusInternal(clientID); 903 } 904 SetFocusInternal(string clientID)905 internal void SetFocusInternal(string clientID) { 906 // If it's not async, just let the page do whatever it wants. If we are 907 // in an async post, we need to keep track of what to focus later on. 908 if (_owner.IsInAsyncPostBack) { 909 _focusedControlID = clientID.Trim(); 910 _focusedControl = null; 911 RegisterFocusScript(); 912 } 913 } 914 UnregisterUpdatePanel(UpdatePanel updatePanel)915 internal void UnregisterUpdatePanel(UpdatePanel updatePanel) { 916 Debug.Assert(updatePanel != null); 917 if ((_allUpdatePanels == null) || !_allUpdatePanels.Contains(updatePanel)) { 918 throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.ScriptManager_UpdatePanelNotRegistered, updatePanel.ID), "updatePanel"); 919 } 920 _allUpdatePanels.Remove(updatePanel); 921 } 922 923 private sealed class ParserHtmlTextWriter : HtmlTextWriter { 924 private bool _writingForm; 925 private string _formAction; 926 ParserHtmlTextWriter()927 public ParserHtmlTextWriter() : base(TextWriter.Null) { 928 } 929 930 public string FormAction { 931 get { 932 return _formAction; 933 } 934 } 935 WriteBeginTag(string tagName)936 public override void WriteBeginTag(string tagName) { 937 base.WriteBeginTag(tagName); 938 939 _writingForm = (tagName == "form"); 940 } 941 WriteAttribute(string name, string value, bool fEncode)942 public override void WriteAttribute(string name, string value, bool fEncode) { 943 base.WriteAttribute(name, value, fEncode); 944 945 if (_writingForm) { 946 if (name == "action") { 947 _formAction = value; 948 } 949 } 950 } 951 } 952 953 private sealed class ScriptDataItem { 954 private Control _control; 955 private string _dataItem; 956 private bool _isJsonSerialized; 957 ScriptDataItem(Control control, string dataItem, bool isJsonSerialized)958 public ScriptDataItem(Control control, string dataItem, bool isJsonSerialized) { 959 _control = control; 960 _dataItem = (dataItem == null) ? String.Empty : dataItem; 961 _isJsonSerialized = isJsonSerialized; 962 } 963 964 public Control Control { 965 get { 966 return _control; 967 } 968 } 969 970 public string DataItem { 971 get { 972 return _dataItem; 973 } 974 } 975 976 public bool IsJsonSerialized { 977 get { 978 return _isJsonSerialized; 979 } 980 } 981 } 982 983 private sealed class ScriptDataItemCollection : List<ScriptDataItem> { ContainsControl(Control control)984 public bool ContainsControl(Control control) { 985 foreach (ScriptDataItem dataItem in this) { 986 if (dataItem.Control == control) { 987 return true; 988 } 989 } 990 return false; 991 } 992 } 993 994 private enum IDType { 995 UniqueID, 996 Both 997 } 998 } 999 } 1000