Set `ModelMetadata.TemplateHint` based on data annotations
- add `CachedDataAnnotationsMetadataAttributes.UIHint` - set `ModelMetadata.TemplateHint` using `UIHintAttribute` or `HiddenInputAttribute` - add doc comments for `TemplateHint`-related properties and methods - add unit tests and use these attributes in functional tests nits: - cache and seal `CachedModelMetadata.IsCollectionType` - correct doc comments for `ModelMetadata.RealModelType` - add doc comments for `IsCollectionType`-related properties and methods - add doc comments for `IsComplexType`-related properties and methods - move `CachedModelMetadata.IsComplexType` right below `IsCollectionType` - same for related fields and methods
This commit is contained in:
parent
451db6fb16
commit
0549769fd0
|
|
@ -1,7 +1,6 @@
|
|||
// 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.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
|
@ -20,6 +19,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
HiddenInput = attributes.OfType<HiddenInputAttribute>().FirstOrDefault();
|
||||
Required = attributes.OfType<RequiredAttribute>().FirstOrDefault();
|
||||
ScaffoldColumn = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
|
||||
UIHint = attributes.OfType<UIHintAttribute>().FirstOrDefault();
|
||||
|
||||
BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
|
||||
PropertyBindingPredicateProviders = attributes.OfType<IPropertyBindingPredicateProvider>();
|
||||
BinderModelNameProvider = attributes.OfType<IModelNameProvider>().FirstOrDefault();
|
||||
|
|
@ -82,7 +83,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public HiddenInputAttribute HiddenInput { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets (or sets in subclasses) <see cref="IEnumerable{IPropertyBindingPredicateProvider}"/> found in
|
||||
/// Gets (or sets in subclasses) <see cref="IEnumerable{IPropertyBindingPredicateProvider}"/> found in
|
||||
/// collection passed to the <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/>
|
||||
/// constructor, if any.
|
||||
/// </summary>
|
||||
|
|
@ -91,5 +92,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
public RequiredAttribute Required { get; protected set; }
|
||||
|
||||
public ScaffoldColumnAttribute ScaffoldColumn { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets (or sets in subclasses) <see cref="UIHintAttribute"/> found in collection passed to the
|
||||
/// <see cref="CachedDataAnnotationsMetadataAttributes(IEnumerable{object})"/> constructor, if any.
|
||||
/// </summary>
|
||||
public UIHintAttribute UIHint { get; protected set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ using System.Reflection;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
// Class does not override ComputeIsComplexType() because value calculated in ModelMetadata's base implementation
|
||||
// is correct.
|
||||
// Class does not override ComputeIsCollectionType() or ComputeIsComplexType() because values calculated in
|
||||
// ModelMetadata's base implementation are correct. No data annotations override those calculations.
|
||||
public class CachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>
|
||||
{
|
||||
private static readonly string HtmlName = DataType.Html.ToString();
|
||||
|
|
@ -333,6 +333,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
: base.ComputeShowForEdit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the <see cref="ModelMetadata.TemplateHint"/> value based on presence of a
|
||||
/// <see cref="UIHintAttribute"/> and its <see cref="UIHintAttribute.UIHint"/> value or presence of a
|
||||
/// <see cref="HiddenInputAttribute"/> when no <see cref="UIHintAttribute"/> exists.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Calculated <see cref="ModelMetadata.TemplateHint"/> value. <see cref="UIHintAttribute.UIHint"/> if a
|
||||
/// <see cref="UIHintAttribute"/> exists. <c>"HiddenInput"</c> if a <see cref="HiddenInputAttribute"/> exists
|
||||
/// and no <see cref="UIHintAttribute"/> exists. <c>null</c> otherwise.
|
||||
/// </returns>
|
||||
protected override string ComputeTemplateHint()
|
||||
{
|
||||
if (PrototypeCache.UIHint != null)
|
||||
{
|
||||
return PrototypeCache.UIHint.UIHint;
|
||||
}
|
||||
|
||||
if (PrototypeCache.HiddenInput != null)
|
||||
{
|
||||
return "HiddenInput";
|
||||
}
|
||||
|
||||
return base.ComputeTemplateHint();
|
||||
}
|
||||
|
||||
private static void ValidateDisplayColumnAttribute(DisplayColumnAttribute displayColumnAttribute,
|
||||
PropertyInfo displayColumnProperty, Type modelType)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,13 +25,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private bool _hasNonDefaultEditFormat;
|
||||
private bool _hideSurroundingHtml;
|
||||
private bool _htmlEncode;
|
||||
private bool _isReadOnly;
|
||||
private bool _isCollectionType;
|
||||
private bool _isComplexType;
|
||||
private bool _isReadOnly;
|
||||
private bool _isRequired;
|
||||
private string _nullDisplayText;
|
||||
private int _order;
|
||||
private bool _showForDisplay;
|
||||
private bool _showForEdit;
|
||||
private string _templateHint;
|
||||
private IBinderMetadata _binderMetadata;
|
||||
private string _binderModelName;
|
||||
private IPropertyBindingPredicateProvider _propertyBindingPredicateProvider;
|
||||
|
|
@ -46,13 +48,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private bool _hasNonDefaultEditFormatComputed;
|
||||
private bool _hideSurroundingHtmlComputed;
|
||||
private bool _htmlEncodeComputed;
|
||||
private bool _isReadOnlyComputed;
|
||||
private bool _isCollectionTypeComputed;
|
||||
private bool _isComplexTypeComputed;
|
||||
private bool _isReadOnlyComputed;
|
||||
private bool _isRequiredComputed;
|
||||
private bool _nullDisplayTextComputed;
|
||||
private bool _orderComputed;
|
||||
private bool _showForDisplayComputed;
|
||||
private bool _showForEditComputed;
|
||||
private bool _templateHintComputed;
|
||||
private bool _isBinderMetadataComputed;
|
||||
private bool _isBinderModelNameComputed;
|
||||
private bool _isBinderTypeComputed;
|
||||
|
|
@ -320,6 +324,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsCollectionType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isCollectionTypeComputed)
|
||||
{
|
||||
_isCollectionType = ComputeIsCollectionType();
|
||||
_isCollectionTypeComputed = true;
|
||||
}
|
||||
|
||||
return _isCollectionType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsComplexType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isComplexTypeComputed)
|
||||
{
|
||||
_isComplexType = ComputeIsComplexType();
|
||||
_isComplexTypeComputed = true;
|
||||
}
|
||||
return _isComplexType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsReadOnly
|
||||
{
|
||||
|
|
@ -358,20 +391,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsComplexType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isComplexTypeComputed)
|
||||
{
|
||||
_isComplexType = ComputeIsComplexType();
|
||||
_isComplexTypeComputed = true;
|
||||
}
|
||||
return _isComplexType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override string NullDisplayText
|
||||
{
|
||||
|
|
@ -495,6 +514,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override string TemplateHint
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_templateHintComputed)
|
||||
{
|
||||
_templateHint = ComputeTemplateHint();
|
||||
_templateHintComputed = true;
|
||||
}
|
||||
|
||||
return _templateHint;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_templateHint = value;
|
||||
_templateHintComputed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override Type BinderType
|
||||
{
|
||||
|
|
@ -605,6 +645,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return base.HtmlEncode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the <see cref="IsCollectionType"/> value.
|
||||
/// </summary>
|
||||
/// <returns>Calculated <see cref="IsCollectionType"/> value.</returns>
|
||||
protected virtual bool ComputeIsCollectionType()
|
||||
{
|
||||
return base.IsCollectionType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the <see cref="IsComplexType"/> value.
|
||||
/// </summary>
|
||||
/// <returns>Calculated <see cref="IsComplexType"/> value.</returns>
|
||||
protected virtual bool ComputeIsComplexType()
|
||||
{
|
||||
return base.IsComplexType;
|
||||
}
|
||||
|
||||
protected virtual bool ComputeIsReadOnly()
|
||||
{
|
||||
return base.IsReadOnly;
|
||||
|
|
@ -615,11 +673,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return base.IsRequired;
|
||||
}
|
||||
|
||||
protected virtual bool ComputeIsComplexType()
|
||||
{
|
||||
return base.IsComplexType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the <see cref="NullDisplayText"/> value.
|
||||
/// </summary>
|
||||
|
|
@ -647,5 +700,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
return base.ShowForEdit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the <see cref="TemplateHint"/> value.
|
||||
/// </summary>
|
||||
/// <returns>Calculated <see cref="TemplateHint"/> value.</returns>
|
||||
protected virtual string ComputeTemplateHint()
|
||||
{
|
||||
return base.TemplateHint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,11 +142,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// </remarks>
|
||||
public virtual bool HideSurroundingHtml { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the <see cref="ModelType"/> is a collection <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <c>true</c> if the <see cref="ModelType"/> is not <see cref="string"/> and is assignable to
|
||||
/// <see cref="System.Collections.IEnumerable"/>; <c>false</c> otherwise.
|
||||
/// </remarks>
|
||||
public virtual bool IsCollectionType
|
||||
{
|
||||
get { return TypeHelper.IsCollectionType(ModelType); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the <see cref="ModelType"/> is a complex type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <c>false</c> if the <see cref="ModelType"/> has a direct conversion to <see cref="string"/>; <c>true</c>
|
||||
/// otherwise.
|
||||
/// </remarks>
|
||||
public virtual bool IsComplexType
|
||||
{
|
||||
get { return !TypeHelper.HasStringConverter(ModelType); }
|
||||
|
|
@ -238,8 +252,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
protected IModelMetadataProvider Provider { get; set; }
|
||||
|
||||
/// <returns>
|
||||
/// Gets <c>T</c> if <see cref="ModelType"/> is <see cref="Nullable{T}"/>;
|
||||
/// <see cref="ModelType"/> otherwise.
|
||||
/// Gets runtime <see cref="Type"/> of <see cref="Model"/> if <see cref="Model"/> is non-<c>null</c> and
|
||||
/// <see cref="ModelType"/> is not <see cref="Nullable{T}"/>; <see cref="ModelType"/> otherwise.
|
||||
/// </returns>
|
||||
public Type RealModelType
|
||||
{
|
||||
|
|
@ -294,6 +308,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
set { _showForEdit = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a hint that suggests what template to use for this model. Overrides <see cref="DataTypeName"/>
|
||||
/// in that context but, unlike <see cref="DataTypeName"/>, this value is not used elsewhere.
|
||||
/// </summary>
|
||||
/// <value><c>null</c> unless set manually or through additional metadata e.g. attributes.</value>
|
||||
public virtual string TemplateHint { get; set; }
|
||||
|
||||
internal EfficientTypePropertyKey<Type, string> CacheKey
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Null(cache.HiddenInput);
|
||||
Assert.Null(cache.Required);
|
||||
Assert.Null(cache.ScaffoldColumn);
|
||||
Assert.Null(cache.UIHint);
|
||||
Assert.Null(cache.BinderMetadata);
|
||||
Assert.Null(cache.BinderModelNameProvider);
|
||||
Assert.Empty(cache.PropertyBindingPredicateProviders);
|
||||
|
|
@ -51,6 +52,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{ new EditableAttribute(allowEdit: false), cache => cache.Editable },
|
||||
{ new HiddenInputAttribute(), cache => cache.HiddenInput },
|
||||
{ new RequiredAttribute(), cache => cache.Required },
|
||||
{ new ScaffoldColumnAttribute(scaffold: true), cache => cache.ScaffoldColumn },
|
||||
{ new UIHintAttribute("hintHint"), cache => cache.UIHint },
|
||||
{ new TestBinderMetadata(), cache => cache.BinderMetadata },
|
||||
{ new TestModelNameProvider(), cache => cache.BinderModelNameProvider },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenInputWorksOnProperty()
|
||||
public void HiddenInputWorksOnProperty_ForHideSurroundingHtml()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
|
@ -195,7 +195,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenInputWorksOnPropertyType()
|
||||
public void HiddenInputWorksOnPropertyType_ForHideSurroundingHtml()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
|
@ -209,6 +209,36 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenInputWorksOnProperty_ForTemplateHint()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
var metadata = provider.GetMetadataForType(modelAccessor: null, modelType: typeof(ClassWithHiddenProperties));
|
||||
var property = metadata.Properties["DirectlyHidden"];
|
||||
|
||||
// Act
|
||||
var result = property.TemplateHint;
|
||||
|
||||
// Assert
|
||||
Assert.Equal("HiddenInput", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenInputWorksOnPropertyType_ForTemplateHint()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
var metadata = provider.GetMetadataForType(modelAccessor: null, modelType: typeof(ClassWithHiddenProperties));
|
||||
var property = metadata.Properties["OfHiddenType"];
|
||||
|
||||
// Act
|
||||
var result = property.TemplateHint;
|
||||
|
||||
// Assert
|
||||
Assert.Equal("HiddenInput", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadataForProperty_WithNoBinderMetadata_GetsItFromType()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -109,7 +109,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
new DisplayFormatAttribute { NullDisplayText = "value" }, metadata => metadata.NullDisplayText
|
||||
},
|
||||
{
|
||||
new TestModelNameProvider() { Name = "value" }, metadata => metadata.BinderModelName
|
||||
new TestModelNameProvider { Name = "value" }, metadata => metadata.BinderModelName
|
||||
},
|
||||
{
|
||||
new UIHintAttribute("value"), metadata => metadata.TemplateHint
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -208,6 +211,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
metadata => metadata.HideSurroundingHtml,
|
||||
false
|
||||
},
|
||||
{
|
||||
new HiddenInputAttribute(),
|
||||
metadata => string.Equals("HiddenInput", metadata.TemplateHint, StringComparison.Ordinal),
|
||||
true
|
||||
},
|
||||
{
|
||||
new RequiredAttribute(),
|
||||
metadata => metadata.IsRequired,
|
||||
|
|
@ -466,6 +474,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Null(metadata.EditFormatString);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TemplateHint_AttributesHaveExpectedPrecedence()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "this is a hint";
|
||||
var hidden = new HiddenInputAttribute();
|
||||
var uiHint = new UIHintAttribute(expected);
|
||||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
var metadata = new CachedDataAnnotationsModelMetadata(
|
||||
provider,
|
||||
containerType: null,
|
||||
modelType: typeof(object),
|
||||
propertyName: null,
|
||||
attributes: new Attribute[] { hidden, uiHint });
|
||||
|
||||
// Act
|
||||
var result = metadata.TemplateHint;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_FindsBinderTypeProviders_Null()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace MvcTagHelpersWebSite.Models
|
||||
{
|
||||
public class Person
|
||||
{
|
||||
[HiddenInput(DisplayValue = false)]
|
||||
[Range(1, 100)]
|
||||
public int Number
|
||||
{
|
||||
|
|
@ -28,6 +30,7 @@ namespace MvcTagHelpersWebSite.Models
|
|||
}
|
||||
|
||||
[EnumDataType(typeof(Gender))]
|
||||
[UIHint("GenderUsingTagHelpers")]
|
||||
public Gender Gender
|
||||
{
|
||||
get;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
@Html.DropDownListFor(m => m.Employee.OfficeNumber, offices)
|
||||
</div>
|
||||
@Html.HiddenFor(m => m.Employee.Number)
|
||||
@Html.EditorFor(m => m.Employee.Number)
|
||||
@Html.ValidationSummary()
|
||||
<input type="submit" />
|
||||
</form>
|
||||
|
|
|
|||
Loading…
Reference in New Issue