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

Commit 6b1bba8

Browse files
author
sornaks
committed
Issue #913 - Model-binding is being case-sensitive when binding Url data to Enum parameter.
Fix: Using TypeConverter solves this problem. Issue #1123 - TypeConverterModelBinder cannot bind "byte" and "short". Fix: Modified code to use TypeConverter which can handle these scenarios.
1 parent 18e11f5 commit 6b1bba8

File tree

4 files changed

+59
-111
lines changed

4 files changed

+59
-111
lines changed

src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ValueProviderResult.cs

Lines changed: 38 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections;
6+
using System.ComponentModel;
67
using System.Globalization;
78
using System.Reflection;
89

@@ -53,7 +54,7 @@ public virtual object ConvertTo([NotNull] Type type, CultureInfo culture)
5354
if (value == null)
5455
{
5556
// treat null route parameters as though they were the default value for the type
56-
return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) :
57+
return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) :
5758
null;
5859
}
5960

@@ -68,7 +69,7 @@ public virtual object ConvertTo([NotNull] Type type, CultureInfo culture)
6869

6970
public static bool CanConvertFromString(Type destinationType)
7071
{
71-
return GetConverterDelegate(destinationType) != null;
72+
return GetConverterDelegate(typeof(string), destinationType) != null;
7273
}
7374

7475
private object ConvertSimpleType(CultureInfo culture, object value, Type destinationType)
@@ -88,7 +89,7 @@ private object ConvertSimpleType(CultureInfo culture, object value, Type destina
8889
return null;
8990
}
9091

91-
var converter = GetConverterDelegate(destinationType);
92+
var converter = GetConverterDelegate(value.GetType(), destinationType);
9293
if (converter == null)
9394
{
9495
var message = Resources.FormatValueProviderResult_NoConverterExists(value.GetType(), destinationType);
@@ -144,121 +145,58 @@ private object UnwrapPossibleArrayType(CultureInfo culture, object value, Type d
144145
return ConvertSimpleType(culture, value, destinationType);
145146
}
146147

147-
private static Func<object, CultureInfo, object> GetConverterDelegate(Type destinationType)
148+
private static Func<object, CultureInfo, object> GetConverterDelegate(Type sourceType, Type destinationType)
148149
{
149150
destinationType = UnwrapNullableType(destinationType);
150-
151-
if (destinationType == typeof(string))
152-
{
153-
return (value, culture) => Convert.ToString(value, culture);
154-
}
155-
156-
if (destinationType == typeof(int))
157-
{
158-
return (value, culture) => Convert.ToInt32(value, culture);
159-
}
160-
161-
if (destinationType == typeof(long))
162-
{
163-
return (value, culture) => Convert.ToInt64(value, culture);
164-
}
165-
166-
if (destinationType == typeof(float))
167-
{
168-
return (value, culture) => Convert.ToSingle(value, culture);
169-
}
170-
171-
if (destinationType == typeof(double))
151+
var converter = TypeDescriptor.GetConverter(destinationType);
152+
var canConvertFrom = converter.CanConvertFrom(sourceType);
153+
if (!canConvertFrom)
172154
{
173-
return (value, culture) => Convert.ToDouble(value, culture);
155+
converter = TypeDescriptor.GetConverter(sourceType);
174156
}
175-
176-
if (destinationType == typeof(decimal))
177-
{
178-
return (value, culture) => Convert.ToDecimal(value, culture);
179-
}
180-
181-
if (destinationType == typeof(bool))
182-
{
183-
return (value, culture) => Convert.ToBoolean(value, culture);
184-
}
185-
186-
if (destinationType == typeof(DateTime))
157+
if (!(canConvertFrom || converter.CanConvertTo(destinationType)))
187158
{
188-
return (value, culture) =>
159+
if (destinationType.IsEnum())
189160
{
190-
ThrowIfNotStringType(value, destinationType);
191-
return DateTime.Parse((string)value, culture);
192-
};
193-
}
161+
return (value, culture) =>
162+
{
163+
// EnumConverter cannot convert integer, so we verify manually
164+
if ((value is int))
165+
{
166+
if (Enum.IsDefined(destinationType, value))
167+
{
168+
return Enum.ToObject(destinationType, (int)value);
169+
}
194170

195-
if (destinationType == typeof(DateTimeOffset))
196-
{
197-
return (value, culture) =>
198-
{
199-
ThrowIfNotStringType(value, destinationType);
200-
return DateTimeOffset.Parse((string)value, culture);
201-
};
202-
}
171+
throw new FormatException(
172+
Resources.FormatValueProviderResult_CannotConvertEnum(value, destinationType));
173+
}
203174

204-
if (destinationType == typeof(TimeSpan))
205-
{
206-
return (value, culture) =>
207-
{
208-
ThrowIfNotStringType(value, destinationType);
209-
return TimeSpan.Parse((string)value, culture);
210-
};
211-
}
175+
throw new InvalidOperationException(
176+
Resources.FormatValueProviderResult_NoConverterExists(sourceType, destinationType));
177+
};
178+
}
212179

213-
if (destinationType == typeof(Guid))
214-
{
215-
return (value, culture) =>
216-
{
217-
ThrowIfNotStringType(value, destinationType);
218-
return Guid.Parse((string)value);
219-
};
180+
return null;
220181
}
221182

222-
if (destinationType.GetTypeInfo().IsEnum)
183+
return (value, culture) =>
223184
{
224-
return (value, culture) =>
185+
try
225186
{
226-
// EnumConverter cannot convert integer, so we verify manually
227-
if ((value is int))
228-
{
229-
if (Enum.IsDefined(destinationType, value))
230-
{
231-
return Enum.ToObject(destinationType, (int)value);
232-
}
233-
234-
throw new FormatException(
235-
Resources.FormatValueProviderResult_CannotConvertEnum(value,
236-
destinationType));
237-
}
238-
else
239-
{
240-
ThrowIfNotStringType(value, destinationType);
241-
return Enum.Parse(destinationType, (string)value);
242-
}
243-
};
244-
}
245-
246-
return null;
187+
return canConvertFrom ? converter.ConvertFrom(null, culture, value) : converter.ConvertTo(null, culture, value, destinationType);
188+
}
189+
catch (Exception ex)
190+
{
191+
throw new InvalidOperationException(
192+
Resources.FormatValueProviderResult_ConversionThrew(sourceType, destinationType), ex);
193+
}
194+
};
247195
}
248196

