Add `DisplayMetadata.NullDisplayTextProvider` and similar properties

- #6730
- `DisplayFormatStringProvider`, `EditFormatStringProvider`

nits:
- use `<see langword="null"/>` more
- accept VS suggestions in changed files
This commit is contained in:
Doug Bunting 2018-01-07 21:47:01 -08:00
parent ecedbd5372
commit c9ac2e6c29
5 changed files with 482 additions and 35 deletions

View File

@ -205,7 +205,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}
/// <inheritdoc />
public override string DisplayFormatString => DisplayMetadata.DisplayFormatString;
public override string DisplayFormatString => DisplayMetadata.DisplayFormatStringProvider();
/// <inheritdoc />
public override string DisplayName
@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}
/// <inheritdoc />
public override string EditFormatString => DisplayMetadata.EditFormatString;
public override string EditFormatString => DisplayMetadata.EditFormatStringProvider();
/// <inheritdoc />
public override ModelMetadata ElementMetadata
@ -349,7 +349,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
BindingMetadata.ModelBindingMessageProvider;
/// <inheritdoc />
public override string NullDisplayText => DisplayMetadata.NullDisplayText;
public override string NullDisplayText => DisplayMetadata.NullDisplayTextProvider();
/// <inheritdoc />
public override int Order => DisplayMetadata.Order;
@ -459,4 +459,4 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
return _provider.GetMetadataForProperties(modelType);
}
}
}
}

View File

@ -11,6 +11,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// </summary>
public class DisplayMetadata
{
private Func<string> _displayFormatStringProvider = () => null;
private Func<string> _editFormatStringProvider = () => null;
private Func<string> _nullDisplayTextProvider = () => null;
/// <summary>
/// Gets a set of additional values. See <see cref="ModelMetadata.AdditionalValues"/>
/// </summary>
@ -18,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// <summary>
/// Gets or sets a value indicating whether or not to convert an empty string value or one containing only
/// whitespace characters to <c>null</c> when representing a model as text. See
/// whitespace characters to <see langword="null"/> when representing a model as text. See
/// <see cref="ModelMetadata.ConvertEmptyStringToNull"/>
/// </summary>
public bool ConvertEmptyStringToNull { get; set; } = true;
@ -39,7 +43,44 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// Gets or sets a display format string for the model.
/// See <see cref="ModelMetadata.DisplayFormatString"/>
/// </summary>
public string DisplayFormatString { get; set; }
/// <remarks>
/// Setting <see cref="DisplayFormatString"/> also changes <see cref="DisplayFormatStringProvider"/>.
/// </remarks>
public string DisplayFormatString
{
get
{
return DisplayFormatStringProvider();
}
set
{
DisplayFormatStringProvider = () => value;
}
}
/// <summary>
/// Gets or sets a delegate which is used to get the display format string for the model. See
/// <see cref="ModelMetadata.DisplayFormatString"/>.
/// </summary>
/// <remarks>
/// Setting <see cref="DisplayFormatStringProvider"/> also changes <see cref="DisplayFormatString"/>.
/// </remarks>
public Func<string> DisplayFormatStringProvider
{
get
{
return _displayFormatStringProvider;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_displayFormatStringProvider = value;
}
}
/// <summary>
/// Gets or sets a delegate which is used to get a value for the
@ -52,10 +93,56 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// See <see cref="ModelMetadata.EditFormatString"/>
/// </summary>
/// <remarks>
/// <see cref="IDisplayMetadataProvider"/> instances that set this property to a non-<c>null</c>, non-empty,
/// non-default value should also set <see cref="HasNonDefaultEditFormat"/> to <c>true</c>.
/// <para>
/// Setting <see cref="EditFormatString"/> also changes <see cref="EditFormatStringProvider"/>.
/// </para>
/// <para>
/// <see cref="IDisplayMetadataProvider"/> instances that set this property to a non-<see langword="null"/>,
/// non-empty, non-default value should also set <see cref="HasNonDefaultEditFormat"/> to
/// <see langword="true"/>.
/// </para>
/// </remarks>
public string EditFormatString { get; set; }
public string EditFormatString
{
get
{
return EditFormatStringProvider();
}
set
{
EditFormatStringProvider = () => value;
}
}
/// <summary>
/// Gets or sets a delegate which is used to get the edit format string for the model. See
/// <see cref="ModelMetadata.EditFormatString"/>.
/// </summary>
/// <remarks>
/// <para>
/// Setting <see cref="EditFormatStringProvider"/> also changes <see cref="EditFormatString"/>.
/// </para>
/// <para>
/// <see cref="IDisplayMetadataProvider"/> instances that set this property to a non-default value should
/// also set <see cref="HasNonDefaultEditFormat"/> to <see langword="true"/>.
/// </para>
/// </remarks>
public Func<string> EditFormatStringProvider
{
get
{
return _editFormatStringProvider;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_editFormatStringProvider = value;
}
}
/// <summary>
/// Gets the ordered and grouped display names and values of all <see cref="System.Enum"/> values in
@ -105,10 +192,47 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
public bool IsFlagsEnum { get; set; }
/// <summary>
/// Gets or sets the text to display when the model value is null.
/// Gets or sets the text to display when the model value is <see langword="null"/>.
/// See <see cref="ModelMetadata.NullDisplayText"/>
/// </summary>
public string NullDisplayText { get; set; }
/// <remarks>
/// Setting <see cref="NullDisplayText"/> also changes <see cref="NullDisplayTextProvider"/>.
/// </remarks>
public string NullDisplayText
{
get
{
return NullDisplayTextProvider();
}
set
{
NullDisplayTextProvider = () => value;
}
}
/// <summary>
/// Gets or sets a delegate which is used to get the text to display when the model is <see langword="null"/>.
/// See <see cref="ModelMetadata.NullDisplayText"/>.
/// </summary>
/// <remarks>
/// Setting <see cref="NullDisplayTextProvider"/> also changes <see cref="NullDisplayText"/>.
/// </remarks>
public Func<string> NullDisplayTextProvider
{
get
{
return _nullDisplayTextProvider;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_nullDisplayTextProvider = value;
}
}
/// <summary>
/// Gets or sets the order.
@ -146,4 +270,4 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// </summary>
public string TemplateHint { get; set; }
}
}
}

