Files
linux-packaging-mono/external/aspnetwebstack/src/System.Web.WebPages/WebPageBase.cs

494 lines
19 KiB
C#
Raw Normal View History

// 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;
}
}
}