Use `ModelMetadata.GetDisplayName()` in error message replacing `FormatException` and `OverflowException`

- #3227
- much of change is to tests, creating and passing `ModelMetadata`
- updated `InputFormatterContext` to make `ModelMetadata` available to `JsonInputFormatter`
  - walk `ModelMetadata` tree to get information about property with an issue
- add missing `null` checks in `ModelStateDictionaryExtensions`
This commit is contained in:
Doug Bunting 2015-10-22 09:16:19 -07:00
parent 9b9d72f3f7
commit 40b7636b72
22 changed files with 376 additions and 86 deletions

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
HttpContext httpContext,
string modelName,
ModelStateDictionary modelState,
Type modelType)
ModelMetadata metadata)
{
if (httpContext == null)
{
@ -46,15 +46,16 @@ namespace Microsoft.AspNet.Mvc.Formatters
throw new ArgumentNullException(nameof(modelState));
}
if (modelType == null)
if (metadata == null)
{
throw new ArgumentNullException(nameof(modelType));
throw new ArgumentNullException(nameof(metadata));
}
HttpContext = httpContext;
ModelName = modelName;
ModelState = modelState;
ModelType = modelType;
Metadata = metadata;
ModelType = metadata.ModelType;
}
/// <summary>
@ -73,7 +74,12 @@ namespace Microsoft.AspNet.Mvc.Formatters
public ModelStateDictionary ModelState { get; }
/// <summary>
/// Gets the expected <see cref="Type"/> of the model represented by the request body.
/// Gets the requested <see cref="ModelMetadata"/> of the request body deserialization.
/// </summary>
public ModelMetadata Metadata { get; }
/// <summary>
/// Gets the requested <see cref="Type"/> of the request body deserialization.
/// </summary>
public Type ModelType { get; }
}

View File

@ -69,7 +69,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <remarks>
/// <para>
/// <see cref="ModelStateDictionary"/> tracks the number of model errors added by calls to
/// <see cref="AddModelError(string, Exception)"/> or <see cref="TryAddModelError(string, Exception)"/>.
/// <see cref="AddModelError(string, Exception, ModelMetadata)"/> or
/// <see cref="TryAddModelError(string, Exception, ModelMetadata)"/>.
/// Once the value of <code>MaxAllowedErrors - 1</code> is reached, if another attempt is made to add an error,
/// the error message will be ignored and a <see cref="TooManyModelErrorsException"/> will be added.
/// </para>
@ -202,7 +203,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
/// <param name="key">The key of the <see cref="ModelStateEntry"/> to add errors to.</param>
/// <param name="exception">The <see cref="Exception"/> to add.</param>
public void AddModelError(string key, Exception exception)
public void AddModelError(string key, Exception exception, ModelMetadata metadata)
{
if (key == null)
{
@ -214,7 +215,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
throw new ArgumentNullException(nameof(exception));
}
TryAddModelError(key, exception);
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
TryAddModelError(key, exception, metadata);
}
/// <summary>
@ -228,7 +234,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <c>True</c> if the given error was added, <c>false</c> if the error was ignored.
/// See <see cref="MaxAllowedErrors"/>.
/// </returns>
public bool TryAddModelError(string key, Exception exception)
public bool TryAddModelError(string key, Exception exception, ModelMetadata metadata)
{
if (key == null)
{
@ -240,6 +246,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
throw new ArgumentNullException(nameof(exception));
}
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
if (ErrorCount >= MaxAllowedErrors - 1)
{
EnsureMaxErrorsReachedRecorded();
@ -252,16 +263,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelStateEntry entry;
TryGetValue(key, out entry);
var name = metadata.GetDisplayName();
string errorMessage;
if (entry == null)
{
errorMessage = Resources.FormatModelError_InvalidValue_GenericMessage(key);
errorMessage = Resources.FormatModelError_InvalidValue_GenericMessage(name);
}
else
{
errorMessage = Resources.FormatModelError_InvalidValue_MessageWithModelValue(
entry.AttemptedValue,
key);
name);
}
return TryAddModelError(key, errorMessage);

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
httpContext,
modelBindingKey,
bindingContext.ModelState,
bindingContext.ModelType);
bindingContext.ModelMetadata);
var formatters = bindingContext.OperationBindingContext.InputFormatters;
var formatter = formatters.FirstOrDefault(f => f.CanRead(formatterContext));
@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
catch (Exception ex)
{
bindingContext.ModelState.AddModelError(modelBindingKey, ex);
bindingContext.ModelState.AddModelError(modelBindingKey, ex, bindingContext.ModelMetadata);
// This model binder is the only handler for the Body binding source and it cannot run twice. Always
// tell the model binding system to skip other model binders and never to fall back i.e. indicate a

View File

@ -50,9 +50,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var model = Convert.FromBase64String(value);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
catch (Exception ex)
catch (Exception exception)
{
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
exception,
bindingContext.ModelMetadata);
}
// Matched the type (byte[]) only this binder supports. As in missing data cases, always tell the model

View File

@ -592,7 +592,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var validationState = modelState.GetFieldValidationState(modelStateKey);
if (validationState == ModelValidationState.Unvalidated)
{
modelState.AddModelError(modelStateKey, exception);
modelState.AddModelError(modelStateKey, exception, bindingContext.ModelMetadata);
}
}

View File

@ -60,9 +60,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
}
catch (Exception ex)
catch (Exception exception)
{
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, ex);
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
exception,
bindingContext.ModelMetadata);
// Were able to find a converter for the type but conversion failed.
// Tell the model binding system to skip other model binders.

View File

@ -6,7 +6,7 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.Net.Http.Headers;
using Microsoft.AspNet.Mvc.ModelBinding;
using Newtonsoft.Json;
namespace Microsoft.AspNet.Mvc.Formatters
@ -101,7 +101,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
}
context.ModelState.TryAddModelError(key, eventArgs.ErrorContext.Error);
var metadata = GetPathMetadata(context.Metadata, eventArgs.ErrorContext.Path);
context.ModelState.TryAddModelError(key, eventArgs.ErrorContext.Error, metadata);
// Error must always be marked as handled
// Failure to do so can cause the exception to be rethrown at every recursive level and
@ -171,5 +172,51 @@ namespace Microsoft.AspNet.Mvc.Formatters
{
return JsonSerializer.Create(SerializerSettings);
}
private ModelMetadata GetPathMetadata(ModelMetadata metadata, string path)
{
var index = 0;
while (index >= 0 && index < path.Length)
{
if (path[index] == '[')
{
// At start of "[0]".
if (metadata.ElementMetadata == null)
{
// Odd case but don't throw just because ErrorContext had an odd-looking path.
break;
}
metadata = metadata.ElementMetadata;
index = path.IndexOf(']', index);
}
else if (path[index] == '.' || path[index] == ']')
{
// Skip '.' in "prefix.property" or "[0].property" or ']' in "[0]".
index++;
}
else
{
// At start of "property", "property." or "property[0]".
var endIndex = path.IndexOfAny(new[] { '.', '[' }, index);
if (endIndex == -1)
{
endIndex = path.Length;
}
var propertyName = path.Substring(index, endIndex - index);
if (metadata.Properties[propertyName] == null)
{
// Odd case but don't throw just because ErrorContext had an odd-looking path.
break;
}
metadata = metadata.Properties[propertyName];
index = endIndex;
}
}
return metadata;
}
}
}

View File

@ -26,6 +26,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Expression<Func<TModel, object>> expression,
string errorMessage)
{
if (modelState == null)
{
throw new ArgumentNullException(nameof(modelState));
}
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
if (errorMessage == null)
{
throw new ArgumentNullException(nameof(errorMessage));
}
modelState.AddModelError(GetExpressionText(expression), errorMessage);
}
@ -40,9 +55,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public static void AddModelError<TModel>(
this ModelStateDictionary modelState,
Expression<Func<TModel, object>> expression,
Exception exception)
Exception exception,
ModelMetadata metadata)
{
modelState.AddModelError(GetExpressionText(expression), exception);
if (modelState == null)
{
throw new ArgumentNullException(nameof(modelState));
}
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
modelState.AddModelError(GetExpressionText(expression), exception, metadata);
}
/// <summary>
@ -59,6 +90,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
this ModelStateDictionary modelState,
Expression<Func<TModel, object>> expression)
{
if (modelState == null)
{
throw new ArgumentNullException(nameof(modelState));
}
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
return modelState.Remove(GetExpressionText(expression));
}
@ -73,6 +114,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
this ModelStateDictionary modelState,
Expression<Func<TModel, object>> expression)
{
if (modelState == null)
{
throw new ArgumentNullException(nameof(modelState));
}
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
string modelKey = GetExpressionText(expression);
if (string.IsNullOrEmpty(modelKey))
{

View File

@ -179,10 +179,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Arrange
var dictionary = new ModelStateDictionary();
dictionary.AddModelError("some key", "some error");
var ex = new Exception();
var exception = new Exception();
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
// Act
dictionary.AddModelError("some key", ex);
dictionary.AddModelError("some key", exception, metadata);
// Assert
Assert.Equal(2, dictionary.ErrorCount);
@ -191,7 +193,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(2, kvp.Value.Errors.Count);
Assert.Equal("some error", kvp.Value.Errors[0].ErrorMessage);
Assert.Same(ex, kvp.Value.Errors[1].Exception);
Assert.Same(exception, kvp.Value.Errors[1].Exception);
}
[Fact]
@ -546,9 +548,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
MaxAllowedErrors = 5
};
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
dictionary.AddModelError("key1", "error1");
dictionary.AddModelError("key2", new Exception());
dictionary.AddModelError("key3", new Exception());
dictionary.AddModelError("key2", new Exception(), metadata);
dictionary.AddModelError("key3", new Exception(), metadata);
dictionary.AddModelError("key4", "error4");
dictionary.AddModelError("key5", "error5");
@ -572,6 +576,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
MaxAllowedErrors = 3
};
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
// Act and Assert
Assert.False(dictionary.HasReachedMaxErrors);
@ -579,7 +585,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.True(result);
Assert.False(dictionary.HasReachedMaxErrors);
result = dictionary.TryAddModelError("key2", new Exception());
result = dictionary.TryAddModelError("key2", new Exception(), metadata);
Assert.True(result);
Assert.False(dictionary.HasReachedMaxErrors); // Still room for TooManyModelErrorsException.
@ -614,10 +620,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
MaxAllowedErrors = 4
};
dictionary.AddModelError("key1", new Exception());
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
dictionary.AddModelError("key1", new Exception(), metadata);
dictionary.AddModelError("key2", "error2");
dictionary.AddModelError("key3", "error3");
dictionary.AddModelError("key3", new Exception());
dictionary.AddModelError("key3", new Exception(), metadata);
// Act and Assert
Assert.True(dictionary.HasReachedMaxErrors);
@ -642,15 +650,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
MaxAllowedErrors = 3
};
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
// Act and Assert
var result = dictionary.TryAddModelError("key1", "error1");
Assert.True(result);
result = dictionary.TryAddModelError("key2", new Exception());
result = dictionary.TryAddModelError("key2", new Exception(), metadata);
Assert.True(result);
result = dictionary.TryAddModelError("key3", new Exception());
result = dictionary.TryAddModelError("key3", new Exception(), metadata);
Assert.False(result);
Assert.Equal(3, dictionary.Count);
@ -668,10 +678,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
MaxAllowedErrors = 3
};
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
// Act
dictionary.AddModelError("key1", "error1");
dictionary.TryAddModelError("key3", new Exception());
dictionary.TryAddModelError("key3", new Exception(), metadata);
var copy = new ModelStateDictionary(dictionary);
copy.AddModelError("key2", "error2");
@ -711,11 +723,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ModelStateDictionary_ReturnGenericErrorMessage_WhenModelStateNotSet()
{
// Arrange
var expected = "The supplied value is invalid for key.";
var expected = "The supplied value is invalid for Length.";
var dictionary = new ModelStateDictionary();
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
// Act
dictionary.TryAddModelError("key", new FormatException());
dictionary.TryAddModelError("key", new FormatException(), metadata);
// Assert
var error = Assert.Single(dictionary["key"].Errors);
@ -726,12 +740,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ModelStateDictionary_ReturnSpecificErrorMessage_WhenModelStateSet()
{
// Arrange
var expected = "The value 'some value' is not valid for key.";
var expected = "The value 'some value' is not valid for Length.";
var dictionary = new ModelStateDictionary();
dictionary.SetModelValue("key", new string[] { "some value" }, "some value");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
// Act
dictionary.TryAddModelError("key", new FormatException());
dictionary.TryAddModelError("key", new FormatException(), metadata);
// Assert
var error = Assert.Single(dictionary["key"].Errors);
@ -744,9 +760,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Arrange
var dictionary = new ModelStateDictionary();
dictionary.SetModelValue("key", new string[] { "some value" }, "some value");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
// Act
dictionary.TryAddModelError("key", new InvalidOperationException());
dictionary.TryAddModelError("key", new InvalidOperationException(), metadata);
// Assert
var error = Assert.Single(dictionary["key"].Errors);

View File

@ -36,11 +36,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
var formatter = new CatchAllFormatter();
var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = requestContentType;
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(void));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(void));
metadata: metadata);
// Act
var result = formatter.CanRead(context);
@ -66,11 +69,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
var formatter = new MultipartFormatter();
var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = requestContentType;
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(void));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(void));
metadata: metadata);
// Act
var result = formatter.CanRead(context);
@ -93,11 +99,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
var formatter = new MultipartFormatter();
var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = requestContentType;
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(void));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(void));
metadata: metadata);
// Act
var result = formatter.CanRead(context);
@ -123,11 +132,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
var formatter = new MultipartMixedFormatter();
var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = requestContentType;
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(void));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(void));
metadata: metadata);
// Act
var result = formatter.CanRead(context);
@ -150,11 +162,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
var formatter = new MultipartMixedFormatter();
var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = requestContentType;
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(void));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(void));
metadata: metadata);
// Act
var result = formatter.CanRead(context);
@ -183,11 +198,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
var formatter = new MathMLFormatter();
var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = requestContentType;
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(void));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(void));
metadata: metadata);
// Act
var result = formatter.CanRead(context);
@ -209,11 +227,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
var formatter = new MathMLFormatter();
var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = requestContentType;
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(void));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(void));
metadata: metadata);
// Act
var result = formatter.CanRead(context);
@ -240,11 +261,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
var formatter = new XmlFormatter();
var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = requestContentType;
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(void));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(void));
metadata: metadata);
// Act
var result = formatter.CanRead(context);
@ -268,11 +292,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
var formatter = new XmlFormatter();
var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = requestContentType;
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(void));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(void));
metadata: metadata);
// Act
var result = formatter.CanRead(context);

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public async Task BindModelAddsModelErrorsOnInvalidCharacters()
{
// Arrange
var expected = "The value '\"Fys1\"' is not valid for foo.";
var expected = "The value '\"Fys1\"' is not valid for Byte[].";
var valueProvider = new SimpleValueProvider()
{

View File

@ -113,7 +113,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public async Task BindModel_Error_FormatExceptionsTurnedIntoStringsInModelState()
{
// Arrange
var message = "The value 'not an integer' is not valid for theModelName.";
var message = "The value 'not an integer' is not valid for Int32.";
var bindingContext = GetBindingContext(typeof(int));
bindingContext.ValueProvider = new SimpleValueProvider
{

View File

@ -38,11 +38,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var contentBytes = Encoding.UTF8.GetBytes("content");
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(string));
var formatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(string));
metadata: metadata);
// Act
var result = formatter.CanRead(formatterContext);
@ -84,11 +86,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(type);
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: type);
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);
@ -107,11 +111,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: typeof(User));
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);
@ -133,11 +139,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(User));
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);
@ -149,6 +157,61 @@ namespace Microsoft.AspNet.Mvc.Formatters
modelState["Age"].Errors[0].Exception.Message);
}
[Fact]
public async Task ReadAsync_InvalidArray_AddsOverflowErrorsToModelState()
{
// Arrange
var content = "[0, 23, 300]";
var formatter = new JsonInputFormatter();
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(byte[]));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);
// Assert
Assert.True(result.HasError);
Assert.Equal("The supplied value is invalid for Byte.", modelState["[2]"].Errors[0].ErrorMessage);
Assert.Null(modelState["[2]"].Errors[0].Exception);
}
[Fact]
public async Task ReadAsync_InvalidComplexArray_AddsOverflowErrorsToModelState()
{
// Arrange
var content = "[{name: 'Name One', Age: 30}, {name: 'Name Two', Small: 300}]";
var formatter = new JsonInputFormatter();
var contentBytes = Encoding.UTF8.GetBytes(content);
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User[]));
var context = new InputFormatterContext(
httpContext,
modelName: "names",
modelState: modelState,
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);
// Assert
Assert.True(result.HasError);
Assert.Equal(
"Error converting value 300 to type 'System.Byte'. Path '[1].Small', line 1, position 59.",
modelState["names[1].Small"].Errors[0].Exception.Message);
}
[Fact]
public async Task ReadAsync_UsesTryAddModelValidationErrorsToModelState()
{
@ -159,11 +222,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(User));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(User));
metadata: metadata);
modelState.MaxAllowedErrors = 3;
modelState.AddModelError("key1", "error1");
@ -215,11 +280,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(UserLogin));
var inputFormatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(UserLogin));
metadata: metadata);
// Act
var result = await jsonFormatter.ReadAsync(inputFormatterContext);
@ -248,11 +315,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, "application/json;charset=utf-8");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(UserLogin));
var inputFormatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(UserLogin));
metadata: metadata);
// Act
var result = await jsonFormatter.ReadAsync(inputFormatterContext);
@ -315,6 +384,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
public string Name { get; set; }
public decimal Age { get; set; }
public byte Small { get; set; }
}
private sealed class UserLogin

View File

@ -25,11 +25,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(JsonPatchDocument<Customer>));
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);
@ -53,11 +55,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(JsonPatchDocument<Customer>));
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);
@ -86,11 +90,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(JsonPatchDocument<Customer>));
var formatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(JsonPatchDocument<Customer>));
metadata: metadata);
// Act
var result = formatter.CanRead(formatterContext);
@ -111,11 +117,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: "application/json-patch+json");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(modelType);
var formatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: modelType);
metadata: metadata);
// Act
var result = formatter.CanRead(formatterContext);
@ -137,11 +145,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: "application/json-patch+json");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(Customer));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(Customer));
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);

View File

@ -73,11 +73,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(string));
var formatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(string));
metadata: metadata);
// Act
var result = formatter.CanRead(formatterContext);
@ -337,11 +339,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(inputBytes, contentType: "application/xml; charset=utf-16");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(TestLevelOne));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(TestLevelOne));
metadata: metadata);
// Act
var ex = await Assert.ThrowsAsync(expectedException, () => formatter.ReadAsync(context));
@ -401,11 +405,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: "application/xml; charset=utf-16");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(TestLevelOne));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(TestLevelOne));
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);
@ -544,11 +550,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType)
{
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(modelType);
return new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: modelType);
metadata: metadata);
}
private static HttpContext GetHttpContext(

View File

@ -59,11 +59,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: requestContentType);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(string));
var formatterContext = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(string));
metadata: metadata);
// Act
var result = formatter.CanRead(formatterContext);
@ -342,11 +344,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(inputBytes, contentType: "application/xml; charset=utf-16");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(TestLevelOne));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(TestLevelOne));
metadata: metadata);
// Act and Assert
var ex = await Assert.ThrowsAsync(expectedException, () => formatter.ReadAsync(context));
@ -403,11 +407,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var modelState = new ModelStateDictionary();
var httpContext = GetHttpContext(contentBytes, contentType: "application/xml; charset=utf-16");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(typeof(TestLevelOne));
var context = new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: modelState,
modelType: typeof(TestLevelOne));
metadata: metadata);
// Act
var result = await formatter.ReadAsync(context);
@ -425,11 +431,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType)
{
var httpContext = GetHttpContext(contentBytes);
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForType(modelType);
return new InputFormatterContext(
httpContext,
modelName: string.Empty,
modelState: new ModelStateDictionary(),
modelType: modelType);
metadata: metadata);
}
private static HttpContext GetHttpContext(

View File

@ -1443,7 +1443,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// 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);
Assert.Equal("The value 'random string' is not valid for DateTime.", result);
}
[Fact]

View File

@ -244,7 +244,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var error = Assert.Single(entry.Errors);
Assert.Null(error.Exception);
Assert.Equal("The value 'abcd' is not valid for Parameter1.", error.ErrorMessage);
Assert.Equal("The value 'abcd' is not valid for Int32.", error.ErrorMessage);
}
[Theory]

View File

@ -81,11 +81,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void AddModelError_ForSingleExpression_AddsExpectedException()
{
// Arrange
var exception = new Exception();
var dictionary = new ModelStateDictionary();
var exception = new Exception();
var provider = new TestModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(TestModel), nameof(TestModel.Text));
// Act
dictionary.AddModelError<TestModel>(model => model.Text, exception);
dictionary.AddModelError<TestModel>(model => model.Text, exception, metadata);
// Assert
var modelState = Assert.Single(dictionary);
@ -99,11 +101,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void AddModelError_ForRelationExpression_AddsExpectedException()
{
// Arrange
var exception = new Exception();
var dictionary = new ModelStateDictionary();
var exception = new Exception();
var provider = new TestModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(ChildModel), nameof(ChildModel.Text));
// Act
dictionary.AddModelError<TestModel>(model => model.Child.Text, exception);
dictionary.AddModelError<TestModel>(model => model.Child.Text, exception, metadata);
// Assert
var modelState = Assert.Single(dictionary);
@ -117,11 +121,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void AddModelError_ForImplicitlyCastedToObjectExpression_AddsExpectedException()
{
// Arrange
var exception = new Exception();
var dictionary = new ModelStateDictionary();
var exception = new Exception();
var provider = new TestModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(ChildModel), nameof(ChildModel.Value));
// Act
dictionary.AddModelError<TestModel>(model => model.Child.Value, exception);
dictionary.AddModelError<TestModel>(model => model.Child.Value, exception, metadata);
// Assert
var modelState = Assert.Single(dictionary);
@ -135,12 +141,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void AddModelError_ForNotModelsExpression_AddsExpectedException()
{
// Arrange
var dictionary = new ModelStateDictionary();
var variable = "Test";
var exception = new Exception();
var dictionary = new ModelStateDictionary();
var provider = new TestModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
// Act
dictionary.AddModelError<TestModel>(model => variable, exception);
dictionary.AddModelError<TestModel>(model => variable, exception, metadata);
// Assert
var modelState = Assert.Single(dictionary);

View File

@ -343,23 +343,29 @@ namespace Microsoft.AspNet.Mvc.Rendering
modelState.AddModelError("Property3.OrderedProperty3", "This is an error for Property3.OrderedProperty3.");
modelState.AddModelError("Property3.OrderedProperty2", "This is an error for Property3.OrderedProperty2.");
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(ValidationModel), nameof(ValidationModel.Property3));
modelState.AddModelError("Property3", "This is an error for Property3.");
modelState.AddModelError("Property3", new InvalidCastException("Exception will be ignored."));
modelState.AddModelError("Property3", new InvalidCastException("Exception will be ignored."), metadata);
metadata = provider.GetMetadataForProperty(typeof(ValidationModel), nameof(ValidationModel.Property2));
modelState.AddModelError("Property2", "This is an error for Property2.");
modelState.AddModelError("Property2", "This is another error for Property2.");
modelState.AddModelError("Property2", new OverflowException("Produces invalid value message"));
modelState.AddModelError("Property2", new OverflowException("Produces invalid value message"), metadata);
metadata = provider.GetMetadataForType(typeof(ValidationModel));
modelState.AddModelError(string.Empty, "This is an error for the model root.");
modelState.AddModelError(string.Empty, "This is another error for the model root.");
modelState.AddModelError(string.Empty, new InvalidOperationException("Another ignored Exception."));
modelState.AddModelError(string.Empty, new InvalidOperationException("Another ignored Exception."), metadata);
}
// Adds one or more errors for all properties in OrderedModel. But adds errors out of order.
private void AddOrderedErrors(ModelStateDictionary modelState)
{
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(OrderedModel), nameof(OrderedModel.Property3));
modelState.AddModelError("Property3", "This is an error for Property3.");
modelState.AddModelError("Property3", new InvalidCastException("An ignored Exception."));
modelState.AddModelError("Property3", new InvalidCastException("An ignored Exception."), metadata);
modelState.AddModelError("Property2", "This is an error for Property2.");
modelState.AddModelError("Property2", "This is another error for Property2.");

View File

@ -66,13 +66,19 @@ namespace System.Web.Http.Dispatcher
[Fact]
public void ModelStateConstructorWithDetail_AddsCorrectDictionaryItems()
{
// Arrange
ModelStateDictionary modelState = new ModelStateDictionary();
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
modelState.AddModelError("[0].Name", "error1");
modelState.AddModelError("[0].Name", "error2");
modelState.AddModelError("[0].Address", "error");
modelState.AddModelError("[2].Name", new Exception("OH NO"));
modelState.AddModelError("[2].Name", new Exception("OH NO"), metadata);
// Act
HttpError error = new HttpError(modelState, true);
// Assert
HttpError modelStateError = error["ModelState"] as HttpError;
Assert.Contains(new KeyValuePair<string, object>("Message", "The request is invalid."), error);
@ -98,13 +104,19 @@ namespace System.Web.Http.Dispatcher
[Fact]
public void ModelStateConstructorWithoutDetail_AddsCorrectDictionaryItems()
{
// Arrange
ModelStateDictionary modelState = new ModelStateDictionary();
var provider = new EmptyModelMetadataProvider();
var metadata = provider.GetMetadataForProperty(typeof(string), nameof(string.Length));
modelState.AddModelError("[0].Name", "error1");
modelState.AddModelError("[0].Name", "error2");
modelState.AddModelError("[0].Address", "error");
modelState.AddModelError("[2].Name", new Exception("OH NO"));
modelState.AddModelError("[2].Name", new Exception("OH NO"), metadata);
// Act
HttpError error = new HttpError(modelState, false);
// Assert
HttpError modelStateError = error["ModelState"] as HttpError;
Assert.Contains(new KeyValuePair<string, object>("Message", "The request is invalid."), error);

View File

@ -24,7 +24,7 @@ namespace XmlFormattersWebSite.Controllers
}
ModelState.AddModelError("key1", "key1-error");
ModelState.AddModelError("key2", exception);
ModelState.AddModelError("key2", exception, ViewData.ModelMetadata);
return new ObjectResult(new SerializableError(ModelState));
}