View File

@ -115,6 +115,117 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
Assert.Equal(typeof(Exception), metadata.ContainerType);
}
[Fact]
public void DisplayFormatString_DoesNotCacheInitialDelegateValue()
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForProperty(
typeof(string),
nameof(TypeWithProperties.PublicGetPublicSetProperty),
typeof(TypeWithProperties));
var attributes = new ModelAttributes(Array.Empty<object>(), Array.Empty<object>(), null);
var displayFormat = "initial format";
var cache = new DefaultMetadataDetails(key, attributes)
{
DisplayMetadata = new DisplayMetadata
{
DisplayFormatStringProvider = () => displayFormat,
},
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
foreach (var newFormat in new[] { "one", "two", "three" })
{
// Arrange n
displayFormat = newFormat;
// Act n
var result = metadata.DisplayFormatString;
// Assert n
Assert.Equal(newFormat, result);
}
}
[Fact]
public void EditFormatString_DoesNotCacheInitialDelegateValue()
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForProperty(
typeof(string),
nameof(TypeWithProperties.PublicGetPublicSetProperty),
typeof(TypeWithProperties));
var attributes = new ModelAttributes(Array.Empty<object>(), Array.Empty<object>(), null);
var editFormat = "initial format";
var cache = new DefaultMetadataDetails(key, attributes)
{
DisplayMetadata = new DisplayMetadata
{
EditFormatStringProvider = () => editFormat,
},
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
foreach (var newFormat in new[] { "one", "two", "three" })
{
// Arrange n
editFormat = newFormat;
// Act n
var result = metadata.EditFormatString;
// Assert n
Assert.Equal(newFormat, result);
}
}
[Fact]
public void NullDisplayText_DoesNotCacheInitialDelegateValue()
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForProperty(
typeof(string),
nameof(TypeWithProperties.PublicGetPublicSetProperty),
typeof(TypeWithProperties));
var attributes = new ModelAttributes(Array.Empty<object>(), Array.Empty<object>(), null);
var nullDisplay = "initial display text";
var cache = new DefaultMetadataDetails(key, attributes)
{
DisplayMetadata = new DisplayMetadata
{
NullDisplayTextProvider = () => nullDisplay,
},
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
foreach (var newDisplay in new[] { "one", "two", "three" })
{
// Arrange n
nullDisplay = newDisplay;
// Act n
var result = metadata.NullDisplayText;
// Assert n
Assert.Equal(newDisplay, result);
}
}
[Theory]
[InlineData(typeof(object))]
[InlineData(typeof(int))]
@ -188,10 +299,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForType(modelType);
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
cache.BindingMetadata = new BindingMetadata()
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null))
{
IsBindingAllowed = false, // Will be ignored.
BindingMetadata = new BindingMetadata()
{
IsBindingAllowed = false, // Will be ignored.
},
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
@ -213,10 +326,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForType(modelType);
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
cache.BindingMetadata = new BindingMetadata()
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null))
{
IsBindingRequired = true, // Will be ignored.
BindingMetadata = new BindingMetadata()
{
IsBindingRequired = true, // Will be ignored.
},
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
@ -461,9 +576,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
{
var propertyCache = new DefaultMetadataDetails(
ModelMetadataIdentity.ForProperty(typeof(int), kvp.Key, typeof(string)),
attributes: new ModelAttributes(new object[0], new object[0], null));
attributes: new ModelAttributes(new object[0], new object[0], null))
{
DisplayMetadata = new DisplayMetadata(),
};
propertyCache.DisplayMetadata = new DisplayMetadata();
propertyCache.DisplayMetadata.Order = kvp.Value;
expectedProperties.Add(new DefaultModelMetadata(
@ -539,10 +656,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(int[]));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
cache.BindingMetadata = new BindingMetadata()
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null))
{
IsReadOnly = true, // Will be ignored.
BindingMetadata = new BindingMetadata()
{
IsReadOnly = true, // Will be ignored.
},
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
@ -650,7 +769,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(TypeWithProperties));
var key = ModelMetadataIdentity.ForType(modelType);
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
@ -683,10 +802,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(int));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
cache.ValidationMetadata = new ValidationMetadata
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null))
{
PropertyValidationFilter = value,
ValidationMetadata = new ValidationMetadata
{
PropertyValidationFilter = value,
},
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
@ -706,10 +827,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(int));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
cache.ValidationMetadata = new ValidationMetadata()
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null))
{
ValidateChildren = true,
ValidationMetadata = new ValidationMetadata()
{
ValidateChildren = true,
},
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
@ -729,10 +852,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(XmlDocument));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
cache.ValidationMetadata = new ValidationMetadata()
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null))
{
ValidateChildren = false,
ValidationMetadata = new ValidationMetadata()
{
ValidateChildren = false,
},
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);