249197
private static Type UnwrapNullableType(Type destinationType)
250198
{
251199
return Nullable.GetUnderlyingType(destinationType) ?? destinationType;
252200
}
253-
254-
private static void ThrowIfNotStringType(object value, Type destinationType)
255-
{
256-
var type = value.GetType();
257-
if (type != typeof(string))
258-
{
259-
var message = Resources.FormatValueProviderResult_NoConverterExists(type, destinationType);
260-
throw new InvalidOperationException(message);
261-
}
262-
}
263201
}
264202
}

src/Microsoft.AspNet.Mvc.ModelBinding/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"System.Collections": "4.0.10-beta-*",
2525
"System.Collections.Concurrent": "4.0.0-beta-*",
2626
"System.ComponentModel": "4.0.0-beta-*",
27+
"System.ComponentModel.TypeConverter": "4.0.0-beta-*",
2728
"System.Diagnostics.Contracts": "4.0.0-beta-*",
2829
"System.Diagnostics.Debug": "4.0.10-beta-*",
2930
"System.Diagnostics.Tools": "4.0.0-beta-*",

test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/TypeConverterModelBinderTest.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public async Task BindModel_ReturnsFalse_IfTypeCannotBeConverted(Type destinatio
3434
}
3535

3636
[Theory]
37+
[InlineData(typeof(byte))]
38+
[InlineData(typeof(short))]
3739
[InlineData(typeof(int))]
3840
[InlineData(typeof(long))]
3941
[InlineData(typeof(Guid))]
@@ -62,8 +64,8 @@ public async Task BindModel_ReturnsTrue_IfTypeCanBeConverted(Type destinationTyp
6264
public async Task BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState()
6365
{
6466
// Arrange
65-
var message = TestPlatformHelper.IsMono ? "Input string was not in the correct format" :
66-
"Input string was not in a correct format.";
67+
var message = "The parameter conversion from type 'System.String' to type 'System.Int32' failed." +
68+
" See the inner exception for more information.";
6769
var bindingContext = GetBindingContext(typeof(int));
6870
bindingContext.ValueProvider = new SimpleHttpValueProvider
6971
{
@@ -78,7 +80,7 @@ public async Task BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState(
7880
// Assert
7981
Assert.True(retVal);
8082
Assert.Null(bindingContext.Model);
81-
Assert.Equal(false, bindingContext.ModelState.IsValid);
83+
Assert.False(bindingContext.ModelState.IsValid);
8284
var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors);
8385
Assert.Equal(message, error.ErrorMessage);
8486
}

test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ValueProviderResultTest.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ public void ConvertToReturnsValueIfArrayElementInstanceOfDestinationType()
289289
[Theory]
290290
[InlineData(new object[] { new[] { 1, 0 } })]
291291
[InlineData(new object[] { new[] { "Value1", "Value0" } })]
292+
[InlineData(new object[] { new[] { "Value1", "value0" } })]
292293
public void ConvertTo_ConvertsEnumArrays(object value)
293294
{
294295
// Arrange
@@ -318,16 +319,20 @@ public void ConvertToReturnsValueIfInstanceOfDestinationType()
318319
}
319320

320321
[Theory]
321-
[InlineData(typeof(int), typeof(FormatException))]
322-
[InlineData(typeof(double?), typeof(FormatException))]
323-
[InlineData(typeof(MyEnum?), typeof(ArgumentException))]
324-
public void ConvertToThrowsIfConverterThrows(Type destinationType, Type exceptionType)
322+
[InlineData(typeof(int), typeof(InvalidOperationException), typeof(Exception))]
323+
[InlineData(typeof(double?), typeof(InvalidOperationException), null)]
324+
[InlineData(typeof(MyEnum?), typeof(InvalidOperationException), typeof(FormatException))]
325+
public void ConvertToThrowsIfConverterThrows(Type destinationType, Type exceptionType, Type innerExceptionType)
325326
{
326327
// Arrange
327328
var vpr = new ValueProviderResult("this-is-not-a-valid-value", null, CultureInfo.InvariantCulture);
328329

329330
// Act & Assert
330-
Assert.Throws(exceptionType, () => vpr.ConvertTo(destinationType));
331+
var ex = Assert.Throws(exceptionType, () => vpr.ConvertTo(destinationType));
332+
if (innerExceptionType != null)
333+
{
334+
Assert.Equal(innerExceptionType, ex.InnerException.GetType());
335+
}
331336
}
332337

333338
[Fact]
@@ -355,11 +360,10 @@ public void ConvertToUsesProvidedCulture()
355360

356361
// Act
357362
var cultureResult = (decimal)vpr.ConvertTo(typeof(decimal), frCulture);
358-
var result = (decimal)vpr.ConvertTo(typeof(decimal));
359363

360364
// Assert
361365
Assert.Equal(12.5M, cultureResult);
362-
Assert.Equal(125, result);
366+
Assert.Throws(typeof(InvalidOperationException), () => (decimal)vpr.ConvertTo(typeof(decimal)));
363367
}
364368

365369
[Fact]
@@ -387,14 +391,17 @@ public static IEnumerable<object[]> IntrinsicConversionData
387391
{
388392
get
389393
{
390-
yield return new object[] { 42, 42M };
391394
yield return new object[] { 42, 42L };
395+
yield return new object[] { 42, (short)42 };
392396
yield return new object[] { 42, (float)42.0 };
393397
yield return new object[] { 42, (double)42.0 };
394398
yield return new object[] { 42M, 42 };
395399
yield return new object[] { 42L, 42 };
400+
yield return new object[] { 42, (byte)42 };
401+
yield return new object[] { (short)42, 42 };
396402
yield return new object[] { (float)42.0, 42 };
397403
yield return new object[] { (double)42.0, 42 };
404+
yield return new object[] { (byte)42, 42 };
398405
yield return new object[] { "2008-01-01", new DateTime(2008, 01, 01) };
399406
yield return new object[] { "00:00:20", TimeSpan.FromSeconds(20) };
400407
yield return new object[]

0 commit comments

Comments
 (0)