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

Commit

Permalink
Issue #913 - Model-binding is being case-sensitive when binding Url d…
Browse files Browse the repository at this point in the history
…ata 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.
  • Loading branch information
sornaks committed Oct 12, 2014
1 parent 18e11f5 commit 6b1bba8
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;

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

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

public static bool CanConvertFromString(Type destinationType)
{
return GetConverterDelegate(destinationType) != null;
return GetConverterDelegate(typeof(string), destinationType) != null;
}

private object ConvertSimpleType(CultureInfo culture, object value, Type destinationType)
Expand All @@ -88,7 +89,7 @@ private object ConvertSimpleType(CultureInfo culture, object value, Type destina
return null;
}

var converter = GetConverterDelegate(destinationType);
var converter = GetConverterDelegate(value.GetType(), destinationType);
if (converter == null)
{
var message = Resources.FormatValueProviderResult_NoConverterExists(value.GetType(), destinationType);
Expand Down Expand Up @@ -144,121 +145,58 @@ private object UnwrapPossibleArrayType(CultureInfo culture, object value, Type d
return ConvertSimpleType(culture, value, destinationType);
}

private static Func<object, CultureInfo, object> GetConverterDelegate(Type destinationType)
private static Func<object, CultureInfo, object> GetConverterDelegate(Type sourceType, Type destinationType)
{
destinationType = UnwrapNullableType(destinationType);

if (destinationType == typeof(string))
{
return (value, culture) => Convert.ToString(value, culture);
}

if (destinationType == typeof(int))
{
return (value, culture) => Convert.ToInt32(value, culture);
}

if (destinationType == typeof(long))
{
return (value, culture) => Convert.ToInt64(value, culture);
}

if (destinationType == typeof(float))
{
return (value, culture) => Convert.ToSingle(value, culture);
}

if (destinationType == typeof(double))
var converter = TypeDescriptor.GetConverter(destinationType);
var canConvertFrom = converter.CanConvertFrom(sourceType);
if (!canConvertFrom)
{
return (value, culture) => Convert.ToDouble(value, culture);
converter = TypeDescriptor.GetConverter(sourceType);
}

if (destinationType == typeof(decimal))
{
return (value, culture) => Convert.ToDecimal(value, culture);
}

if (destinationType == typeof(bool))
{
return (value, culture) => Convert.ToBoolean(value, culture);
}

if (destinationType == typeof(DateTime))
if (!(canConvertFrom || converter.CanConvertTo(destinationType)))
{
return (value, culture) =>
if (destinationType.IsEnum())
{
ThrowIfNotStringType(value, destinationType);
return DateTime.Parse((string)value, culture);
};
}
return (value, culture) =>
{
// EnumConverter cannot convert integer, so we verify manually
if ((value is int))
{
if (Enum.IsDefined(destinationType, value))
{
return Enum.ToObject(destinationType, (int)value);
}
if (destinationType == typeof(DateTimeOffset))
{
return (value, culture) =>
{
ThrowIfNotStringType(value, destinationType);
return DateTimeOffset.Parse((string)value, culture);
};
}
throw new FormatException(
Resources.FormatValueProviderResult_CannotConvertEnum(value, destinationType));
}
if (destinationType == typeof(TimeSpan))
{
return (value, culture) =>
{
ThrowIfNotStringType(value, destinationType);
return TimeSpan.Parse((string)value, culture);
};
}
throw new InvalidOperationException(
Resources.FormatValueProviderResult_NoConverterExists(sourceType, destinationType));
};
}

if (destinationType == typeof(Guid))
{
return (value, culture) =>
{
ThrowIfNotStringType(value, destinationType);
return Guid.Parse((string)value);
};
return null;
}

if (destinationType.GetTypeInfo().IsEnum)
return (value, culture) =>
{
return (value, culture) =>
try
{
// EnumConverter cannot convert integer, so we verify manually
if ((value is int))
{
if (Enum.IsDefined(destinationType, value))
{
return Enum.ToObject(destinationType, (int)value);
}
throw new FormatException(
Resources.FormatValueProviderResult_CannotConvertEnum(value,
destinationType));
}
else
{
ThrowIfNotStringType(value, destinationType);
return Enum.Parse(destinationType, (string)value);
}
};
}

return null;
return canConvertFrom ? converter.ConvertFrom(null, culture, value) : converter.ConvertTo(null, culture, value, destinationType);
}
catch (Exception ex)
{
throw new InvalidOperationException(
Resources.FormatValueProviderResult_ConversionThrew(sourceType, destinationType), ex);
}
};
}