View File

@ -0,0 +1,178 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
{
public class DisplayMetadataTest
{
[Fact]
public void DisplayFormatString_AffectsBothDisplayFormatProperties()
{
// Arrange
var displayMetadata = new DisplayMetadata();
// Act
displayMetadata.DisplayFormatString = "expected string";
// Assert
Assert.Equal("expected string", displayMetadata.DisplayFormatString);
Assert.Equal("expected string", displayMetadata.DisplayFormatStringProvider());
}
[Fact]
public void DisplayFormatStringProvider_AffectsBothDisplayFormatProperties()
{
// Arrange
var displayMetadata = new DisplayMetadata();
// Act
displayMetadata.DisplayFormatStringProvider = () => "expected string";
// Assert
Assert.Equal("expected string", displayMetadata.DisplayFormatString);
Assert.Equal("expected string", displayMetadata.DisplayFormatStringProvider());
}
[Fact]
public void DisplayFormatString_LastSettingWins()
{
// Arrange
var displayMetadata = new DisplayMetadata();
// Act 1
displayMetadata.DisplayFormatString = "first string";
// Assert 1
Assert.Equal("first string", displayMetadata.DisplayFormatString);
Assert.Equal("first string", displayMetadata.DisplayFormatStringProvider());
// Act 2
displayMetadata.DisplayFormatStringProvider = () => "second string";
// Assert 2
Assert.Equal("second string", displayMetadata.DisplayFormatString);
Assert.Equal("second string", displayMetadata.DisplayFormatStringProvider());
// Act 3
displayMetadata.DisplayFormatString = "third string";
// Assert 3
Assert.Equal("third string", displayMetadata.DisplayFormatString);
Assert.Equal("third string", displayMetadata.DisplayFormatStringProvider());
}
[Fact]
public void EditFormatString_AffectsBothEditFormatProperties()
{
// Arrange
var displayMetadata = new DisplayMetadata();
// Act
displayMetadata.EditFormatString = "expected string";
// Assert
Assert.Equal("expected string", displayMetadata.EditFormatString);
Assert.Equal("expected string", displayMetadata.EditFormatStringProvider());
}
[Fact]
public void EditFormatStringProvider_AffectsBothEditFormatProperties()
{
// Arrange
var displayMetadata = new DisplayMetadata();
// Act
displayMetadata.EditFormatStringProvider = () => "expected string";
// Assert
Assert.Equal("expected string", displayMetadata.EditFormatString);
Assert.Equal("expected string", displayMetadata.EditFormatStringProvider());
}
[Fact]
public void EditFormatString_LastSettingWins()
{
// Arrange
var displayMetadata = new DisplayMetadata();
// Act 1
displayMetadata.EditFormatString = "first string";
// Assert 1
Assert.Equal("first string", displayMetadata.EditFormatString);
Assert.Equal("first string", displayMetadata.EditFormatStringProvider());
// Act 2
displayMetadata.EditFormatStringProvider = () => "second string";
// Assert 2
Assert.Equal("second string", displayMetadata.EditFormatString);
Assert.Equal("second string", displayMetadata.EditFormatStringProvider());
// Act 3
displayMetadata.EditFormatString = "third string";
// Assert 3
Assert.Equal("third string", displayMetadata.EditFormatString);
Assert.Equal("third string", displayMetadata.EditFormatStringProvider());
}
[Fact]
public void NullDisplayText_AffectsBothNullDisplayProperties()
{
// Arrange
var displayMetadata = new DisplayMetadata();
// Act
displayMetadata.NullDisplayText = "expected string";
// Assert
Assert.Equal("expected string", displayMetadata.NullDisplayText);
Assert.Equal("expected string", displayMetadata.NullDisplayTextProvider());
}
[Fact]
public void NullDisplayTextProvider_AffectsBothNullDisplayProperties()
{
// Arrange
var displayMetadata = new DisplayMetadata();
// Act
displayMetadata.NullDisplayTextProvider = () => "expected string";
// Assert
Assert.Equal("expected string", displayMetadata.NullDisplayText);
Assert.Equal("expected string", displayMetadata.NullDisplayTextProvider());
}
[Fact]
public void NullDisplayText_LastSettingWins()
{
// Arrange
var displayMetadata = new DisplayMetadata();
// Act 1
displayMetadata.NullDisplayText = "first string";
// Assert 1
Assert.Equal("first string", displayMetadata.NullDisplayText);
Assert.Equal("first string", displayMetadata.NullDisplayTextProvider());
// Act 2
displayMetadata.NullDisplayTextProvider = () => "second string";
// Assert 2
Assert.Equal("second string", displayMetadata.NullDisplayText);
Assert.Equal("second string", displayMetadata.NullDisplayTextProvider());
// Act 3
displayMetadata.NullDisplayText = "third string";
// Assert 3
Assert.Equal("third string", displayMetadata.NullDisplayText);
Assert.Equal("third string", displayMetadata.NullDisplayTextProvider());
}
}
}

