// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc.Razor { /// /// Represents properties and methods that are needed in order to render a view that uses Razor syntax. /// public abstract class RazorPage : IRazorPage { private IUrlHelper _urlHelper; private readonly HashSet _renderedSections = new HashSet(StringComparer.OrdinalIgnoreCase); private bool _renderedBody; public RazorPage() { SectionWriters = new Dictionary(StringComparer.OrdinalIgnoreCase); } public HttpContext Context { get { if (ViewContext == null) { return null; } return ViewContext.HttpContext; } } /// public string Path { get; set; } /// public ViewContext ViewContext { get; set; } public string Layout { get; set; } /// /// Gets the TextWriter that the page is writing output to. /// public virtual TextWriter Output { get { if (ViewContext == null) { var message = Resources.FormatViewContextMustBeSet("ViewContext", "Output"); throw new InvalidOperationException(message); } return ViewContext.Writer; } } public virtual IPrincipal User { get { if (Context == null) { return null; } return Context.User; } } public dynamic ViewBag { get { return (ViewContext == null) ? null : ViewContext.ViewBag; } } /// public Action RenderBodyDelegate { get; set; } /// public Dictionary PreviousSectionWriters { get; set; } /// public Dictionary SectionWriters { get; private set; } /// public abstract Task ExecuteAsync(); /// /// Writes the specified with HTML encoding to . /// /// The to write. public virtual void Write(object value) { WriteTo(Output, value); } /// /// Writes the specified with HTML encoding to . /// /// The instance to write to. /// The to write. /// /// s of type are written without encoding and the /// is invoked for types. /// For all other types, the encoded result of is written to the /// . /// public virtual void WriteTo(TextWriter writer, object content) { if (content != null) { var helperResult = content as HelperResult; if (helperResult != null) { helperResult.WriteTo(writer); } else { var htmlString = content as HtmlString; if (htmlString != null) { writer.Write(content.ToString()); } else { writer.Write(WebUtility.HtmlEncode(content.ToString())); } } } } /// /// Writes the specified without HTML encoding to . /// /// The to write. public virtual void WriteLiteral(object value) { WriteLiteralTo(Output, value); } /// /// Writes the specified without HTML encoding to the . /// /// The instance to write to. /// The to write. public virtual void WriteLiteralTo(TextWriter writer, object text) { if (text != null) { writer.Write(text.ToString()); } } public virtual void WriteAttribute(string name, PositionTagged prefix, PositionTagged suffix, params AttributeValue[] values) { WriteAttributeTo(Output, name, prefix, suffix, values); } public virtual void WriteAttributeTo(TextWriter writer, string name, PositionTagged prefix, PositionTagged suffix, params AttributeValue[] values) { var first = true; var wroteSomething = false; if (values.Length == 0) { // Explicitly empty attribute, so write the prefix and suffix WritePositionTaggedLiteral(writer, prefix); WritePositionTaggedLiteral(writer, suffix); } else { for (var i = 0; i < values.Length; i++) { var attrVal = values[i]; var val = attrVal.Value; var next = i == values.Length - 1 ? suffix : // End of the list, grab the suffix values[i + 1].Prefix; // Still in the list, grab the next prefix if (val.Value == null) { // Nothing to write continue; } // The special cases here are that the value we're writing might already be a string, or that the // value might be a bool. If the value is the bool 'true' we want to write the attribute name // instead of the string 'true'. If the value is the bool 'false' we don't want to write anything. // Otherwise the value is another object (perhaps an HtmlString) and we'll ask it to format itself. string stringValue; var boolValue = val.Value as bool?; if (boolValue == true) { stringValue = name; } else if (boolValue == false) { continue; } else { stringValue = val.Value as string; } if (first) { WritePositionTaggedLiteral(writer, prefix); first = false; } else { WritePositionTaggedLiteral(writer, attrVal.Prefix); } // Calculate length of the source span by the position of the next value (or suffix) var sourceLength = next.Position - attrVal.Value.Position; if (attrVal.Literal) { WriteLiteralTo(writer, stringValue ?? val.Value); } else { WriteTo(writer, stringValue ?? val.Value); // Write value } wroteSomething = true; } if (wroteSomething) { WritePositionTaggedLiteral(writer, suffix); } } } public virtual string Href([NotNull] string contentPath) { if (_urlHelper == null) { _urlHelper = Context.RequestServices.GetService(); } return _urlHelper.Content(contentPath); } private void WritePositionTaggedLiteral(TextWriter writer, string value, int position) { WriteLiteralTo(writer, value); } private void WritePositionTaggedLiteral(TextWriter writer, PositionTagged value) { WritePositionTaggedLiteral(writer, value.Value, value.Position); } protected virtual HelperResult RenderBody() { if (RenderBodyDelegate == null) { throw new InvalidOperationException(Resources.FormatRenderBodyCannotBeCalled("RenderBody")); } _renderedBody = true; return new HelperResult(RenderBodyDelegate); } public void DefineSection(string name, HelperResult action) { if (SectionWriters.ContainsKey(name)) { throw new InvalidOperationException(Resources.FormatSectionAlreadyDefined(name)); } SectionWriters[name] = action; } public bool IsSectionDefined([NotNull] string name) { EnsureMethodCanBeInvoked("IsSectionDefined"); return PreviousSectionWriters.ContainsKey(name); } public HelperResult RenderSection([NotNull] string name) { return RenderSection(name, required: true); } public HelperResult RenderSection([NotNull] string name, bool required) { EnsureMethodCanBeInvoked("RenderSection"); if (_renderedSections.Contains(name)) { throw new InvalidOperationException(Resources.FormatSectionAlreadyRendered("RenderSection", name)); } HelperResult action; if (PreviousSectionWriters.TryGetValue(name, out action)) { _renderedSections.Add(name); return action; } else if (required) { // If the section is not found, and it is not optional, throw an error. throw new InvalidOperationException(Resources.FormatSectionNotDefined(name)); } else { // If the section is optional and not found, then don't do anything. return null; } } /// public void EnsureBodyAndSectionsWereRendered() { // If PreviousSectionWriters is set, ensure all defined sections were rendered. if (PreviousSectionWriters != null) { var sectionsNotRendered = PreviousSectionWriters.Keys.Except(_renderedSections, StringComparer.OrdinalIgnoreCase); if (sectionsNotRendered.Any()) { var sectionNames = string.Join(", ", sectionsNotRendered); throw new InvalidOperationException(Resources.FormatSectionsNotRendered(sectionNames)); } } // If BodyContent is set, ensure it was rendered. if (RenderBodyDelegate != null && !_renderedBody) { // If a body was defined, then RenderBody should have been called. throw new InvalidOperationException(Resources.FormatRenderBodyNotCalled("RenderBody")); } } private void EnsureMethodCanBeInvoked(string methodName) { if (PreviousSectionWriters == null) { throw new InvalidOperationException(Resources.FormatView_MethodCannotBeCalled(methodName)); } } } }