From 796ff1d3d32d07522c7b2fedea5c038a03a7945c Mon Sep 17 00:00:00 2001 From: dougbu Date: Sun, 10 Aug 2014 21:33:43 -0700 Subject: [PATCH] Add `HideSurroundingHtml` property to `ModelMetadata` - #843 - add property as well as the related `[HiddenInput]` attribute - use this property to address some TODOs in default display and editor templates --- .../Rendering/Html/DefaultDisplayTemplates.cs | 10 +++-- .../Rendering/Html/DefaultEditorTemplates.cs | 18 +++++--- .../HiddenInputAttribute.cs | 41 +++++++++++++++++++ ...CachedDataAnnotationsMetadataAttributes.cs | 7 ++++ .../CachedDataAnnotationsModelMetadata.cs | 17 ++++++++ .../Metadata/CachedModelMetadata.cs | 32 +++++++++++++++ .../Metadata/ModelMetadata.cs | 13 ++++++ 7 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/HiddenInputAttribute.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs index 746d7456b2..548c8c0ea5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs @@ -186,7 +186,11 @@ namespace Microsoft.AspNet.Mvc.Rendering public static string HiddenInputTemplate(IHtmlHelper html) { - // TODO: add ModelMetadata.HideSurroundingHtml and use here (return string.Empty) + if (html.ViewData.ModelMetadata.HideSurroundingHtml) + { + return string.Empty; + } + return StringTemplate(html); } @@ -219,7 +223,7 @@ namespace Microsoft.AspNet.Mvc.Rendering { var divTag = new TagBuilder("div"); - // TODO: add ModelMetadata.HideSurroundingHtml and use here (skip this block) + if (!propertyMetadata.HideSurroundingHtml) { var label = propertyMetadata.GetDisplayName(); if (!string.IsNullOrEmpty(label)) @@ -248,7 +252,7 @@ namespace Microsoft.AspNet.Mvc.Rendering builder.Append(templateBuilder.Build()); - // TODO: add ModelMetadata.HideSurroundingHtml and use here (skip this block) + if (!propertyMetadata.HideSurroundingHtml) { builder.AppendLine(divTag.ToString(TagRenderMode.EndTag)); } diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs index ef320cdac3..49d2bee693 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs @@ -135,12 +135,18 @@ namespace Microsoft.AspNet.Mvc.Rendering public static string HiddenInputTemplate(IHtmlHelper html) { var viewData = html.ViewData; - - // TODO: add ModelMetadata.HideSurroundingHtml and use here (set result to string.Empty) - var result = DefaultDisplayTemplates.StringTemplate(html); - var model = viewData.Model; + string result; + if (viewData.ModelMetadata.HideSurroundingHtml) + { + result = string.Empty; + } + else + { + result = DefaultDisplayTemplates.StringTemplate(html); + } + // Special-case opaque values and arbitrary binary data. var modelAsByteArray = model as byte[]; if (modelAsByteArray != null) @@ -225,7 +231,7 @@ namespace Microsoft.AspNet.Mvc.Rendering { var divTag = new TagBuilder("div"); - // TODO: add ModelMetadata.HideSurroundingHtml and use here (skip this block) + if (!propertyMetadata.HideSurroundingHtml) { var label = html.Label( propertyMetadata.PropertyName, @@ -258,8 +264,8 @@ namespace Microsoft.AspNet.Mvc.Rendering builder.Append(templateBuilder.Build()); - // TODO: add ModelMetadata.HideSurroundingHtml and use here (skip this block) // TODO: Add IHtmlHelper.ValidationMessage() and call just prior to closing the
tag + if (!propertyMetadata.HideSurroundingHtml) { builder.Append(" "); builder.AppendLine(divTag.ToString(TagRenderMode.EndTag)); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/HiddenInputAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/HiddenInputAttribute.cs new file mode 100644 index 0000000000..7647cb7fca --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/HiddenInputAttribute.cs @@ -0,0 +1,41 @@ +// 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; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Indicates associated property or all properties of associated type should be edited using an <input> + /// element of type "hidden". + /// + /// + /// When overriding a inherited from a base class, should apply both + /// [HiddenInput(DisplayValue = true)] (if the inherited attribute had DisplayValue = false) and a + /// with some value other than "HiddenInput". + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class HiddenInputAttribute : Attribute + { + /// + /// Instantiates a new instance of the class. + /// + public HiddenInputAttribute() + { + DisplayValue = true; + } + + /// + /// Gets or sets a value indicating whether to display the value as well as provide a hidden <input> + /// element. The default value is true. + /// + /// + /// If false, also causes the default display and editor templates to return HTML + /// lacking the usual per-property <div> wrapper around the associated property and the default display + /// "HiddenInput" template to return string.Empty for the associated property. Thus the default + /// display template effectively skips the property and the default + /// editor template returns only the hidden <input> element for the property. + /// + public bool DisplayValue { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs index edcbd1556e..e262373881 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs @@ -16,6 +16,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding DisplayFormat = attributes.OfType().FirstOrDefault(); DisplayColumn = attributes.OfType().FirstOrDefault(); Editable = attributes.OfType().FirstOrDefault(); + HiddenInput = attributes.OfType().FirstOrDefault(); Required = attributes.OfType().FirstOrDefault(); ScaffoldColumn = attributes.OfType().FirstOrDefault(); } @@ -28,6 +29,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public EditableAttribute Editable { get; protected set; } + /// + /// Gets (or sets in subclasses) found in collection passed to the + /// constructor, if any. + /// + public HiddenInputAttribute HiddenInput { get; protected set; } + public RequiredAttribute Required { get; protected set; } public ScaffoldColumnAttribute ScaffoldColumn { get; protected set; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs index 8b6421b4e4..f082bbc038 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs @@ -67,6 +67,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return base.ComputeDisplayName(); } + /// + /// Calculate based on presence of an + /// and its value. + /// + /// Calculated value. true if an + /// exists and its value is + /// false; false otherwise. + protected override bool ComputeHideSurroundingHtml() + { + if (PrototypeCache.HiddenInput != null) + { + return !PrototypeCache.HiddenInput.DisplayValue; + } + + return base.ComputeHideSurroundingHtml(); + } + protected override bool ComputeIsReadOnly() { if (PrototypeCache.Editable != null) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs index 15b1acf53d..e94f166786 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs @@ -19,6 +19,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private string _nullDisplayText; private string _description; private string _displayName; + private bool _hideSurroundingHtml; private bool _isReadOnly; private bool _isComplexType; private bool _isRequired; @@ -29,6 +30,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private bool _nullDisplayTextComputed; private bool _descriptionComputed; private bool _displayNameComputed; + private bool _hideSurroundingHtmlComputed; private bool _isReadOnlyComputed; private bool _isComplexTypeComputed; private bool _isRequiredComputed; @@ -134,6 +136,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// + public sealed override bool HideSurroundingHtml + { + get + { + if (!_hideSurroundingHtmlComputed) + { + _hideSurroundingHtml = ComputeHideSurroundingHtml(); + _hideSurroundingHtmlComputed = true; + } + + return _hideSurroundingHtml; + } + + set + { + _hideSurroundingHtml = value; + _hideSurroundingHtmlComputed = true; + } + } + public sealed override bool IsReadOnly { get @@ -241,6 +264,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return base.DisplayName; } + /// + /// Calculate the value. + /// + /// Calculated value. + protected virtual bool ComputeHideSurroundingHtml() + { + return base.HideSurroundingHtml; + } + protected virtual bool ComputeIsReadOnly() { return base.IsReadOnly; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs index 921ea3c5f9..5e07928cbe 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs @@ -69,6 +69,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public virtual string EditFormatString { get; set; } + /// + /// Gets or sets a value indicating whether the "HiddenInput" display template should return + /// string.Empty (not the expression value) and whether the "HiddenInput" editor template should not + /// also return the expression value (together with the hidden <input> element). + /// + /// + /// If true, also causes the default display and editor templates to return HTML + /// lacking the usual per-property <div> wrapper around the associated property. Thus the default + /// display template effectively skips the property and the default + /// editor template returns only the hidden <input> element for the property. + /// + public virtual bool HideSurroundingHtml { get; set; } + public virtual bool IsComplexType { get { return !ValueProviderResult.CanConvertFromString(ModelType); }