From 2b70156cf48524efb0dda5dc4cd7b8114fade575 Mon Sep 17 00:00:00 2001 From: dougbu Date: Thu, 27 Mar 2014 15:18:18 -0700 Subject: [PATCH] Inject `HtmlHelper` property into `RazorView` - add `IHtmlHelper` and `ICanHasViewContext` - adjust `HtmlHelper` and `HtmlHelper` to match - throw if `ViewContext` accessed prior to `Contextualize` call - XML comments (from old world) all around Note - no current need for an `HtmlHelper` copy constructor or `Clone()` method - expect recursion code to get another injected `IHtmlHelper` and then "contextualize" that instance with a new `ViewContext` --- .../RazorViewOfT.cs | 10 ++- .../Html/HtmlHelper.cs | 68 +++++++++++++------ .../Html/HtmlHelperOfT.cs | 36 ++++++++-- .../ICanHasViewContext.cs | 7 ++ .../IHtmlHelperOfT.cs | 68 +++++++++++++++++++ .../Properties/Resources.Designer.cs | 56 +++++++++++++-- .../Properties/Resources.resx | 9 +++ src/Microsoft.AspNet.Mvc/MvcServices.cs | 8 ++- 8 files changed, 229 insertions(+), 33 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Rendering/ICanHasViewContext.cs create mode 100644 src/Microsoft.AspNet.Mvc.Rendering/IHtmlHelperOfT.cs diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewOfT.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewOfT.cs index 5893ab84e6..3cc5af08fe 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewOfT.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.Razor public ViewDataDictionary ViewData { get; private set; } - public HtmlHelper Html { get; set; } + public IHtmlHelper Html { get; set; } public override Task RenderAsync([NotNull] ViewContext context) { @@ -46,7 +46,13 @@ namespace Microsoft.AspNet.Mvc.Razor private void InitHelpers(ViewContext context) { - Html = new HtmlHelper(context.HttpContext, ViewData); + Html = context.ServiceProvider.GetService>(); + + var contextable = Html as ICanHasViewContext; + if (contextable != null) + { + contextable.Contextualize(context); + } } } } diff --git a/src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelper.cs index f9d2600281..c8fbff3714 100644 --- a/src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelper.cs @@ -8,7 +8,10 @@ using Microsoft.AspNet.Abstractions; namespace Microsoft.AspNet.Mvc.Rendering { - public class HtmlHelper + /// + /// Default implementation of non-generic portions of . + /// + public class HtmlHelper : ICanHasViewContext { public static readonly string ValidationInputCssClassName = "input-validation-error"; public static readonly string ValidationInputValidCssClassName = "input-validation-valid"; @@ -17,11 +20,13 @@ namespace Microsoft.AspNet.Mvc.Rendering public static readonly string ValidationSummaryCssClassName = "validation-summary-errors"; public static readonly string ValidationSummaryValidCssClassName = "validation-summary-valid"; - public HtmlHelper([NotNull] HttpContext httpContext, ViewDataDictionary viewData) - { - HttpContext = httpContext; - ViewData = viewData; + private ViewContext _viewContext; + /// + /// Initializes a new instance of the class. + /// + public HtmlHelper() + { // Underscores are fine characters in id's. IdAttributeDotReplacement = "_"; } @@ -30,10 +35,37 @@ namespace Microsoft.AspNet.Mvc.Rendering public HttpContext HttpContext { get; private set; } + public ViewContext ViewContext + { + get + { + if (_viewContext == null) + { + throw new InvalidOperationException(Resources.HtmlHelper_NotContextualized); + } + + return _viewContext; + } + private set + { + _viewContext = value; + } + } + + public dynamic ViewBag + { + get + { + return ViewContext.ViewBag; + } + } + public ViewDataDictionary ViewData { - get; - private set; + get + { + return ViewContext.ViewData; + } } /// @@ -71,6 +103,11 @@ namespace Microsoft.AspNet.Mvc.Rendering return result; } + public virtual void Contextualize([NotNull] ViewContext viewContext) + { + ViewContext = viewContext; + } + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")] public string Encode(string value) { @@ -88,6 +125,11 @@ namespace Microsoft.AspNet.Mvc.Rendering return TagBuilder.CreateSanitizedId(name, IdAttributeDotReplacement); } + /// + /// Returns the HTTP method that handles form input (GET or POST) as a string. + /// + /// The HTTP method that handles the form. + /// The form method string, either "get" or "post". public static string GetFormMethodString(FormMethod method) { switch (method) @@ -120,24 +162,12 @@ namespace Microsoft.AspNet.Mvc.Rendering } } - /// - /// Wraps HTML markup in an IHtmlString, which will enable HTML markup to be - /// rendered to the output without getting HTML encoded. - /// - /// HTML markup string. - /// An IHtmlString that represents HTML markup. [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")] public HtmlString Raw(string value) { return new HtmlString(value); } - /// - /// Wraps HTML markup from the string representation of an object in an IHtmlString, - /// which will enable HTML markup to be rendered to the output without getting HTML encoded. - /// - /// object with string representation as HTML markup - /// An IHtmlString that represents HTML markup. [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")] public HtmlString Raw(object value) { diff --git a/src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelperOfT.cs index 17041c3326..08e2a5acf7 100644 --- a/src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelperOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelperOfT.cs @@ -1,18 +1,40 @@ -using Microsoft.AspNet.Abstractions; +using System; +using Microsoft.AspNet.Abstractions; namespace Microsoft.AspNet.Mvc.Rendering { - public class HtmlHelper : HtmlHelper + public class HtmlHelper : HtmlHelper, IHtmlHelper { - public HtmlHelper([NotNull]HttpContext httpContext, ViewDataDictionary viewData) - : base(httpContext, viewData) + /// + /// Initializes a new instance of the class. + /// + public HtmlHelper() + : base() { - ViewData = viewData; } - public new ViewDataDictionary ViewData + /// + public new ViewDataDictionary ViewData { get; private set;} + + public override void Contextualize([NotNull] ViewContext viewContext) { - get; private set; + if (viewContext.ViewData == null) + { + throw new ArgumentException(Resources.FormatArgumentPropertyNull("ViewData"), "viewContext"); + } + + ViewData = viewContext.ViewData as ViewDataDictionary; + if (ViewData == null) + { + // viewContext may contain a base ViewDataDictionary instance. So complain about that type, not TModel. + throw new ArgumentException(Resources.FormatArgumentPropertyUnexpectedType( + "ViewData", + viewContext.ViewData.GetType().FullName, + typeof(ViewDataDictionary).FullName), + "viewContext"); + } + + base.Contextualize(viewContext); } } } diff --git a/src/Microsoft.AspNet.Mvc.Rendering/ICanHasViewContext.cs b/src/Microsoft.AspNet.Mvc.Rendering/ICanHasViewContext.cs new file mode 100644 index 0000000000..7e5dc3e150 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Rendering/ICanHasViewContext.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNet.Mvc.Rendering +{ + public interface ICanHasViewContext + { + void Contextualize([NotNull] ViewContext viewContext); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Rendering/IHtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.Rendering/IHtmlHelperOfT.cs new file mode 100644 index 0000000000..5fd8e96379 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Rendering/IHtmlHelperOfT.cs @@ -0,0 +1,68 @@ +using System; + +namespace Microsoft.AspNet.Mvc.Rendering +{ + /// + /// An for Linq expressions. + /// + /// The of the model. + public interface IHtmlHelper + { + /// + /// Gets or sets the character that replaces periods in the ID attribute of an element. + /// + string IdAttributeDotReplacement { get; set; } + + /// + /// Gets the view bag. + /// + dynamic ViewBag { get; } + + /// + /// Gets the context information about the view. + /// + ViewContext ViewContext { get; } + + /// + /// Gets the current view data. + /// + ViewDataDictionary ViewData { get; } + + /// + /// Converts the value of the specified object to an HTML-encoded string. + /// + /// The object to encode. + /// The HTML-encoded string. + string Encode(object value); + + /// + /// Converts the specified string to an HTML-encoded string. + /// + /// The string to encode. + /// The HTML-encoded string. + string Encode(string value); + + /// + /// Creates an HTML element ID using the specified element name. + /// + /// The name of the HTML element. + /// The ID of the HTML element. + string GenerateIdFromName(string name); + + /// + /// Wraps HTML markup in an , which will enable HTML markup to be + /// rendered to the output without getting HTML encoded. + /// + /// HTML markup string. + /// An that represents HTML markup. + HtmlString Raw(string value); + + /// + /// Wraps HTML markup from the string representation of an object in an , + /// which will enable HTML markup to be rendered to the output without getting HTML encoded. + /// + /// object with string representation as HTML markup. + /// An that represents HTML markup. + HtmlString Raw(object value); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Rendering/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Rendering/Properties/Resources.Designer.cs index 26d6012253..80de399c91 100644 --- a/src/Microsoft.AspNet.Mvc.Rendering/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Rendering/Properties/Resources.Designer.cs @@ -26,6 +26,38 @@ namespace Microsoft.AspNet.Mvc.Rendering return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentNullOrEmpty"), p0); } + /// + /// Property '{0}' must not be null. + /// + internal static string ArgumentPropertyNull + { + get { return GetString("ArgumentPropertyNull"); } + } + + /// + /// Property '{0}' must not be null. + /// + internal static string FormatArgumentPropertyNull(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentPropertyNull"), p0); + } + + /// + /// Property '{0}' is of type '{1}', but this method requires a value of type '{2}'. + /// + internal static string ArgumentPropertyUnexpectedType + { + get { return GetString("ArgumentPropertyUnexpectedType"); } + } + + /// + /// Property '{0}' is of type '{1}', but this method requires a value of type '{2}'. + /// + internal static string FormatArgumentPropertyUnexpectedType(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentPropertyUnexpectedType"), p0, p1, p2); + } + /// /// The partial view '{0}' was not found or no view engine supports the searched locations. The following locations were searched:{1} /// @@ -59,7 +91,23 @@ namespace Microsoft.AspNet.Mvc.Rendering } /// - /// The model item passed is null, but this ViewData instance requires a non-null model item of type '{0}'. + /// Must call 'Contextualize' method before using this HtmlHelper instance. + /// + internal static string HtmlHelper_NotContextualized + { + get { return GetString("HtmlHelper_NotContextualized"); } + } + + /// + /// Must call 'Contextualize' method before using this HtmlHelper instance. + /// + internal static string FormatHtmlHelper_NotContextualized() + { + return GetString("HtmlHelper_NotContextualized"); + } + + /// + /// The model item passed is null, but this ViewDataDictionary instance requires a non-null model item of type '{0}'. /// internal static string ViewData_ModelCannotBeNull { @@ -67,7 +115,7 @@ namespace Microsoft.AspNet.Mvc.Rendering } /// - /// The model item passed is null, but this ViewData instance requires a non-null model item of type '{0}'. + /// The model item passed is null, but this ViewDataDictionary instance requires a non-null model item of type '{0}'. /// internal static string FormatViewData_ModelCannotBeNull(object p0) { @@ -75,7 +123,7 @@ namespace Microsoft.AspNet.Mvc.Rendering } /// - /// The model item passed into the ViewData is of type '{0}', but this ViewData instance requires a model item of type '{1}'. + /// The model item passed into the ViewDataDictionary is of type '{0}', but this ViewDataDictionary instance requires a model item of type '{1}'. /// internal static string ViewData_WrongTModelType { @@ -83,7 +131,7 @@ namespace Microsoft.AspNet.Mvc.Rendering } /// - /// The model item passed into the ViewData is of type '{0}', but this ViewData instance requires a model item of type '{1}'. + /// The model item passed into the ViewDataDictionary is of type '{0}', but this ViewDataDictionary instance requires a model item of type '{1}'. /// internal static string FormatViewData_WrongTModelType(object p0, object p1) { diff --git a/src/Microsoft.AspNet.Mvc.Rendering/Properties/Resources.resx b/src/Microsoft.AspNet.Mvc.Rendering/Properties/Resources.resx index ac76688ab9..d4f1444d9a 100644 --- a/src/Microsoft.AspNet.Mvc.Rendering/Properties/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Rendering/Properties/Resources.resx @@ -120,12 +120,21 @@ The argument '{0}' is null or empty. + + Property '{0}' must not be null. + + + Property '{0}' is of type '{1}', but this method requires a value of type '{2}'. + The partial view '{0}' was not found or no view engine supports the searched locations. The following locations were searched:{1} ViewData value must not be null. + + Must call 'Contextualize' method before using this HtmlHelper instance. + The model item passed is null, but this ViewDataDictionary instance requires a non-null model item of type '{0}'. diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index c0fa7ae963..f4654d1198 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -88,7 +88,13 @@ namespace Microsoft.AspNet.Mvc typeof(NestedProviderManagerAsync<>), implementationInstance: null, lifecycle: LifecycleKind.Transient); - + + yield return + describe.Describe( + typeof(IHtmlHelper<>), + typeof(HtmlHelper<>), + implementationInstance: null, + lifecycle: LifecycleKind.Transient); } } }