Handle `!ConvertEmptyStringToNull` cases correctly in `SimpleTypeModelBinder`
- #4988 - preserve whitespace as the setting demands - correct previous `string.IsNullOrEmpty()` call to match previous `ValueProviderResultExtensions.ConvertTo()` use - short-circuit other `string`-to-`string` conversions (as `ValueProviderResultExtensions.ConvertTo()` does) - correct documentation of `ConvertEmptyStringToNull` properties - add more tests of these scenarios and remove duplicate `BindModel_ValidValueProviderResult_ConvertEmptyStringsToNull()` test
This commit is contained in:
parent
6016966dcf
commit
fae0e9a66e
|
|
@ -92,8 +92,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public abstract BindingSource BindingSource { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not to convert an empty string value to <c>null</c> when
|
||||
/// representing a model as text.
|
||||
/// Gets 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.
|
||||
/// </summary>
|
||||
public abstract bool ConvertEmptyStringToNull { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -47,8 +47,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
{
|
||||
var value = valueProviderResult.FirstValue;
|
||||
|
||||
object model = null;
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
object model;
|
||||
if (bindingContext.ModelType == typeof(string))
|
||||
{
|
||||
// Already have a string. No further conversion required but handle ConvertEmptyStringToNull.
|
||||
if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
model = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
model = value;
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
// Other than the StringConverter, converters Trim() the value then throw if the result is empty.
|
||||
model = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
model = _typeConverter.ConvertFrom(
|
||||
context: null,
|
||||
|
|
@ -56,16 +73,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
value: value);
|
||||
}
|
||||
|
||||
if (bindingContext.ModelType == typeof(string))
|
||||
{
|
||||
var modelAsString = model as string;
|
||||
if (bindingContext.ModelMetadata.ConvertEmptyStringToNull &&
|
||||
string.IsNullOrEmpty(modelAsString))
|
||||
{
|
||||
model = null;
|
||||
}
|
||||
}
|
||||
|
||||
// When converting newModel a null value may indicate a failed conversion for an otherwise required
|
||||
// model (can't set a ValueType to null). This detects if a null model value is acceptable given the
|
||||
// current bindingContext. If not, an error is logged.
|
||||
|
|
@ -75,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
|
||||
valueProviderResult.ToString()));
|
||||
|
||||
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
public IDictionary<object, object> AdditionalValues { get; } = new Dictionary<object, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not empty strings should be treated as <c>null</c>.
|
||||
/// See <see cref="ModelMetadata.ConvertEmptyStringToNull"/>
|
||||
/// 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
|
||||
/// <see cref="ModelMetadata.ConvertEmptyStringToNull"/>
|
||||
/// </summary>
|
||||
public bool ConvertEmptyStringToNull { get; set; } = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,58 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
{
|
||||
public class SimpleTypeModelBinderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("value")]
|
||||
[InlineData("intermediate whitespace")]
|
||||
[InlineData("\t untrimmed whitespace \t\r\n")]
|
||||
public async Task BindModelAsync_ReturnsProvidedString(string value)
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(typeof(string));
|
||||
bindingContext.ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "theModelName", value }
|
||||
};
|
||||
|
||||
var binder = new SimpleTypeModelBinder(typeof(string));
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Same(value, bindingContext.Result.Model);
|
||||
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" \t \r\n ")]
|
||||
public async Task BindModel_ReturnsProvidedWhitespaceString_WhenNotConvertEmptyStringToNull(string value)
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(typeof(string));
|
||||
bindingContext.ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "theModelName", value }
|
||||
};
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForType(typeof(string))
|
||||
.DisplayDetails(d => d.ConvertEmptyStringToNull = false);
|
||||
bindingContext.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string));
|
||||
|
||||
var binder = new SimpleTypeModelBinder(typeof(string));
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Same(value, bindingContext.Result.Model);
|
||||
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
||||
}
|
||||
|
||||
public static TheoryData<Type> ConvertableTypeData
|
||||
{
|
||||
get
|
||||
|
|
@ -125,26 +177,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
Assert.Empty(bindingContext.ModelState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_ValidValueProviderResult_ConvertEmptyStringsToNull()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = GetBindingContext(typeof(string));
|
||||
bindingContext.ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ "theModelName", string.Empty }
|
||||
};
|
||||
|
||||
var binder = new SimpleTypeModelBinder(typeof(string));
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" \t \r\n ")]
|
||||
|
|
@ -215,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
// Arrange
|
||||
var bindingContext = GetBindingContext(typeof(int));
|
||||
bindingContext.ValueProvider = new SimpleValueProvider
|
||||
{
|
||||
{
|
||||
{ "theModelName", "42" }
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue