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