From 4351ddd09255da3a3e389d80c5945fdcbc49d201 Mon Sep 17 00:00:00 2001 From: dougbu Date: Fri, 15 Aug 2014 18:57:05 -0700 Subject: [PATCH] Add `ModelMetadata.HasNonDefaultEditFormat` property - add `DataType` property to `CachedDataAnnotationsMetadataAttributes` in support of `ComputeHasNonDefaultEditFormat()` - provide doc comments and flesh out the implementation of `DisplayFormatString` and `EditFormatString` nits: - add comment about _not_ overriding `ComputeIsComplexType()` in `CachedDataAnnotationsModelMetadata` - restore alphabetic order in `CachedDataAnnotationsMetadataAttributes` - seal `SimpleDisplayText` in `CachedModelMetadata`, matching MVC 5.2 --- ...CachedDataAnnotationsMetadataAttributes.cs | 29 ++++- .../CachedDataAnnotationsModelMetadata.cs | 87 ++++++++++++++ .../Metadata/CachedModelMetadata.cs | 111 ++++++++++++++++++ .../Metadata/ModelMetadata.cs | 19 ++- 4 files changed, 242 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs index e262373881..b41134a60f 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs @@ -12,21 +12,44 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public CachedDataAnnotationsMetadataAttributes(IEnumerable attributes) { + DataType = attributes.OfType().FirstOrDefault(); Display = attributes.OfType().FirstOrDefault(); - DisplayFormat = attributes.OfType().FirstOrDefault(); DisplayColumn = attributes.OfType().FirstOrDefault(); + DisplayFormat = attributes.OfType().FirstOrDefault(); Editable = attributes.OfType().FirstOrDefault(); HiddenInput = attributes.OfType().FirstOrDefault(); Required = attributes.OfType().FirstOrDefault(); ScaffoldColumn = attributes.OfType().FirstOrDefault(); + + // Special case the [DisplayFormat] attribute hanging off an applied [DataType] attribute. This property is + // non-null for DataType.Currency, DataType.Date, DataType.Time, and potentially custom [DataType] + // subclasses. The DataType.Currency, DataType.Date, and DataType.Time [DisplayFormat] attributes have a + // non-null DataFormatString and the DataType.Date and DataType.Time [DisplayFormat] attributes have + // ApplyFormatInEditMode==true. + if (DisplayFormat == null && DataType != null) + { + DisplayFormat = DataType.DisplayFormat; + } } + /// + /// Gets (or sets in subclasses) found in collection passed to the + /// constructor, if any. + /// + public DataTypeAttribute DataType { get; protected set; } + public DisplayAttribute Display { get; protected set; } - public DisplayFormatAttribute DisplayFormat { get; protected set; } - public DisplayColumnAttribute DisplayColumn { get; protected set; } + /// + /// Gets (or sets in subclasses) found in collection passed to the + /// constructor, if any. + /// If no such attribute was found but a was, gets the + /// value. + /// + public DisplayFormatAttribute DisplayFormat { get; protected set; } + public EditableAttribute Editable { get; protected set; } /// diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs index f082bbc038..d20ec7a2d8 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs @@ -8,8 +8,12 @@ using System.Reflection; namespace Microsoft.AspNet.Mvc.ModelBinding { + // Class does not override ComputeIsComplexType() because value calculated in ModelMetadata's base implementation + // is correct. public class CachedDataAnnotationsModelMetadata : CachedModelMetadata { + private bool _isEditFormatStringFromCache; + public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadata prototype, Func modelAccessor) : base(prototype, modelAccessor) @@ -50,6 +54,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding : base.ComputeDescription(); } + /// + /// Calculate based on presence of an + /// and its value. + /// + /// + /// Calculated value. + /// if an exists. + /// null otherwise. + /// + protected override string ComputeDisplayFormatString() + { + return PrototypeCache.DisplayFormat != null + ? PrototypeCache.DisplayFormat.DataFormatString + : base.ComputeEditFormatString(); + } + protected override string ComputeDisplayName() { // DisplayName may be provided by DisplayAttribute. @@ -67,6 +87,73 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return base.ComputeDisplayName(); } + /// + /// Calculate based on presence of an + /// and its and + /// values. + /// + /// + /// Calculated value. + /// if an exists and + /// its is true; null otherwise. + /// + /// + /// Subclasses overriding this method should also override to + /// ensure the two calculations remain consistent. + /// + protected override string ComputeEditFormatString() + { + if (PrototypeCache.DisplayFormat != null && PrototypeCache.DisplayFormat.ApplyFormatInEditMode) + { + _isEditFormatStringFromCache = true; + return PrototypeCache.DisplayFormat.DataFormatString; + } + + return base.ComputeEditFormatString(); + } + + /// + /// Calculate based on + /// and presence of and + /// . + /// + /// + /// Calculated value. true if + /// is non-null, non-empty, and came from the cache (was + /// not set directly). In addition the applied must not have come from an + /// applied . false otherwise. + /// + protected override bool ComputeHasNonDefaultEditFormat() + { + // Following calculation ignores possibility something (an IModelMetadataProvider) set EditFormatString + // directly. + if (!string.IsNullOrEmpty(EditFormatString) && _isEditFormatStringFromCache) + { + // Have a non-empty EditFormatString based on [DisplayFormat] from our cache. + if (PrototypeCache.DataType == null) + { + // Attributes include no [DataType]; [DisplayFormat] was applied directly. + return true; + } + + if (PrototypeCache.DataType.DisplayFormat != PrototypeCache.DisplayFormat) + { + // Attributes include separate [DataType] and [DisplayFormat]; [DisplayFormat] provided override. + return true; + } + + if (PrototypeCache.DataType.GetType() != typeof(DataTypeAttribute)) + { + // Attributes include [DisplayFormat] copied from [DataType] and [DataType] was of a subclass. + // Assume the [DataType] constructor used the protected DisplayFormat setter to override its + // default. That is derived [DataType] provided override. + return true; + } + } + + return base.ComputeHasNonDefaultEditFormat(); + } + /// /// Calculate based on presence of an /// and its value. diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs index e94f166786..76b992de94 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs @@ -18,7 +18,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private bool _convertEmptyStringToNull; private string _nullDisplayText; private string _description; + private string _displayFormatString; private string _displayName; + private string _editFormatString; + private bool _hasNonDefaultEditFormat; private bool _hideSurroundingHtml; private bool _isReadOnly; private bool _isComplexType; @@ -29,7 +32,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private bool _convertEmptyStringToNullComputed; private bool _nullDisplayTextComputed; private bool _descriptionComputed; + private bool _displayFormatStringComputed; private bool _displayNameComputed; + private bool _editFormatStringComputed; + private bool _hasNonDefaultEditFormatComputed; private bool _hideSurroundingHtmlComputed; private bool _isReadOnlyComputed; private bool _isComplexTypeComputed; @@ -117,6 +123,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// + public sealed override string DisplayFormatString + { + get + { + if (!_displayFormatStringComputed) + { + _displayFormatString = ComputeDisplayFormatString(); + _displayFormatStringComputed = true; + } + + return _displayFormatString; + } + + set + { + _displayFormatString = value; + _displayFormatStringComputed = true; + } + } + public sealed override string DisplayName { get @@ -136,6 +163,48 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// + public sealed override string EditFormatString + { + get + { + if (!_editFormatStringComputed) + { + _editFormatString = ComputeEditFormatString(); + _editFormatStringComputed = true; + } + + return _editFormatString; + } + + set + { + _editFormatString = value; + _editFormatStringComputed = true; + } + } + + /// + public sealed override bool HasNonDefaultEditFormat + { + get + { + if (!_hasNonDefaultEditFormatComputed) + { + _hasNonDefaultEditFormat = ComputeHasNonDefaultEditFormat(); + _hasNonDefaultEditFormatComputed = true; + } + + return _hasNonDefaultEditFormat; + } + + set + { + _hasNonDefaultEditFormat = value; + _hasNonDefaultEditFormatComputed = true; + } + } + /// public sealed override bool HideSurroundingHtml { @@ -242,6 +311,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// + public sealed override string SimpleDisplayText + { + get + { + // Value already cached in ModelMetadata. That class also already exposes ComputeSimpleDisplayText() + // for overrides. Sealed here for consistency with other properties. + return base.SimpleDisplayText; + } + set + { + base.SimpleDisplayText = value; + } + } + protected TPrototypeCache PrototypeCache { get; set; } protected virtual bool ComputeConvertEmptyStringToNull() @@ -259,11 +343,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return base.Description; } + /// + /// Calculate the value. + /// + /// Calculated value. + protected virtual string ComputeDisplayFormatString() + { + return base.DisplayFormatString; + } + protected virtual string ComputeDisplayName() { return base.DisplayName; } + /// + /// Calculate the value. + /// + /// Calculated value. + protected virtual string ComputeEditFormatString() + { + return base.EditFormatString; + } + + /// + /// Calculate the value. + /// + /// Calculated value. + protected virtual bool ComputeHasNonDefaultEditFormat() + { + return base.HasNonDefaultEditFormat; + } + /// /// Calculate the value. /// diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs index 32ae5408d5..db1dda5083 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using Microsoft.AspNet.Mvc.ModelBinding.Internal; @@ -64,12 +63,30 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public virtual string Description { get; set; } + /// + /// Gets or sets the composite format (see + /// http://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to display the . + /// public virtual string DisplayFormatString { get; set; } public virtual string DisplayName { get; set; } + /// + /// Gets or sets the composite format (see + /// http://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to edit the . + /// + /// + /// instances that set this property to a non-null, non-empty, + /// non-default value should also set to true. + /// public virtual string EditFormatString { get; set; } + /// + /// Gets or sets a value indicating whether has a non-null, non-empty + /// value different from the default for the datatype. + /// + public virtual bool HasNonDefaultEditFormat { 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