Adding ModelStateError if there is no input formatter selected.
This commit is contained in:
parent
9faca78a84
commit
313a537ea1
|
|
@ -237,14 +237,20 @@ namespace Microsoft.AspNet.Mvc
|
||||||
if (parameter.BodyParameterInfo != null)
|
if (parameter.BodyParameterInfo != null)
|
||||||
{
|
{
|
||||||
var parameterType = parameter.BodyParameterInfo.ParameterType;
|
var parameterType = parameter.BodyParameterInfo.ParameterType;
|
||||||
var modelMetadata = metadataProvider.GetMetadataForType(
|
|
||||||
modelAccessor: null,
|
|
||||||
modelType: parameterType);
|
|
||||||
var formatterContext = new InputFormatterContext(actionBindingContext.ActionContext,
|
var formatterContext = new InputFormatterContext(actionBindingContext.ActionContext,
|
||||||
modelMetadata.ModelType);
|
parameterType);
|
||||||
var inputFormatter = actionBindingContext.InputFormatterSelector.SelectFormatter(
|
var inputFormatter = actionBindingContext.InputFormatterSelector.SelectFormatter(
|
||||||
formatterContext);
|
formatterContext);
|
||||||
parameterValues[parameter.Name] = await inputFormatter.ReadAsync(formatterContext);
|
if (inputFormatter == null)
|
||||||
|
{
|
||||||
|
var request = ActionContext.HttpContext.Request;
|
||||||
|
var unsupportedContentType = Resources.FormatUnsupportedContentType(request.ContentType);
|
||||||
|
ActionContext.ModelState.AddModelError(parameter.Name, unsupportedContentType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parameterValues[parameter.Name] = await inputFormatter.ReadAsync(formatterContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,12 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
public class DefaultInputFormatterSelector : IInputFormatterSelector
|
public class DefaultInputFormatterSelector : IInputFormatterSelector
|
||||||
{
|
{
|
||||||
public IInputFormatter SelectFormatter(InputFormatterContext context)
|
public IInputFormatter SelectFormatter(InputFormatterContext context)
|
||||||
{
|
{
|
||||||
// TODO: https://github.com/aspnet/Mvc/issues/1014
|
|
||||||
var formatters = context.ActionContext.InputFormatters;
|
var formatters = context.ActionContext.InputFormatters;
|
||||||
foreach (var formatter in formatters)
|
foreach (var formatter in formatters)
|
||||||
{
|
{
|
||||||
|
|
@ -19,13 +15,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return formatter;
|
return formatter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = context.ActionContext.HttpContext.Request;
|
return null;
|
||||||
|
|
||||||
// TODO: https://github.com/aspnet/Mvc/issues/458
|
|
||||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
|
||||||
"415: Unsupported content type {0}",
|
|
||||||
request.ContentType));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1082,6 +1082,22 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("InputFormatterNoEncoding"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("InputFormatterNoEncoding"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsupported content type '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string UnsupportedContentType
|
||||||
|
{
|
||||||
|
get { return GetString("UnsupportedContentType"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsupported content type '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatUnsupportedContentType(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedContentType"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No supported media type registered for output formatter '{0}'. There must be at least one supported media type registered in order for the output formatter to write content.
|
/// No supported media type registered for output formatter '{0}'. There must be at least one supported media type registered in order for the output formatter to write content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,9 @@
|
||||||
<data name="InputFormatterNoEncoding" xml:space="preserve">
|
<data name="InputFormatterNoEncoding" xml:space="preserve">
|
||||||
<value>No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content.</value>
|
<value>No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="UnsupportedContentType" xml:space="preserve">
|
||||||
|
<value>Unsupported content type '{0}'.</value>
|
||||||
|
</data>
|
||||||
<data name="OutputFormatterNoMediaType" xml:space="preserve">
|
<data name="OutputFormatterNoMediaType" xml:space="preserve">
|
||||||
<value>No supported media type registered for output formatter '{0}'. There must be at least one supported media type registered in order for the output formatter to write content.</value>
|
<value>No supported media type registered for output formatter '{0}'. There must be at least one supported media type registered in order for the output formatter to write content.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.PipelineCore;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.AspNet.Testing;
|
using Microsoft.AspNet.Testing;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
@ -1454,6 +1455,64 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Equal(value, result["foo"]);
|
Assert.Equal(value, result["foo"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetActionArguments_NoInputFormatterFound_SetsModelStateError()
|
||||||
|
{
|
||||||
|
var actionDescriptor = new ReflectedActionDescriptor
|
||||||
|
{
|
||||||
|
MethodInfo = typeof(TestController).GetTypeInfo().GetMethod("ActionMethodWithDefaultValues"),
|
||||||
|
Parameters = new List<ParameterDescriptor>
|
||||||
|
{
|
||||||
|
new ParameterDescriptor
|
||||||
|
{
|
||||||
|
Name = "bodyParam",
|
||||||
|
BodyParameterInfo = new BodyParameterInfo(typeof(Person))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FilterDescriptors = new List<FilterDescriptor>()
|
||||||
|
};
|
||||||
|
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
var routeContext = new RouteContext(context);
|
||||||
|
var actionContext = new ActionContext(routeContext,
|
||||||
|
actionDescriptor);
|
||||||
|
var bindingContext = new ActionBindingContext(actionContext,
|
||||||
|
Mock.Of<IModelMetadataProvider>(),
|
||||||
|
Mock.Of<IModelBinder>(),
|
||||||
|
Mock.Of<IValueProvider>(),
|
||||||
|
Mock.Of<IInputFormatterSelector>(),
|
||||||
|
Enumerable.Empty<IModelValidatorProvider>());
|
||||||
|
|
||||||
|
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||||
|
actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
|
||||||
|
.Returns(Task.FromResult(bindingContext));
|
||||||
|
var controllerFactory = new Mock<IControllerFactory>();
|
||||||
|
controllerFactory.Setup(c => c.CreateController(It.IsAny<ActionContext>()))
|
||||||
|
.Returns(new TestController());
|
||||||
|
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
|
||||||
|
inputFormattersProvider.SetupGet(o => o.InputFormatters)
|
||||||
|
.Returns(new List<IInputFormatter>());
|
||||||
|
var invoker = new ReflectedActionInvoker(actionContext,
|
||||||
|
actionBindingContextProvider.Object,
|
||||||
|
Mock.Of<INestedProviderManager<FilterProviderContext>>(),
|
||||||
|
controllerFactory.Object,
|
||||||
|
actionDescriptor,
|
||||||
|
inputFormattersProvider.Object);
|
||||||
|
|
||||||
|
|
||||||
|
var modelStateDictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await invoker.GetActionArguments(modelStateDictionary);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(result);
|
||||||
|
Assert.DoesNotContain("bodyParam", result.Keys);
|
||||||
|
Assert.False(actionContext.ModelState.IsValid);
|
||||||
|
Assert.Equal("Unsupported content type '" + context.Request.ContentType + "'.",
|
||||||
|
actionContext.ModelState["bodyParam"].Errors[0].ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Invoke_UsesDefaultValuesIfNotBound()
|
public async Task Invoke_UsesDefaultValuesIfNotBound()
|
||||||
{
|
{
|
||||||
|
|
@ -1524,6 +1583,18 @@ namespace Microsoft.AspNet.Mvc
|
||||||
throw _actionException;
|
throw _actionException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JsonResult ActionMethodWithBodyParameter([FromBody] Person bodyParam)
|
||||||
|
{
|
||||||
|
return new JsonResult(bodyParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Person
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public int Age { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class TestController
|
private sealed class TestController
|
||||||
{
|
{
|
||||||
public IActionResult ActionMethodWithDefaultValues(int value = 5)
|
public IActionResult ActionMethodWithDefaultValues(int value = 5)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.TestHost;
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
@ -60,23 +61,43 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("")]
|
[InlineData("", true)]
|
||||||
[InlineData(null)]
|
[InlineData(null, true)]
|
||||||
[InlineData("invalid")]
|
[InlineData("invalid", true)]
|
||||||
public async Task JsonInputFormatter_IsNotSelectedForNonJsonRequests(string requestContentType)
|
[InlineData("application/custom", true)]
|
||||||
|
[InlineData("image/jpg", true)]
|
||||||
|
[InlineData("", false)]
|
||||||
|
[InlineData(null, false)]
|
||||||
|
[InlineData("invalid", false)]
|
||||||
|
[InlineData("application/custom", false)]
|
||||||
|
[InlineData("image/jpg", false)]
|
||||||
|
public async Task ModelStateErrorValidation_NoInputFormatterFound_ForGivenContetType(string requestContentType,
|
||||||
|
bool filterHandlesModelStateError)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var actionName = filterHandlesModelStateError ? "ActionFilterHandlesError" : "ActionHandlesError";
|
||||||
|
var expectedSource = filterHandlesModelStateError ? "filter" : "action";
|
||||||
|
|
||||||
var server = TestServer.Create(_services, _app);
|
var server = TestServer.Create(_services, _app);
|
||||||
var client = server.Handler;
|
var client = server.Handler;
|
||||||
var input = "{\"SampleInt\":10}";
|
var input = "{\"SampleInt\":10}";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>
|
var response = await client.PostAsync("http://localhost/InputFormatter/" + actionName,
|
||||||
(() => client.PostAsync("http://localhost/Home/CheckIfDummyIsNull", input, requestContentType));
|
input,
|
||||||
|
requestContentType,
|
||||||
|
(request) => request.Accept = "application/json");
|
||||||
|
|
||||||
//Assert
|
var responseBody = await response.ReadBodyAsStringAsync();
|
||||||
// TODO: Change the validation after https://github.com/aspnet/Mvc/issues/458 is fixed.
|
var result = JsonConvert.DeserializeObject<FormatterWebSite.ErrorInfo>(responseBody);
|
||||||
Assert.Equal("415: Unsupported content type " + requestContentType, ex.Message);
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(1, result.Errors.Count);
|
||||||
|
Assert.Equal("Unsupported content type '" + requestContentType + "'.",
|
||||||
|
result.Errors[0]);
|
||||||
|
Assert.Equal(actionName, result.ActionName);
|
||||||
|
Assert.Equal("dummy", result.ParameterName);
|
||||||
|
Assert.Equal(expectedSource, result.Source);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: By default XmlSerializerInputFormatter is called because of the order in which
|
// TODO: By default XmlSerializerInputFormatter is called because of the order in which
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class InputFormatterController : Controller
|
||||||
|
{
|
||||||
|
[HttpPost]
|
||||||
|
public object ActionHandlesError([FromBody] DummyClass dummy)
|
||||||
|
{
|
||||||
|
if (!ActionContext.ModelState.IsValid)
|
||||||
|
{
|
||||||
|
var parameterBindingErrors = ActionContext.ModelState["dummy"].Errors;
|
||||||
|
if (parameterBindingErrors.Count != 0)
|
||||||
|
{
|
||||||
|
return new ErrorInfo
|
||||||
|
{
|
||||||
|
ActionName = "ActionHandlesError",
|
||||||
|
ParameterName = "dummy",
|
||||||
|
Errors = parameterBindingErrors.Select(x => x.ErrorMessage).ToList(),
|
||||||
|
Source = "action"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateBodyParameter]
|
||||||
|
public object ActionFilterHandlesError([FromBody] DummyClass dummy)
|
||||||
|
{
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FormatterWebSite
|
||||||
|
{
|
||||||
|
public class ErrorInfo
|
||||||
|
{
|
||||||
|
public string Source { get; set; }
|
||||||
|
|
||||||
|
public string ActionName { get; set; }
|
||||||
|
|
||||||
|
public string ParameterName { get; set; }
|
||||||
|
|
||||||
|
public List<string> Errors { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace FormatterWebSite
|
||||||
|
{
|
||||||
|
public class ValidateBodyParameterAttribute : ActionFilterAttribute
|
||||||
|
{
|
||||||
|
public override void OnActionExecuting(ActionExecutingContext context)
|
||||||
|
{
|
||||||
|
if (!context.ModelState.IsValid)
|
||||||
|
{
|
||||||
|
var bodyParameter = context.ActionDescriptor
|
||||||
|
.Parameters
|
||||||
|
.FirstOrDefault(parameter => parameter.BodyParameterInfo != null);
|
||||||
|
if (bodyParameter != null)
|
||||||
|
{
|
||||||
|
var parameterBindingErrors = context.ModelState[bodyParameter.Name].Errors;
|
||||||
|
if (parameterBindingErrors.Count != 0)
|
||||||
|
{
|
||||||
|
var errorInfo = new ErrorInfo
|
||||||
|
{
|
||||||
|
ActionName = context.ActionDescriptor.Name,
|
||||||
|
ParameterName = bodyParameter.Name,
|
||||||
|
Errors = parameterBindingErrors.Select(x => x.ErrorMessage).ToList(),
|
||||||
|
Source = "filter"
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Result = new ObjectResult(errorInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnActionExecuting(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue