1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
|
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Text;
using System.Web.WebPages.Resources;
using Microsoft.Internal.Web.Utils;
namespace System.Web.WebPages
{
// TODO(elipton): Clean this up and remove the suppression
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "This is temporary (elipton)")]
public abstract class WebPageBase : WebPageRenderingBase
{
// Keep track of which sections RenderSection has already been called on
private HashSet<string> _renderedSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// Keep track of whether RenderBody has been called
private bool _renderedBody = false;
// Action for rendering the body within a layout page
private Action<TextWriter> _body;
// TODO(elipton): Figure out if we still need these two writers
private TextWriter _tempWriter;
private TextWriter _currentWriter;
private DynamicPageDataDictionary<dynamic> _dynamicPageData;
public override string Layout { get; set; }
public TextWriter Output
{
get { return OutputStack.Peek(); }
}
public Stack<TextWriter> OutputStack
{
get { return PageContext.OutputStack; }
}
public override IDictionary<object, dynamic> PageData
{
get { return PageContext.PageData; }
}
public override dynamic Page
{
get
{
if (_dynamicPageData == null)
{
_dynamicPageData = new DynamicPageDataDictionary<dynamic>((PageDataDictionary<dynamic>)PageData);
}
return _dynamicPageData;
}
}
// Retrieves the sections defined in the calling page. If this is null, that means
// this page has been requested directly.
private Dictionary<string, SectionWriter> PreviousSectionWriters
{
get
{
var top = SectionWritersStack.Pop();
var previous = SectionWritersStack.Count > 0 ? SectionWritersStack.Peek() : null;
SectionWritersStack.Push(top);
return previous;
}
}
// Retrieves the current Dictionary of sectionWriters on the stack without poping it.
// There should be at least one on the stack which is added when the Render(ViewData,TextWriter)
// is called.
private Dictionary<string, SectionWriter> SectionWriters
{
get { return SectionWritersStack.Peek(); }
}
private Stack<Dictionary<string, SectionWriter>> SectionWritersStack
{
get { return PageContext.SectionWritersStack; }
}
protected virtual void ConfigurePage(WebPageBase parentPage)
{
}
public static WebPageBase CreateInstanceFromVirtualPath(string virtualPath)
{
return CreateInstanceFromVirtualPath(virtualPath, VirtualPathFactoryManager.Instance);
}
internal static WebPageBase CreateInstanceFromVirtualPath(string virtualPath, IVirtualPathFactory virtualPathFactory)
{
// Get the compiled object
try
{
WebPageBase webPage = virtualPathFactory.CreateInstance<WebPageBase>(virtualPath);
// Give it its virtual path
webPage.VirtualPath = virtualPath;
// Assign it the VirtualPathFactory
webPage.VirtualPathFactory = virtualPathFactory;
return webPage;
}
catch (HttpException e)
{
BuildManagerExceptionUtil.ThrowIfUnsupportedExtension(virtualPath, e);
throw;
}
}
/// <summary>
/// Attempts to create a WebPageBase instance from a virtualPath and wraps complex compiler exceptions with simpler messages
/// </summary>
private WebPageBase CreatePageFromVirtualPath(string virtualPath, HttpContextBase httpContext, Func<string, bool> virtualPathExists, DisplayModeProvider displayModeProvider, IDisplayMode displayMode)
{
try
{
DisplayInfo resolvedDisplayInfo = displayModeProvider.GetDisplayInfoForVirtualPath(virtualPath, httpContext, virtualPathExists, displayMode);
if (resolvedDisplayInfo != null)
{
var webPage = VirtualPathFactory.CreateInstance<WebPageBase>(resolvedDisplayInfo.FilePath);
if (webPage != null)
{
// Give it its virtual path
webPage.VirtualPath = virtualPath;
webPage.VirtualPathFactory = VirtualPathFactory;
webPage.DisplayModeProvider = DisplayModeProvider;
return webPage;
}
}
}
catch (HttpException e)
{
// If the path uses an unregistered extension, such as Foo.txt,
// then an error regarding build providers will be thrown.
// Check if this is the case and throw a simpler error.
BuildManagerExceptionUtil.ThrowIfUnsupportedExtension(virtualPath, e);
// If the path uses an extension registered with codedom, such as Foo.js,
// then an unfriendly compilation error might get thrown by the underlying compiler.
// Check if this is the case and throw a simpler error.
BuildManagerExceptionUtil.ThrowIfCodeDomDefinedExtension(virtualPath, e);
// Rethrow any errors
throw;
}
// The page is missing, could not be compiled or is of an invalid type.
throw new HttpException(String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_InvalidPageType, virtualPath));
}
private WebPageContext CreatePageContextFromParameters(bool isLayoutPage, params object[] data)
{
object first = null;
if (data != null && data.Length > 0)
{
first = data[0];
}
var pageData = PageDataDictionary<dynamic>.CreatePageDataFromParameters(PageData, data);
return WebPageContext.CreateNestedPageContext(PageContext, pageData, first, isLayoutPage);
}
public void DefineSection(string name, SectionWriter action)
{
if (SectionWriters.ContainsKey(name))
{
throw new HttpException(String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionAleadyDefined, name));
}
SectionWriters[name] = action;
}
internal void EnsurePageCanBeRequestedDirectly(string methodName)
{
if (PreviousSectionWriters == null)
{
throw new HttpException(String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_CannotRequestDirectly, VirtualPath, methodName));
}
}
public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer)
{
ExecutePageHierarchy(pageContext, writer, startPage: null);
}
// This method is only used by WebPageBase to allow passing in the view context and writer.
public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
{
PushContext(pageContext, writer);
if (startPage != null)
{
if (startPage != this)
{
var startPageContext = WebPageContext.CreateNestedPageContext<object>(parentContext: pageContext, pageData: null, model: null, isLayoutPage: false);
startPageContext.Page = startPage;
startPage.PageContext = startPageContext;
}
startPage.ExecutePageHierarchy();
}
else
{
ExecutePageHierarchy();
}
PopContext();
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We really don't care if SourceHeader fails, and we don't want it to fail any real requests ever")]
public override void ExecutePageHierarchy()
{
// Unlike InitPages, for a WebPage there is no hierarchy - it is always
// the last file to execute in the chain. There can still be layout pages
// and partial pages, but they are never part of the hierarchy.
// (add server header for falcon debugging)
// call to MapPath() is expensive. If we are not emiting source files to header,
// don't bother to populate the SourceFiles collection. This saves perf significantly.
if (WebPageHttpHandler.ShouldGenerateSourceHeader(Context))
{
try
{
string vp = VirtualPath;
if (vp != null)
{
string path = Context.Request.MapPath(vp);
if (!path.IsEmpty())
{
PageContext.SourceFiles.Add(path);
}
}
}
catch
{
// we really don't care if this ever fails, so we swallow all exceptions
}
}
TemplateStack.Push(Context, this);
try
{
// Execute the developer-written code of the WebPage
Execute();
}
finally
{
TemplateStack.Pop(Context);
}
}
protected virtual void InitializePage()
{
}
public bool IsSectionDefined(string name)
{
EnsurePageCanBeRequestedDirectly("IsSectionDefined");
return PreviousSectionWriters.ContainsKey(name);
}
public void PopContext()
{
string renderedContent = _tempWriter.ToString();
OutputStack.Pop();
if (!String.IsNullOrEmpty(Layout))
{
string layoutPagePath = NormalizeLayoutPagePath(Layout);
// If a layout file was specified, render it passing our page content
OutputStack.Push(_currentWriter);
RenderSurrounding(
layoutPagePath,
w => w.Write(renderedContent));
OutputStack.Pop();
}
else
{
// Otherwise, just render the page
_currentWriter.Write(renderedContent);
}
VerifyRenderedBodyOrSections();
SectionWritersStack.Pop();
}
public void PushContext(WebPageContext pageContext, TextWriter writer)
{
_currentWriter = writer;
PageContext = pageContext;
pageContext.Page = this;
InitializePage();
// Create a temporary writer
_tempWriter = new StringWriter(CultureInfo.InvariantCulture);
// Render the page into it
OutputStack.Push(_tempWriter);
SectionWritersStack.Push(new Dictionary<string, SectionWriter>(StringComparer.OrdinalIgnoreCase));
// If the body is defined in the ViewData, remove it and store it on the instance
// so that it won't affect rendering of partial pages when they call VerifyRenderedBodyOrSections
if (PageContext.BodyAction != null)
{
_body = PageContext.BodyAction;
PageContext.BodyAction = null;
}
}
public HelperResult RenderBody()
{
EnsurePageCanBeRequestedDirectly("RenderBody");
if (_renderedBody)
{
throw new HttpException(WebPageResources.WebPage_RenderBodyAlreadyCalled);
}
_renderedBody = true;
// _body should have previously been set in Render(ViewContext,TextWriter) if it
// was available in the ViewData.
if (_body != null)
{
return new HelperResult(tw => _body(tw));
}
else
{
throw new HttpException(String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_CannotRequestDirectly, VirtualPath, "RenderBody"));
}
}
public override HelperResult RenderPage(string path, params object[] data)
{
return RenderPageCore(path, isLayoutPage: false, data: data);
}
private HelperResult RenderPageCore(string path, bool isLayoutPage, object[] data)
{
if (String.IsNullOrEmpty(path))
{
throw ExceptionHelper.CreateArgumentNullOrEmptyException("path");
}
return new HelperResult(writer =>
{
path = NormalizePath(path);
WebPageBase subPage = CreatePageFromVirtualPath(path, Context, VirtualPathFactory.Exists, DisplayModeProvider, DisplayMode);
var pageContext = CreatePageContextFromParameters(isLayoutPage, data);
subPage.ConfigurePage(this);
subPage.ExecutePageHierarchy(pageContext, writer);
});
}
public HelperResult RenderSection(string name)
{
return RenderSection(name, required: true);
}
public HelperResult RenderSection(string name, bool required)
{
EnsurePageCanBeRequestedDirectly("RenderSection");
if (PreviousSectionWriters.ContainsKey(name))
{
var result = new HelperResult(tw =>
{
if (_renderedSections.Contains(name))
{
throw new HttpException(String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionAleadyRendered, name));
}
var body = PreviousSectionWriters[name];
// Since the body can also call RenderSection, we need to temporarily remove
// the current sections from the stack.
var top = SectionWritersStack.Pop();
bool pushed = false;
try
{
if (Output != tw)
{
OutputStack.Push(tw);
pushed = true;
}
body();
}
finally
{
if (pushed)
{
OutputStack.Pop();
}
}
SectionWritersStack.Push(top);
_renderedSections.Add(name);
});
return result;
}
else if (required)
{
// If the section is not found, and it is not optional, throw an error.
throw new HttpException(String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionNotDefined, name));
}
else
{
// If the section is optional and not found, then don't do anything.
return null;
}
}
private void RenderSurrounding(string partialViewName, Action<TextWriter> body)
{
// Save the previous body action and set ours instead.
// This value will be retrieved by the sub-page being rendered when it runs
// Render(ViewData, TextWriter).
var priorValue = PageContext.BodyAction;
PageContext.BodyAction = body;
// Render the layout file
Write(RenderPageCore(partialViewName, isLayoutPage: true, data: new object[0]));
// Restore the state
PageContext.BodyAction = priorValue;
}
// Verifies that RenderBody is called, or that RenderSection is called for all sections
private void VerifyRenderedBodyOrSections()
{
// The _body will be set within a layout page because PageContext.BodyAction was set by RenderSurrounding,
// which is only called in the case of rendering layout pages.
// Using RenderPage will not result in a _body being set in a partial page, thus the following checks for
// sections should not apply when RenderPage is called.
// Dev10 bug 928341
if (_body != null)
{
if (SectionWritersStack.Count > 1 && PreviousSectionWriters != null && PreviousSectionWriters.Count > 0)
{
// There are sections defined. Check that all sections have been rendered.
StringBuilder sectionsNotRendered = new StringBuilder();
foreach (var name in PreviousSectionWriters.Keys)
{
if (!_renderedSections.Contains(name))
{
if (sectionsNotRendered.Length > 0)
{
sectionsNotRendered.Append("; ");
}
sectionsNotRendered.Append(name);
}
}
if (sectionsNotRendered.Length > 0)
{
throw new HttpException(String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_SectionsNotRendered, VirtualPath, sectionsNotRendered.ToString()));
}
}
else if (!_renderedBody)
{
// There are no sections defined, but RenderBody was NOT called.
// If a body was defined, then RenderBody should have been called.
throw new HttpException(String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_RenderBodyNotCalled, VirtualPath));
}
}
}
public override void Write(HelperResult result)
{
WriteTo(Output, result);
}
public override void Write(object value)
{
WriteTo(Output, value);
}
public override void WriteLiteral(object value)
{
Output.Write(value);
}
protected internal override TextWriter GetOutputWriter()
{
return Output;
}
}
}
|