Added custom exception message for Format Exception

This commit is contained in:
Ajay Bhargav Baaskaran 2015-01-28 14:05:32 -08:00
parent 59db8143da
commit 5e704cd5ef
14 changed files with 157 additions and 60 deletions

View File

@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
catch (Exception ex)
{
ModelBindingHelper.AddModelErrorBasedOnExceptionType(bindingContext, ex);
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
}
return true;

View File

@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
catch (Exception ex)
{
ModelBindingHelper.AddModelErrorBasedOnExceptionType(bindingContext, ex);
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
}
return true;

View File

@ -96,30 +96,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Internal
}
}
internal static void AddModelErrorBasedOnExceptionType(ModelBindingContext bindingContext, Exception ex)
{
if (IsFormatException(ex))
{
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex.Message);
}
else
{
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
}
}
internal static bool IsFormatException(Exception ex)
{
for (; ex != null; ex = ex.InnerException)
{
if (ex is FormatException)
{
return true;
}
}
return false;
}
public static object ConvertValuesToCollectionType<T>(Type modelType, IList<T> values)
{
// There's a limited set of collection types we can support here.

View File

@ -202,6 +202,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return false;
}
if (exception is FormatException)
{
// Convert FormatExceptions to Invalid value messages.
ModelState modelState;
TryGetValue(key, out modelState);
string errorMessage;
if (modelState == null)
{
errorMessage = Resources.FormatModelBinderUtil_ValueInvalidGeneric(key);
}
else
{
errorMessage = Resources.FormatModelBinderUtil_ValueInvalid(
modelState.Value.AttemptedValue,
key);
}
return TryAddModelError(key, errorMessage);
}
ErrorCount++;
AddModelErrorCore(key, exception);
return true;

View File

@ -362,22 +362,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return string.Format(CultureInfo.CurrentCulture, GetString("ValueProviderResult_CannotConvertEnum"), p0, p1);
}
/// <summary>
/// The parameter conversion from type '{0}' to type '{1}' failed. See the inner exception for more information.
/// </summary>
internal static string ValueProviderResult_ConversionThrew
{
get { return GetString("ValueProviderResult_ConversionThrew"); }
}
/// <summary>
/// The parameter conversion from type '{0}' to type '{1}' failed. See the inner exception for more information.
/// </summary>
internal static string FormatValueProviderResult_ConversionThrew(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ValueProviderResult_ConversionThrew"), p0, p1);
}
/// <summary>
/// The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types.
/// </summary>
@ -458,6 +442,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return string.Format(CultureInfo.CurrentCulture, GetString("BinderType_MustBeIModelBinderOrIModelBinderProvider"), p0, p1, p2);
}
/// <summary>
/// The value '{0}' is not valid for {1}.
/// </summary>
internal static string ModelBinderUtil_ValueInvalid
{
get { return GetString("ModelBinderUtil_ValueInvalid"); }
}
/// <summary>
/// The value '{0}' is not valid for {1}.
/// </summary>
internal static string FormatModelBinderUtil_ValueInvalid(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ModelBinderUtil_ValueInvalid"), p0, p1);
}
/// <summary>
/// The supplied value is invalid for {0}.
/// </summary>
internal static string ModelBinderUtil_ValueInvalidGeneric
{
get { return GetString("ModelBinderUtil_ValueInvalidGeneric"); }
}
/// <summary>
/// The supplied value is invalid for {0}.
/// </summary>
internal static string FormatModelBinderUtil_ValueInvalidGeneric(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ModelBinderUtil_ValueInvalidGeneric"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -183,9 +183,6 @@
<data name="ValueProviderResult_CannotConvertEnum" xml:space="preserve">
<value>Cannot convert value '{0}' to enum type '{1}'.</value>
</data>
<data name="ValueProviderResult_ConversionThrew" xml:space="preserve">
<value>The parameter conversion from type '{0}' to type '{1}' failed. See the inner exception for more information.</value>
</data>
<data name="ValueProviderResult_NoConverterExists" xml:space="preserve">
<value>The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types.</value>
</data>
@ -201,4 +198,10 @@
<data name="BinderType_MustBeIModelBinderOrIModelBinderProvider" xml:space="preserve">
<value>The type '{0}' must implement either '{1}' or '{2}' to be used as a model binder.</value>
</data>
<data name="ModelBinderUtil_ValueInvalid" xml:space="preserve">
<value>The value '{0}' is not valid for {1}.</value>
</data>
<data name="ModelBinderUtil_ValueInvalidGeneric" xml:space="preserve">
<value>The supplied value is invalid for {0}.</value>
</data>
</root>

View File

@ -6,6 +6,7 @@ using System.Collections;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Runtime.ExceptionServices;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -162,8 +163,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
catch (Exception ex)
{
throw new InvalidOperationException(
Resources.FormatValueProviderResult_ConversionThrew(value.GetType(), destinationType), ex);
if (ex is FormatException)
{
throw ex;
}
else
{
// TypeConverter throws System.Exception wrapping the FormatException,
// so we throw the inner exception.
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
// this code is never reached because the previous line is throwing;
throw;
}
}
}

View File

@ -19,7 +19,7 @@
<hr />
<div class="text-danger validation-summary-errors" data-valmsg-summary="true">
<ul><li>The field Age must be between 10 and 100.</li>
<li>The parameter conversion from type &#39;System.String&#39; to type &#39;System.Int32&#39; failed. See the inner exception for more information.</li>
<li>The value &#39;z&#39; is not valid for Salary.</li>
<li>The JoinDate field is required.</li>
</ul></div>
<div class="form-group">
@ -63,7 +63,7 @@
<label class="control-label col-md-2" for="Salary">Salary</label>
<div class="col-md-10">
<input class="form-control input-validation-error" type="number" id="Salary" name="Salary" value="z" />
<span class="field-validation-error" data-valmsg-for="Salary" data-valmsg-replace="true">The parameter conversion from type &#39;System.String&#39; to type &#39;System.Int32&#39; failed. See the inner exception for more information.</span>
<span class="field-validation-error" data-valmsg-for="Salary" data-valmsg-replace="true">The value &#39;z&#39; is not valid for Salary.</span>
</div>
</div>

View File

@ -1529,5 +1529,27 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var body = await response.Content.ReadAsStringAsync();
Assert.Equal(expectedContent, body);
}
public async Task ModelBinder_FormatsDontMatch_ThrowsUserFriendlyException()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
var url = "http://localhost/Home/GetErrorMessage";
var nameValueCollection = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string,string>("birthdate", "random string"),
};
var formData = new FormUrlEncodedContent(nameValueCollection);
// Act
var response = await client.PostAsync(url, formData);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadAsStringAsync();
Assert.Equal("The value 'random string' is not valid for birthdate.", result);
}
}
}

View File

@ -61,8 +61,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Arrange
var expected = TestPlatformHelper.IsMono ?
"Invalid length." :
"The input is not a valid Base-64 string as it contains a non-base 64 character," +
" more than two padding characters, or an illegal character among the padding characters. ";
"The supplied value is invalid for foo.";
var valueProvider = new SimpleHttpValueProvider()
{

View File

@ -71,8 +71,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public async Task BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState()
{
// Arrange
var message = "The parameter conversion from type 'System.String' to type 'System.Int32' failed." +
" See the inner exception for more information.";
var message = "The value 'not an integer' is not valid for theModelName.";
var bindingContext = GetBindingContext(typeof(int));
bindingContext.ValueProvider = new SimpleHttpValueProvider
{

View File

@ -538,6 +538,51 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(expected, canAdd);
}
[Fact]
public void ModelStateDictionary_ReturnGenericErrorMessage_WhenModelStateNotSet()
{
// Arrange
var expected = "The supplied value is invalid for key.";
var dictionary = new ModelStateDictionary();
// Act
dictionary.TryAddModelError("key", new FormatException());
// Assert
var error = Assert.Single(dictionary["key"].Errors);
Assert.Equal(expected, error.ErrorMessage);
}
[Fact]
public void ModelStateDictionary_ReturnSpecificErrorMessage_WhenModelStateSet()
{
// Arrange
var expected = "The value 'some value' is not valid for key.";
var dictionary = new ModelStateDictionary();
dictionary.SetModelValue("key", GetValueProviderResult());
// Act
dictionary.TryAddModelError("key", new FormatException());
// Assert
var error = Assert.Single(dictionary["key"].Errors);
Assert.Equal(expected, error.ErrorMessage);
}
[Fact]
public void ModelStateDictionary_NoErrorMessage_ForNonFormatException()
{
// Arrange
var dictionary = new ModelStateDictionary();
dictionary.SetModelValue("key", GetValueProviderResult());
// Act
dictionary.TryAddModelError("key", new InvalidOperationException());
// Assert
var error = Assert.Single(dictionary["key"].Errors);
Assert.Empty(error.ErrorMessage);
}
private static ValueProviderResult GetValueProviderResult(object rawValue = null, string attemptedValue = null)
{

View File

@ -339,17 +339,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
[Theory]
[InlineData(typeof(int), typeof(InvalidOperationException), typeof(Exception))]
[InlineData(typeof(double?), typeof(InvalidOperationException), typeof(Exception))]
[InlineData(typeof(MyEnum?), typeof(InvalidOperationException), typeof(FormatException))]
public void ConvertToThrowsIfConverterThrows(Type destinationType, Type exceptionType, Type innerExceptionType)
[InlineData(typeof(int))]
[InlineData(typeof(double?))]
[InlineData(typeof(MyEnum?))]
public void ConvertToThrowsIfConverterThrows(Type destinationType)
{
// Arrange
var vpr = new ValueProviderResult("this-is-not-a-valid-value", null, CultureInfo.InvariantCulture);
// Act & Assert
var ex = Assert.Throws(exceptionType, () => vpr.ConvertTo(destinationType));
Assert.IsType(innerExceptionType, ex.InnerException);
var ex = Assert.Throws(typeof(FormatException), () => vpr.ConvertTo(destinationType));
}
[Fact]
@ -380,7 +379,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Assert
Assert.Equal(12.5M, cultureResult);
Assert.Throws<InvalidOperationException>(() => vpr.ConvertTo(typeof(decimal)));
Assert.Throws<FormatException>(() => vpr.ConvertTo(typeof(decimal)));
}
[Fact]

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@ -59,6 +60,11 @@ namespace ModelBindingWebSite.Controllers
return customer;
}
public IActionResult GetErrorMessage(DateTime birthdate)
{
return Content(ModelState[nameof(birthdate)].Errors[0].ErrorMessage);
}
private Customer CreateCustomer(int id)
{
return new Customer()