View File

@ -38,13 +38,33 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
{ new DisplayFormatAttribute() { ConvertEmptyStringToNull = true }, d => d.ConvertEmptyStringToNull, true },
{ new DisplayFormatAttribute() { DataFormatString = "{0:G}" }, d => d.DisplayFormatString, "{0:G}" },
{
new DisplayFormatAttribute() { DataFormatString = "{0:G}" },
d => d.DisplayFormatStringProvider(),
"{0:G}"
},
{
new DisplayFormatAttribute() { DataFormatString = "{0:G}", ApplyFormatInEditMode = true },
d => d.EditFormatString,
"{0:G}"
},
{
new DisplayFormatAttribute() { DataFormatString = "{0:G}", ApplyFormatInEditMode = true },
d => d.EditFormatStringProvider(),
"{0:G}"
},
{
new DisplayFormatAttribute() { DataFormatString = "{0:G}", ApplyFormatInEditMode = true },
d => d.HasNonDefaultEditFormat,
true
},
{ new DisplayFormatAttribute() { HtmlEncode = false }, d => d.HtmlEncode, false },
{ new DisplayFormatAttribute() { NullDisplayText = "(null)" }, d => d.NullDisplayText, "(null)" },
{
new DisplayFormatAttribute() { NullDisplayText = "(null)" },
d => d.NullDisplayTextProvider(),
"(null)"
},
{ new DisplayNameAttribute("DisplayNameValue"), d => d.DisplayName(), "DisplayNameValue"},
{ new HiddenInputAttribute() { DisplayValue = false }, d => d.HideSurroundingHtml, true },
@ -299,7 +319,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
.Returns(() => sharedLocalizer.Object);
var options = Options.Create(new MvcDataAnnotationsLocalizationOptions());
bool dataAnnotationLocalizerProviderWasUsed = false;
var dataAnnotationLocalizerProviderWasUsed = false;
options.Value.DataAnnotationLocalizerProvider = (type, stringLocalizerFactory) =>
{
dataAnnotationLocalizerProviderWasUsed = true;
@ -1418,4 +1438,4 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public string Name { get; private set; }
}
}
}
}