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; }
|
public abstract BindingSource BindingSource { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not to convert an empty string value to <c>null</c> when
|
/// Gets a value indicating whether or not to convert an empty string value or one containing only whitespace
|
||||||
/// representing a model as text.
|
/// characters to <c>null</c> when representing a model as text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract bool ConvertEmptyStringToNull { get; }
|
public abstract bool ConvertEmptyStringToNull { get; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
var value = valueProviderResult.FirstValue;
|
var value = valueProviderResult.FirstValue;
|
||||||
|
|
||||||
object model = null;
|
object model;
|
||||||
if (!string.IsNullOrWhiteSpace(value))
|
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(
|
model = _typeConverter.ConvertFrom(
|
||||||
context: null,
|
context: null,
|
||||||
|
|
@ -56,16 +73,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
value: value);
|
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
|
// 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
|
// 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.
|
// current bindingContext. If not, an error is logged.
|
||||||
|
|
@ -75,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
bindingContext.ModelName,
|
bindingContext.ModelName,
|
||||||
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
|
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
|
||||||
valueProviderResult.ToString()));
|
valueProviderResult.ToString()));
|
||||||
|
|
||||||
return TaskCache.CompletedTask;
|
return TaskCache.CompletedTask;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
public IDictionary<object, object> AdditionalValues { get; } = new Dictionary<object, object>();
|
public IDictionary<object, object> AdditionalValues { get; } = new Dictionary<object, object>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not empty strings should be treated as <c>null</c>.
|
/// Gets or sets a value indicating whether or not to convert an empty string value or one containing only
|
||||||
/// See <see cref="ModelMetadata.ConvertEmptyStringToNull"/>
|
/// whitespace characters to <c>null</c> when representing a model as text. See
|
||||||
|
/// <see cref="ModelMetadata.ConvertEmptyStringToNull"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ConvertEmptyStringToNull { get; set; } = true;
|
public bool ConvertEmptyStringToNull { get; set; } = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,58 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
{
|
{
|
||||||
public class SimpleTypeModelBinderTest
|
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
|
public static TheoryData<Type> ConvertableTypeData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -125,26 +177,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
Assert.Empty(bindingContext.ModelState);
|
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]
|
[Theory]
|
||||||
[InlineData("")]
|
[InlineData("")]
|
||||||
[InlineData(" \t \r\n ")]
|
[InlineData(" \t \r\n ")]
|
||||||
|
|
@ -215,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
// Arrange
|
// Arrange
|
||||||
var bindingContext = GetBindingContext(typeof(int));
|
var bindingContext = GetBindingContext(typeof(int));
|
||||||
bindingContext.ValueProvider = new SimpleValueProvider
|
bindingContext.ValueProvider = new SimpleValueProvider
|
||||||
{
|
{
|
||||||
{ "theModelName", "42" }
|
{ "theModelName", "42" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue