[Fixes #1184] Formatting issues in requests should not cause a 500 response
This commit is contained in:
parent
f737083c94
commit
5bb69e495e
|
|
@ -57,12 +57,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets if deserialization errors are captured. When set, these errors appear in
|
|
||||||
/// the <see cref="ActionContext.ModelState"/> instance of <see cref="InputFormatterContext"/>.
|
|
||||||
/// </summary>
|
|
||||||
public bool CaptureDeserilizationErrors { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
public override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
||||||
{
|
{
|
||||||
|
|
@ -82,19 +76,17 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var jsonSerializer = CreateJsonSerializer();
|
var jsonSerializer = CreateJsonSerializer();
|
||||||
|
|
||||||
EventHandler<Newtonsoft.Json.Serialization.ErrorEventArgs> errorHandler = null;
|
EventHandler<Newtonsoft.Json.Serialization.ErrorEventArgs> errorHandler = null;
|
||||||
if (CaptureDeserilizationErrors)
|
errorHandler = (sender, e) =>
|
||||||
{
|
{
|
||||||
errorHandler = (sender, e) =>
|
var exception = e.ErrorContext.Error;
|
||||||
{
|
context.ActionContext.ModelState.TryAddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
|
||||||
var exception = e.ErrorContext.Error;
|
|
||||||
context.ActionContext.ModelState.TryAddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
|
// Error must always be marked as handled
|
||||||
// Error must always be marked as handled
|
// Failure to do so can cause the exception to be rethrown at every recursive level and
|
||||||
// Failure to do so can cause the exception to be rethrown at every recursive level and
|
// overflow the stack for x64 CLR processes
|
||||||
// overflow the stack for x64 CLR processes
|
e.ErrorContext.Handled = true;
|
||||||
e.ErrorContext.Handled = true;
|
};
|
||||||
};
|
jsonSerializer.Error += errorHandler;
|
||||||
jsonSerializer.Error += errorHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.Core;
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
@ -59,8 +60,29 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return new ModelBindingResult(null, bindingContext.ModelName, isModelSet: false);
|
return new ModelBindingResult(null, bindingContext.ModelName, isModelSet: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var model = await formatter.ReadAsync(formatterContext);
|
object model = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
model = await formatter.ReadAsync(formatterContext);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
model = GetDefaultValueForType(bindingContext.ModelType);
|
||||||
|
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
|
||||||
|
return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
|
||||||
|
}
|
||||||
|
|
||||||
return new ModelBindingResult(model, bindingContext.ModelName, isModelSet: true);
|
return new ModelBindingResult(model, bindingContext.ModelName, isModelSet: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object GetDefaultValueForType(Type modelType)
|
||||||
|
{
|
||||||
|
if (modelType.GetTypeInfo().IsValueType)
|
||||||
|
{
|
||||||
|
return Activator.CreateInstance(modelType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,17 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Core;
|
using Microsoft.AspNet.Http.Core;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -35,6 +39,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
mockInputFormatter.Verify(v => v.ReadAsync(It.IsAny<InputFormatterContext>()), Times.Once);
|
mockInputFormatter.Verify(v => v.ReadAsync(It.IsAny<InputFormatterContext>()), Times.Once);
|
||||||
|
Assert.NotNull(binderResult);
|
||||||
|
Assert.True(binderResult.IsModelSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -116,14 +122,48 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Null(binderResult);
|
Assert.Null(binderResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CustomFormatterDeserializationException_AddedToModelState()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("Bad data!"));
|
||||||
|
httpContext.Request.ContentType = "text/xyz";
|
||||||
|
var bindingContext = GetBindingContext(httpContext, typeof(Person), inputFormatter: new XyzFormatter());
|
||||||
|
bindingContext.ModelMetadata.BinderMetadata = new FromBodyAttribute();
|
||||||
|
|
||||||
|
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
|
||||||
|
// Returns true because it understands the metadata type.
|
||||||
|
Assert.NotNull(binderResult);
|
||||||
|
Assert.False(binderResult.IsModelSet);
|
||||||
|
Assert.Null(binderResult.Model);
|
||||||
|
Assert.True(bindingContext.ModelState.ContainsKey("someName"));
|
||||||
|
var errorMessage = bindingContext.ModelState["someName"].Errors[0].Exception.Message;
|
||||||
|
Assert.Equal("Your input is bad!", errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
private static ModelBindingContext GetBindingContext(Type modelType, IInputFormatter inputFormatter)
|
private static ModelBindingContext GetBindingContext(Type modelType, IInputFormatter inputFormatter)
|
||||||
|
{
|
||||||
|
return GetBindingContext(new DefaultHttpContext(), modelType, inputFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModelBindingContext GetBindingContext(
|
||||||
|
HttpContext httpContext,
|
||||||
|
Type modelType,
|
||||||
|
IInputFormatter inputFormatter)
|
||||||
{
|
{
|
||||||
var metadataProvider = new EmptyModelMetadataProvider();
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
var operationBindingContext = new OperationBindingContext
|
var operationBindingContext = new OperationBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = GetBodyBinder(inputFormatter),
|
ModelBinder = GetBodyBinder(httpContext, inputFormatter),
|
||||||
MetadataProvider = metadataProvider,
|
MetadataProvider = metadataProvider,
|
||||||
HttpContext = new DefaultHttpContext(),
|
HttpContext = httpContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
|
|
@ -140,7 +180,12 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
private static BodyModelBinder GetBodyBinder(IInputFormatter inputFormatter)
|
private static BodyModelBinder GetBodyBinder(IInputFormatter inputFormatter)
|
||||||
{
|
{
|
||||||
var actionContext = CreateActionContext(new DefaultHttpContext());
|
return GetBodyBinder(new DefaultHttpContext(), inputFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BodyModelBinder GetBodyBinder(HttpContext httpContext, IInputFormatter inputFormatter)
|
||||||
|
{
|
||||||
|
var actionContext = CreateActionContext(httpContext);
|
||||||
var inputFormatterSelector = new Mock<IInputFormatterSelector>();
|
var inputFormatterSelector = new Mock<IInputFormatterSelector>();
|
||||||
inputFormatterSelector
|
inputFormatterSelector
|
||||||
.Setup(o => o.SelectFormatter(
|
.Setup(o => o.SelectFormatter(
|
||||||
|
|
@ -194,5 +239,24 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class XyzFormatter : InputFormatter
|
||||||
|
{
|
||||||
|
public XyzFormatter()
|
||||||
|
{
|
||||||
|
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xyz"));
|
||||||
|
SupportedEncodings.Add(Encoding.UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CanReadType(Type type)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<object> ReadRequestBodyAsync(InputFormatterContext context)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Your input is bad!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -112,29 +112,13 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ReadAsync_ThrowsOnDeserializationErrors()
|
public async Task ReadAsync_AddsModelValidationErrorsToModelState()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var content = "{name: 'Person Name', Age: 'not-an-age'}";
|
var content = "{name: 'Person Name', Age: 'not-an-age'}";
|
||||||
var formatter = new JsonInputFormatter();
|
var formatter = new JsonInputFormatter();
|
||||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
var httpContext = GetActionContext(contentBytes);
|
|
||||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(User));
|
|
||||||
var context = new InputFormatterContext(httpContext, metadata.ModelType);
|
|
||||||
|
|
||||||
// Act and Assert
|
|
||||||
await Assert.ThrowsAsync<JsonReaderException>(() => formatter.ReadAsync(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadAsync_AddsModelValidationErrorsToModelState_WhenCaptureErrorsIsSet()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var content = "{name: 'Person Name', Age: 'not-an-age'}";
|
|
||||||
var formatter = new JsonInputFormatter { CaptureDeserilizationErrors = true };
|
|
||||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
|
||||||
|
|
||||||
var actionContext = GetActionContext(contentBytes);
|
var actionContext = GetActionContext(contentBytes);
|
||||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(User));
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(User));
|
||||||
var context = new InputFormatterContext(actionContext, metadata.ModelType);
|
var context = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||||
|
|
@ -148,11 +132,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ReadAsync_UsesTryAddModelValidationErrorsToModelState_WhenCaptureErrorsIsSet()
|
public async Task ReadAsync_UsesTryAddModelValidationErrorsToModelState()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var content = "{name: 'Person Name', Age: 'not-an-age'}";
|
var content = "{name: 'Person Name', Age: 'not-an-age'}";
|
||||||
var formatter = new JsonInputFormatter { CaptureDeserilizationErrors = true };
|
var formatter = new JsonInputFormatter();
|
||||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
var actionContext = GetActionContext(contentBytes);
|
var actionContext = GetActionContext(contentBytes);
|
||||||
|
|
@ -189,7 +173,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
// missing password property here
|
// missing password property here
|
||||||
var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}");
|
var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}");
|
||||||
|
|
||||||
var jsonFormatter = new JsonInputFormatter() { CaptureDeserilizationErrors = true };
|
var jsonFormatter = new JsonInputFormatter();
|
||||||
// by default we ignore missing members, so here explicitly changing it
|
// by default we ignore missing members, so here explicitly changing it
|
||||||
jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
|
jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
|
||||||
|
|
||||||
|
|
@ -214,7 +198,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
// missing password property here
|
// missing password property here
|
||||||
var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}");
|
var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}");
|
||||||
|
|
||||||
var jsonFormatter = new JsonInputFormatter() { CaptureDeserilizationErrors = true };
|
var jsonFormatter = new JsonInputFormatter();
|
||||||
// by default we ignore missing members, so here explicitly changing it
|
// by default we ignore missing members, so here explicitly changing it
|
||||||
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
|
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
|
||||||
{
|
{
|
||||||
|
|
@ -240,7 +224,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var contentBytes = Encoding.UTF8.GetBytes("{\"Id\":\"null\",\"Name\":\"Programming C#\"}");
|
var contentBytes = Encoding.UTF8.GetBytes("{\"Id\":\"null\",\"Name\":\"Programming C#\"}");
|
||||||
var jsonFormatter = new JsonInputFormatter() { CaptureDeserilizationErrors = true };
|
var jsonFormatter = new JsonInputFormatter();
|
||||||
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
||||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(Book));
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(Book));
|
||||||
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||||
|
|
@ -267,7 +251,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var contentBytes = Encoding.UTF8.GetBytes("{ \"Name\" : \"Programming C#\"}");
|
var contentBytes = Encoding.UTF8.GetBytes("{ \"Name\" : \"Programming C#\"}");
|
||||||
var jsonFormatter = new JsonInputFormatter() { CaptureDeserilizationErrors = true };
|
var jsonFormatter = new JsonInputFormatter();
|
||||||
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
||||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(type);
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(type);
|
||||||
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||||
|
|
@ -288,7 +272,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var contentBytes = Encoding.UTF8.GetBytes("{\"Longitude\":{}}");
|
var contentBytes = Encoding.UTF8.GetBytes("{\"Longitude\":{}}");
|
||||||
var jsonFormatter = new JsonInputFormatter() { CaptureDeserilizationErrors = true };
|
var jsonFormatter = new JsonInputFormatter();
|
||||||
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
||||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(GpsCoordinate));
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(GpsCoordinate));
|
||||||
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||||
|
|
@ -317,7 +301,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var contentBytes = Encoding.UTF8.GetBytes("{\"Name\":\"Seattle\"}");
|
var contentBytes = Encoding.UTF8.GetBytes("{\"Name\":\"Seattle\"}");
|
||||||
var jsonFormatter = new JsonInputFormatter() { CaptureDeserilizationErrors = true };
|
var jsonFormatter = new JsonInputFormatter();
|
||||||
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
||||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(Location));
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(Location));
|
||||||
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||||
|
|
@ -338,7 +322,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var contentBytes = Encoding.UTF8.GetBytes("{}");
|
var contentBytes = Encoding.UTF8.GetBytes("{}");
|
||||||
var jsonFormatter = new JsonInputFormatter() { CaptureDeserilizationErrors = true };
|
var jsonFormatter = new JsonInputFormatter();
|
||||||
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
||||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(Venue));
|
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(Venue));
|
||||||
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
@ -30,6 +31,28 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
nameof(DataMemberAttribute.IsRequired),
|
nameof(DataMemberAttribute.IsRequired),
|
||||||
bool.TrueString);
|
bool.TrueString);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ThrowsOnInvalidInput_AndAddsToModelState()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var input = "Not a valid xml document";
|
||||||
|
var content = new StringContent(input, Encoding.UTF8, "application/xml-dcs");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PostAsync("http://localhost/Home/Index", content);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
var data = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Contains(
|
||||||
|
string.Format(
|
||||||
|
"dummyObject:There was an error deserializing the object of type {0}",
|
||||||
|
typeof(DummyClass).FullName),
|
||||||
|
data);
|
||||||
|
}
|
||||||
|
|
||||||
// Verifies that even though all the required data is posted to an action, the model
|
// Verifies that even though all the required data is posted to an action, the model
|
||||||
// state has errors related to value types's Required attribute validation.
|
// state has errors related to value types's Required attribute validation.
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -38,24 +38,21 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task XmlSerializerFormatter_ThrowsOnIncorrectInputNamespace()
|
public async Task ThrowsOnInvalidInput_AndAddsToModelState()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var server = TestServer.Create(_services, _app);
|
var server = TestServer.Create(_services, _app);
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
var sampleInputInt = 10;
|
var input = "Not a valid xml document";
|
||||||
var input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
|
||||||
"<DummyClas xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" " +
|
|
||||||
"i:type=\"DerivedDummyClass\" xmlns=\"http://schemas.datacontract.org/2004/07/XmlFormattersWebSite\">" +
|
|
||||||
"<SampleInt>" + sampleInputInt.ToString() + "</SampleInt></DummyClass>";
|
|
||||||
var content = new StringContent(input, Encoding.UTF8, "application/xml-xmlser");
|
var content = new StringContent(input, Encoding.UTF8, "application/xml-xmlser");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await client.PostAsync("http://localhost/Home/Index", content);
|
var response = await client.PostAsync("http://localhost/Home/Index", content);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var exception = response.GetServerException();
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType);
|
var data = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Contains("dummyObject:There is an error in XML document", data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,8 +29,6 @@ namespace ModelBindingWebSite
|
||||||
m.MaxModelValidationErrors = 8;
|
m.MaxModelValidationErrors = 8;
|
||||||
m.ModelBinders.Insert(0, typeof(TestBindingSourceModelBinder));
|
m.ModelBinders.Insert(0, typeof(TestBindingSourceModelBinder));
|
||||||
|
|
||||||
m.InputFormatters.InstanceOf<JsonInputFormatter>().CaptureDeserilizationErrors = true;
|
|
||||||
|
|
||||||
m.AddXmlDataContractSerializerFormatter();
|
m.AddXmlDataContractSerializerFormatter();
|
||||||
m.ValidationExcludeFilters.Add(typeof(Address));
|
m.ValidationExcludeFilters.Add(typeof(Address));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace XmlFormattersWebSite
|
namespace XmlFormattersWebSite
|
||||||
{
|
{
|
||||||
|
|
@ -13,7 +14,52 @@ namespace XmlFormattersWebSite
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult Index([FromBody]DummyClass dummyObject)
|
public IActionResult Index([FromBody]DummyClass dummyObject)
|
||||||
{
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return new ObjectResult(GetModelStateErrorMessages(ModelState))
|
||||||
|
{
|
||||||
|
StatusCode = 400
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return Content(dummyObject.SampleInt.ToString());
|
return Content(dummyObject.SampleInt.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cannot use 'SerializableError' here as it sanitizes exceptions in model state with generic error message.
|
||||||
|
// Since the tests need to verify the messages, we are doing the following.
|
||||||
|
private List<string> GetModelStateErrorMessages(ModelStateDictionary modelStateDictionary)
|
||||||
|
{
|
||||||
|
var allErrorMessages = new List<string>();
|
||||||
|
foreach (var keyModelStatePair in modelStateDictionary)
|
||||||
|
{
|
||||||
|
var key = keyModelStatePair.Key;
|
||||||
|
var errors = keyModelStatePair.Value.Errors;
|
||||||
|
if (errors != null && errors.Count > 0)
|
||||||
|
{
|
||||||
|
string errorMessage = null;
|
||||||
|
foreach (var modelError in errors)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(modelError.ErrorMessage))
|
||||||
|
{
|
||||||
|
if (modelError.Exception != null)
|
||||||
|
{
|
||||||
|
errorMessage = modelError.Exception.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorMessage = modelError.ErrorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorMessage != null)
|
||||||
|
{
|
||||||
|
allErrorMessages.Add(string.Format("{0}:{1}", key, errorMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrorMessages;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue