diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ValueProviderResult.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ValueProviderResult.cs index a0d2ddbf04..ff20f444b6 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ValueProviderResult.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ValueProviderResult.cs @@ -3,6 +3,7 @@ using System; using System.Collections; +using System.ComponentModel; using System.Globalization; using System.Reflection; @@ -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; } @@ -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) @@ -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); @@ -144,121 +145,58 @@ private object UnwrapPossibleArrayType(CultureInfo culture, object value, Type d return ConvertSimpleType(culture, value, destinationType); } - private static Func GetConverterDelegate(Type destinationType) + private static Func 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); - } - } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/project.json b/src/Microsoft.AspNet.Mvc.ModelBinding/project.json index f2f1271d2a..664d517169 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/project.json +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/project.json @@ -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-*", diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/TypeConverterModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/TypeConverterModelBinderTest.cs index 4ed7d233c7..7c16625922 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/TypeConverterModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/TypeConverterModelBinderTest.cs @@ -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))] @@ -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 { @@ -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); } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ValueProviderResultTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ValueProviderResultTest.cs index 649adb7919..6f09016d8b 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ValueProviderResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ValueProviderResultTest.cs @@ -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 @@ -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] @@ -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] @@ -387,14 +391,17 @@ public static IEnumerable 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[]