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)
|
||||
{
|
||||
var parameterType = parameter.BodyParameterInfo.ParameterType;
|
||||
var modelMetadata = metadataProvider.GetMetadataForType(
|
||||
modelAccessor: null,
|
||||
modelType: parameterType);
|
||||
var formatterContext = new InputFormatterContext(actionBindingContext.ActionContext,
|
||||
modelMetadata.ModelType);
|
||||
parameterType);
|
||||
var inputFormatter = actionBindingContext.InputFormatterSelector.SelectFormatter(
|
||||
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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
// 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.Globalization;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class DefaultInputFormatterSelector : IInputFormatterSelector
|
||||
{
|
||||
public IInputFormatter SelectFormatter(InputFormatterContext context)
|
||||
{
|
||||
// TODO: https://github.com/aspnet/Mvc/issues/1014
|
||||
var formatters = context.ActionContext.InputFormatters;
|
||||
foreach (var formatter in formatters)
|
||||
{
|
||||
|
|
@ -19,13 +15,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
return formatter;
|
||||
}
|
||||
}
|
||||
|
||||
var request = context.ActionContext.HttpContext.Request;
|
||||
|
||||
// TODO: https://github.com/aspnet/Mvc/issues/458
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
"415: Unsupported content type {0}",
|
||||
request.ContentType));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1082,6 +1082,22 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
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>
|
||||
/// 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>
|
||||
|
|
|
|||
|
|
@ -318,6 +318,9 @@
|
|||
<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>
|
||||
</data>
|
||||
<data name="UnsupportedContentType" xml:space="preserve">
|
||||
<value>Unsupported content type '{0}'.</value>
|
||||
</data>
|
||||
<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>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.Reflection;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
|
@ -1454,6 +1455,64 @@ namespace Microsoft.AspNet.Mvc
|
|||
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]
|
||||
public async Task Invoke_UsesDefaultValuesIfNotBound()
|
||||
{
|
||||
|
|
@ -1524,6 +1583,18 @@ namespace Microsoft.AspNet.Mvc
|
|||
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
|
||||
{
|
||||
public IActionResult ActionMethodWithDefaultValues(int value = 5)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
|
|
@ -60,23 +61,43 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
[InlineData("invalid")]
|
||||
public async Task JsonInputFormatter_IsNotSelectedForNonJsonRequests(string requestContentType)
|
||||
[InlineData("", true)]
|
||||
[InlineData(null, true)]
|
||||
[InlineData("invalid", true)]
|
||||
[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
|
||||
var actionName = filterHandlesModelStateError ? "ActionFilterHandlesError" : "ActionHandlesError";
|
||||
var expectedSource = filterHandlesModelStateError ? "filter" : "action";
|
||||
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.Handler;
|
||||
var input = "{\"SampleInt\":10}";
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>
|
||||
(() => client.PostAsync("http://localhost/Home/CheckIfDummyIsNull", input, requestContentType));
|
||||
var response = await client.PostAsync("http://localhost/InputFormatter/" + actionName,
|
||||
input,
|
||||
requestContentType,
|
||||
(request) => request.Accept = "application/json");
|
||||
|
||||
//Assert
|
||||
// TODO: Change the validation after https://github.com/aspnet/Mvc/issues/458 is fixed.
|
||||
Assert.Equal("415: Unsupported content type " + requestContentType, ex.Message);
|
||||
var responseBody = await response.ReadBodyAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<FormatterWebSite.ErrorInfo>(responseBody);
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -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