private static Type UnwrapNullableType(Type destinationType)
{
return Nullable.GetUnderlyingType(destinationType) ?? destinationType;
}

private static void ThrowIfNotStringType(object value, Type destinationType)
{
var type = value.GetType();
if (type != typeof(string))
{
var message = Resources.FormatValueProviderResult_NoConverterExists(type, destinationType);
throw new InvalidOperationException(message);
}
}
}
}
1 change: 1 addition & 0 deletions src/Microsoft.AspNet.Mvc.ModelBinding/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"System.Collections": "4.0.10-beta-*",
"System.Collections.Concurrent": "4.0.0-beta-*",
"System.ComponentModel": "4.0.0-beta-*",
"System.ComponentModel.TypeConverter": "4.0.0-beta-*",
"System.Diagnostics.Contracts": "4.0.0-beta-*",
"System.Diagnostics.Debug": "4.0.10-beta-*",
"System.Diagnostics.Tools": "4.0.0-beta-*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public async Task BindModel_ReturnsFalse_IfTypeCannotBeConverted(Type destinatio
}

[Theory]
[InlineData(typeof(byte))]
[InlineData(typeof(short))]
[InlineData(typeof(int))]
[InlineData(typeof(long))]
[InlineData(typeof(Guid))]
Expand Down Expand Up @@ -62,8 +64,8 @@ public async Task BindModel_ReturnsTrue_IfTypeCanBeConverted(Type destinationTyp
public async Task BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState()
{
// Arrange
var message = TestPlatformHelper.IsMono ? "Input string was not in the correct format" :
"Input string was not in a correct format.";
var message = "The parameter conversion from type 'System.String' to type 'System.Int32' failed." +
" See the inner exception for more information.";
var bindingContext = GetBindingContext(typeof(int));
bindingContext.ValueProvider = new SimpleHttpValueProvider
{
Expand All @@ -78,7 +80,7 @@ public async Task BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState(
// Assert
Assert.True(retVal);
Assert.Null(bindingContext.Model);
Assert.Equal(false, bindingContext.ModelState.IsValid);
Assert.False(bindingContext.ModelState.IsValid);
var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors);
Assert.Equal(message, error.ErrorMessage);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ public void ConvertToReturnsValueIfArrayElementInstanceOfDestinationType()
[Theory]
[InlineData(new object[] { new[] { 1, 0 } })]
[InlineData(new object[] { new[] { "Value1", "Value0" } })]
[InlineData(new object[] { new[] { "Value1", "value0" } })]
public void ConvertTo_ConvertsEnumArrays(object value)
{
// Arrange
Expand Down Expand Up @@ -318,16 +319,20 @@ public void ConvertToReturnsValueIfInstanceOfDestinationType()
}

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

// Act & Assert
Assert.Throws(exceptionType, () => vpr.ConvertTo(destinationType));
var ex = Assert.Throws(exceptionType, () => vpr.ConvertTo(destinationType));
if (innerExceptionType != null)
{
Assert.Equal(innerExceptionType, ex.InnerException.GetType());
}
}

[Fact]
Expand Down Expand Up @@ -355,11 +360,10 @@ public void ConvertToUsesProvidedCulture()

// Act
var cultureResult = (decimal)vpr.ConvertTo(typeof(decimal), frCulture);
var result = (decimal)vpr.ConvertTo(typeof(decimal));

// Assert
Assert.Equal(12.5M, cultureResult);
Assert.Equal(125, result);
Assert.Throws(typeof(InvalidOperationException), () => (decimal)vpr.ConvertTo(typeof(decimal)));
}

[Fact]
Expand Down Expand Up @@ -387,14 +391,17 @@ public static IEnumerable<object[]> IntrinsicConversionData
{
get
{
yield return new object[] { 42, 42M };
yield return new object[] { 42, 42L };
yield return new object[] { 42, (short)42 };
yield return new object[] { 42, (float)42.0 };
yield return new object[] { 42, (double)42.0 };
yield return new object[] { 42M, 42 };
yield return new object[] { 42L, 42 };
yield return new object[] { 42, (byte)42 };
yield return new object[] { (short)42, 42 };
yield return new object[] { (float)42.0, 42 };
yield return new object[] { (double)42.0, 42 };
yield return new object[] { (byte)42, 42 };
yield return new object[] { "2008-01-01", new DateTime(2008, 01, 01) };
yield return new object[] { "00:00:20", TimeSpan.FromSeconds(20) };
yield return new object[]
Expand Down

0 comments on commit 6b1bba8

Please sign in to comment.