Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit fae0e9a

Browse files
committed
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
1 parent 6016966 commit fae0e9a

File tree

4 files changed

+78
-38
lines changed

4 files changed

+78
-38
lines changed

src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ public string PropertyName
9292
public abstract BindingSource BindingSource { get; }
9393

9494
/// <summary>
95-
/// Gets a value indicating whether or not to convert an empty string value to <c>null</c> when
96-
/// representing a model as text.
95+
/// Gets a value indicating whether or not to convert an empty string value or one containing only whitespace
96+
/// characters to <c>null</c> when representing a model as text.
9797
/// </summary>
9898
public abstract bool ConvertEmptyStringToNull { get; }
9999

src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,30 @@ public Task BindModelAsync(ModelBindingContext bindingContext)
4747
{
4848
var value = valueProviderResult.FirstValue;
4949

50-
object model = null;
51-
if (!string.IsNullOrWhiteSpace(value))
52-
{
53-
model = _typeConverter.ConvertFrom(
54-
context: null,
55-
culture: valueProviderResult.Culture,
56-
value: value);
57-
}
58-
50+
object model;
5951
if (bindingContext.ModelType == typeof(string))
6052
{
61-
var modelAsString = model as string;
62-
if (bindingContext.ModelMetadata.ConvertEmptyStringToNull &&
63-
string.IsNullOrEmpty(modelAsString))
53+
// Already have a string. No further conversion required but handle ConvertEmptyStringToNull.
54+
if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && string.IsNullOrWhiteSpace(value))
6455
{
6556
model = null;
6657
}
58+
else
59+
{
60+
model = value;
61+
}
62+
}
63+
else if (string.IsNullOrWhiteSpace(value))
64+
{
65+
// Other than the StringConverter, converters Trim() the value then throw if the result is empty.
66+
model = null;
67+
}
68+
else
69+
{
70+
model = _typeConverter.ConvertFrom(
71+
context: null,
72+
culture: valueProviderResult.Culture,
73+
value: value);
6774
}
6875

6976
// When converting newModel a null value may indicate a failed conversion for an otherwise required
@@ -75,7 +82,7 @@ public Task BindModelAsync(ModelBindingContext bindingContext)
7582
bindingContext.ModelName,
7683
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
7784
valueProviderResult.ToString()));
78-
85+
7986
return TaskCache.CompletedTask;
8087
}
8188
else

src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ public class DisplayMetadata
1717
public IDictionary<object, object> AdditionalValues { get; } = new Dictionary<object, object>();
1818

1919
/// <summary>
20-
/// Gets or sets a value indicating whether or not empty strings should be treated as <c>null</c>.
21-
/// See <see cref="ModelMetadata.ConvertEmptyStringToNull"/>
20+
/// Gets or sets a value indicating whether or not to convert an empty string value or one containing only
21+
/// whitespace characters to <c>null</c> when representing a model as text. See
22+
/// <see cref="ModelMetadata.ConvertEmptyStringToNull"/>
2223
/// </summary>
2324
public bool ConvertEmptyStringToNull { get; set; } = true;
2425

test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderTest.cs

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,58 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
1111
{
1212
public class SimpleTypeModelBinderTest
1313
{
14+
[Theory]
15+
[InlineData(null)]
16+
[InlineData("value")]
17+
[InlineData("intermediate whitespace")]
18+
[InlineData("\t untrimmed whitespace \t\r\n")]
19+
public async Task BindModelAsync_ReturnsProvidedString(string value)
20+
{
21+
// Arrange
22+
var bindingContext = GetBindingContext(typeof(string));
23+
bindingContext.ValueProvider = new SimpleValueProvider
24+
{
25+
{ "theModelName", value }
26+
};
27+
28+
var binder = new SimpleTypeModelBinder(typeof(string));
29+
30+
// Act
31+
await binder.BindModelAsync(bindingContext);
32+
33+
// Assert
34+
Assert.Same(value, bindingContext.Result.Model);
35+
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
36+
}
37+
38+
[Theory]
39+
[InlineData("")]
40+
[InlineData(" \t \r\n ")]
41+
public async Task BindModel_ReturnsProvidedWhitespaceString_WhenNotConvertEmptyStringToNull(string value)
42+
{
43+
// Arrange
44+
var bindingContext = GetBindingContext(typeof(string));
45+
bindingContext.ValueProvider = new SimpleValueProvider
46+
{
47+
{ "theModelName", value }
48+
};
49+
50+
var metadataProvider = new TestModelMetadataProvider();
51+
metadataProvider
52+
.ForType(typeof(string))
53+
.DisplayDetails(d => d.ConvertEmptyStringToNull = false);
54+
bindingContext.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string));
55+
56+
var binder = new SimpleTypeModelBinder(typeof(string));
57+
58+
// Act
59+
await binder.BindModelAsync(bindingContext);
60+
61+
// Assert
62+
Assert.Same(value, bindingContext.Result.Model);
63+
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
64+
}
65+
1466
public static TheoryData<Type> ConvertableTypeData
1567
{
1668
get
@@ -125,26 +177,6 @@ public async Task BindModel_EmptyValueProviderResult_ReturnsFailed()
125177
Assert.Empty(bindingContext.ModelState);
126178
}
127179

128-
[Fact]
129-
public async Task BindModel_ValidValueProviderResult_ConvertEmptyStringsToNull()
130-
{
131-
// Arrange
132-
var bindingContext = GetBindingContext(typeof(string));
133-
bindingContext.ValueProvider = new SimpleValueProvider
134-
{
135-
{ "theModelName", string.Empty }
136-
};
137-
138-
var binder = new SimpleTypeModelBinder(typeof(string));
139-
140-
// Act
141-
await binder.BindModelAsync(bindingContext);
142-
143-
// Assert
144-
Assert.Null(bindingContext.Result.Model);
145-
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
146-
}
147-
148180
[Theory]
149181
[InlineData("")]
150182
[InlineData(" \t \r\n ")]
@@ -215,7 +247,7 @@ public async Task BindModel_ValidValueProviderResult_ReturnsModel()
215247
// Arrange
216248
var bindingContext = GetBindingContext(typeof(int));
217249
bindingContext.ValueProvider = new SimpleValueProvider
218-
{
250+
{
219251
{ "theModelName", "42" }
220252
};
221253

0 commit comments

Comments
 (0)