diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
index 898e8dd3c9..b73a8797c4 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
@@ -48,6 +48,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
options.ModelBinders.Add(new GenericModelBinder());
options.ModelBinders.Add(new MutableObjectModelBinder());
+ // Set up filters
+ options.Filters.Add(new UnsupportedContentTypeFilter());
+
// Set up default output formatters.
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
options.OutputFormatters.Add(new StringOutputFormatter());
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BodyModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BodyModelBinder.cs
index 532368e395..4ad43c9168 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BodyModelBinder.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BodyModelBinder.cs
@@ -84,9 +84,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
if (formatter == null)
{
- var unsupportedContentType = Resources.FormatUnsupportedContentType(
+ var message = Resources.FormatUnsupportedContentType(
bindingContext.OperationBindingContext.HttpContext.Request.ContentType);
- bindingContext.ModelState.AddModelError(modelBindingKey, unsupportedContentType);
+
+ var exception = new UnsupportedContentTypeException(message);
+ bindingContext.ModelState.AddModelError(modelBindingKey, exception, 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
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.cs
new file mode 100644
index 0000000000..819ce86c10
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Mvc.Formatters;
+
+namespace Microsoft.AspNetCore.Mvc.ModelBinding
+{
+ ///
+ /// The that is added to model state when a model binder for the body of the request is
+ /// unable to understand the request content type header.
+ ///
+ public class UnsupportedContentTypeException : Exception
+ {
+ ///
+ /// Creates a new instance of with the specified
+ /// exception .
+ ///
+ /// The message that describes the error.
+ public UnsupportedContentTypeException(string message)
+ : base(message)
+ {
+ if (message == null)
+ {
+ throw new ArgumentNullException(nameof(message));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs
new file mode 100644
index 0000000000..35e30a119c
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs
@@ -0,0 +1,55 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Mvc.ModelBinding
+{
+ ///
+ /// A filter that scans for in the
+ /// and shortcircuits the pipeline
+ /// with an Unsupported Media Type (415) response.
+ ///
+ public class UnsupportedContentTypeFilter : IActionFilter
+ {
+ ///
+ public void OnActionExecuting(ActionExecutingContext context)
+ {
+ if (HasUnsupportedContentTypeError(context))
+ {
+ context.Result = new UnsupportedMediaTypeResult();
+ }
+ }
+
+ private bool HasUnsupportedContentTypeError(ActionExecutingContext context)
+ {
+ var modelState = context.ModelState;
+ if (modelState.IsValid)
+ {
+ return false;
+ }
+
+ foreach (var kvp in modelState)
+ {
+ var errors = kvp.Value.Errors;
+ for (int i = 0; i < errors.Count; i++)
+ {
+ var error = errors[i];
+ if (error.Exception is UnsupportedContentTypeException)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ public void OnActionExecuted(ActionExecutedContext context)
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/UnsupportedContentTypeFilterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/UnsupportedContentTypeFilterTest.cs
new file mode 100644
index 0000000000..2da01f56d3
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/UnsupportedContentTypeFilterTest.cs
@@ -0,0 +1,132 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.AspNetCore.Mvc.Abstractions;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Routing;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.ModelBinding
+{
+ public class UnsupportedContentTypeFilterTest
+ {
+ [Fact]
+ public void OnActionExecuting_ChangesActionResult_IfUnsupportedContentTypeExceptionIsFoundOnModelState()
+ {
+ // Arrange
+ var context = new ActionExecutingContext(
+ new ActionContext
+ {
+ HttpContext = new DefaultHttpContext(),
+ RouteData = new RouteData(),
+ ActionDescriptor = new ActionDescriptor()
+ },
+ new List(),
+ new Dictionary(),
+ new object());
+
+ var modelMetadata = new EmptyModelMetadataProvider()
+ .GetMetadataForType(typeof(int));
+
+ context.ModelState.AddModelError(
+ "person.body",
+ new UnsupportedContentTypeException("error"),
+ modelMetadata);
+
+ var filter = new UnsupportedContentTypeFilter();
+
+ // Act
+ filter.OnActionExecuting(context);
+
+ // Assert
+ Assert.NotNull(context.Result);
+ var status = Assert.IsType(context.Result);
+ }
+
+ [Fact]
+ public void OnActionExecuting_DoesNotChangeActionResult_IfOtherErrorsAreFoundOnModelState()
+ {
+ // Arrange
+ var context = new ActionExecutingContext(
+ new ActionContext
+ {
+ HttpContext = new DefaultHttpContext(),
+ RouteData = new RouteData(),
+ ActionDescriptor = new ActionDescriptor()
+ },
+ new List(),
+ new Dictionary(),
+ new object());
+
+ context.ModelState.AddModelError("person.body", "Some error");
+
+ var filter = new UnsupportedContentTypeFilter();
+
+ // Act
+ filter.OnActionExecuting(context);
+
+ // Assert
+ Assert.Null(context.Result);
+ }
+
+ [Fact]
+ public void OnActionExecuting_DoesNotChangeActionResult_IfModelStateIsValid()
+ {
+ // Arrange
+ var context = new ActionExecutingContext(
+ new ActionContext
+ {
+ HttpContext = new DefaultHttpContext(),
+ RouteData = new RouteData(),
+ ActionDescriptor = new ActionDescriptor()
+ },
+ new List(),
+ new Dictionary(),
+ new object());
+
+ var filter = new UnsupportedContentTypeFilter();
+
+ // Act
+ filter.OnActionExecuting(context);
+
+ // Assert
+ Assert.Null(context.Result);
+ }
+
+ [Fact]
+ public void OnActionExecuting_DoesNotChangeActionResult_IfOtherExceptionsAreFoundOnModelState()
+ {
+ // Arrange
+ var context = new ActionExecutingContext(
+ new ActionContext
+ {
+ HttpContext = new DefaultHttpContext(),
+ RouteData = new RouteData(),
+ ActionDescriptor = new ActionDescriptor()
+ },
+ new List(),
+ new Dictionary(),
+ new object());
+
+ var modelMetadata = new EmptyModelMetadataProvider()
+ .GetMetadataForType(typeof(int));
+
+ context.ModelState.AddModelError(
+ "person.body",
+ new Exception("error"),
+ modelMetadata);
+
+ var filter = new UnsupportedContentTypeFilter();
+
+ // Act
+ filter.OnActionExecuting(context);
+
+ // Assert
+ Assert.Null(context.Result);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs
index 5d57a147a7..2179704e52 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs
@@ -203,7 +203,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
// Key is empty because this was a top-level binding.
var entry = Assert.Single(bindingContext.ModelState);
Assert.Equal(string.Empty, entry.Key);
- var errorMessage = Assert.Single(entry.Value.Errors).ErrorMessage;
+ var errorMessage = Assert.Single(entry.Value.Errors).Exception.Message;
Assert.Equal("Unsupported content type 'text/xyz'.", errorMessage);
}
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs
index a091631bd7..18e162e3d6 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs
@@ -32,11 +32,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Act
var response = await Client.SendAsync(request);
- var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
+ var body = await response.Content.ReadAsStringAsync();
// Assert
- Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
- Assert.Null(product);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("CreateProduct_Product_Text", body);
}
[Fact]
@@ -49,11 +49,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Act
var response = await Client.SendAsync(request);
- var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
+ var body = await response.Content.ReadAsStringAsync();
// Assert
- Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
- Assert.Null(product);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("ConsumesAttribute_PassThrough_Product_Json", body);
}
[Theory]
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs
index 499f5e0cbb..9b0ba5a8fa 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs
@@ -552,7 +552,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
"10" +
"";
- // There's nothing that can deserialize the body, so the result contains the default value.
+ // There's nothing that can deserialize the body, so the result is UnsupportedMediaType.
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Json");
request.Content = new StringContent(input, Encoding.UTF8, "application/xml");
@@ -560,8 +560,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var response = await Client.SendAsync(request);
// Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("\"0\"", await response.Content.ReadAsStringAsync());
+ Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode);
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs
index 88d2a11151..eb56bc0298 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs
@@ -59,47 +59,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(sampleInputInt.ToString(), await response.Content.ReadAsStringAsync());
}
- [Theory]
- [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_ForGivenContentType(
- string requestContentType,
- bool filterHandlesModelStateError)
- {
- // Arrange
- var actionName = filterHandlesModelStateError ? "ActionFilterHandlesError" : "ActionHandlesError";
- var expectedSource = filterHandlesModelStateError ? "filter" : "action";
- var input = "{\"SampleInt\":10}";
- var content = new StringContent(input);
- content.Headers.Clear();
- content.Headers.TryAddWithoutValidation("Content-Type", requestContentType);
-
- // Act
- var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/InputFormatter/" + actionName);
- request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
- request.Content = content;
- var response = await Client.SendAsync(request);
-
- var responseBody = await response.Content.ReadAsStringAsync();
- var result = JsonConvert.DeserializeObject(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);
- }
-
[Theory]
[InlineData("application/json", "{\"SampleInt\":10}", 10)]
[InlineData("application/json", "{}", 0)]
@@ -154,13 +113,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(expected, responseBody);
}
- [Theory]
- [InlineData("{\"SampleInt\":10}")]
- [InlineData("{}")]
- [InlineData("")]
- public async Task JsonInputFormatter_IsModelStateInvalid_ForEmptyContentType(string jsonInput)
+ [Fact]
+ public async Task JsonInputFormatter_Returns415UnsupportedMediaType_ForEmptyContentType()
{
// Arrange
+ var jsonInput = "{\"SampleInt\":10}";
var content = new StringContent(jsonInput, Encoding.UTF8, "application/json");
content.Headers.Clear();
@@ -169,7 +126,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
- Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode);
}
[Theory]
@@ -225,7 +182,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
- Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode);
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs
index 464736ac50..e8bc1b4984 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs
@@ -3,6 +3,7 @@
using System.Net;
using System.Net.Http;
+using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Xunit;
@@ -290,9 +291,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[InlineData("GET", "api/Admin/Test?name=mario&ssn=123456", "GetUserByNameAndSsn")]
[InlineData("GET", "api/Admin/Test?name=mario&ssn=123456&age=3", "GetUserByNameAgeAndSsn")]
[InlineData("GET", "api/Admin/Test/5?random=9", "GetUser")]
- [InlineData("POST", "api/Admin/Test", "PostUser")]
- [InlineData("POST", "api/Admin/Test?name=mario&age=10", "PostUserByNameAndAge")]
-
// Note: Normally the following would not match DeleteUserByIdAndOptName because it has 'id' and 'age' as parameters while the DeleteUserByIdAndOptName action has 'id' and 'name'.
// However, because the default value is provided on action parameter 'name', having the 'id' in the request was enough to match the action.
[InlineData("DELETE", "api/Admin/Test/6?age=10", "DeleteUserByIdAndOptName")]
@@ -328,6 +326,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(expectedActionName, result.ActionName);
}
+ [Theory]
+ [InlineData("api/Admin/Test", "PostUser")]
+ [InlineData("api/Admin/Test?name=mario&age=10", "PostUserByNameAndAge")]
+ public async Task LegacyActionSelection_OverloadedAction_WithUnnamedAction(string requestUrl, string expectedActionName)
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + requestUrl);
+ request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ var data = Assert.Single(response.Headers.GetValues("ActionSelection"));
+ var result = JsonConvert.DeserializeObject(data);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(expectedActionName, result.ActionName);
+ }
+
[Theory]
[InlineData("GET", "api/Store/Test", "GetUsers")]
[InlineData("GET", "api/Store/Test/2", "GetUsersByName")]
@@ -378,7 +395,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[InlineData("GET", "api/Blog/Test/GetUser/4?id=3", "GetUser")]
[InlineData("GET", "api/Blog/Test/GetUserByNameAgeAndSsn?name=user&age=90&ssn=123456789", "GetUserByNameAgeAndSsn")]
[InlineData("GET", "api/Blog/Test/GetUserByNameAndSsn?name=user&ssn=123456789", "GetUserByNameAndSsn")]
- [InlineData("POST", "api/Blog/Test/PostUserByNameAndAddress?name=user", "PostUserByNameAndAddress")]
public async Task LegacyActionSelection_RouteWithActionName(string httpMethod, string requestUrl, string expectedActionName)
{
// Arrange
@@ -394,6 +410,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(expectedActionName, result.ActionName);
}
+ [Fact]
+ public async Task LegacyActionSelection_RouteWithActionName()
+ {
+ // Arrange
+ var request = new HttpRequestMessage(
+ HttpMethod.Post,
+ "http://localhost/api/Blog/Test/PostUserByNameAndAddress?name=user");
+ request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ var data = Assert.Single(response.Headers.GetValues("ActionSelection"));
+ var result = JsonConvert.DeserializeObject(data);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("PostUserByNameAndAddress", result.ActionName);
+ }
+
[Theory]
[InlineData("GET", "api/Blog/Test/getusers", "GetUsers")]
[InlineData("GET", "api/Blog/Test/getuseR/1", "GetUser")]
@@ -401,7 +436,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[InlineData("GET", "api/Blog/Test/GetUser/4?Id=3", "GetUser")]
[InlineData("GET", "api/Blog/Test/GetUserByNameAgeandSsn?name=user&age=90&ssn=123456789", "GetUserByNameAgeAndSsn")]
[InlineData("GET", "api/Blog/Test/getUserByNameAndSsn?name=user&ssn=123456789", "GetUserByNameAndSsn")]
- [InlineData("POST", "api/Blog/Test/PostUserByNameAndAddress?name=user", "PostUserByNameAndAddress")]
public async Task LegacyActionSelection_RouteWithActionName_Casing(string httpMethod, string requestUrl, string expectedActionName)
{
// Arrange
@@ -417,6 +451,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(expectedActionName, result.ActionName);
}
+ [Fact]
+ public async Task LegacyActionSelection_RouteWithActionName_Casing()
+ {
+ // Arrange
+ var request = new HttpRequestMessage(
+ HttpMethod.Post,
+ "http://localhost/api/Blog/Test/PostUserByNameAndAddress?name=user");
+ request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ var data = Assert.Single(response.Headers.GetValues("ActionSelection"));
+ var result = JsonConvert.DeserializeObject(data);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("PostUserByNameAndAddress", result.ActionName);
+ }
+
[Theory]
[InlineData("GET", "api/Admin/Test", "GetUsers")]
[InlineData("GET", "api/Admin/Test/?name=peach", "GetUsersByName")]
@@ -438,19 +491,36 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(expectedActionName, result.ActionName);
}
-
[Theory]
[InlineData("GET", "api/Admin/ParameterAttribute/2", "GetUser")]
[InlineData("GET", "api/Admin/ParameterAttribute?id=2", "GetUser")]
[InlineData("GET", "api/Admin/ParameterAttribute?myId=2", "GetUserByMyId")]
- [InlineData("POST", "api/Admin/ParameterAttribute/3?name=user", "PostUserNameFromUri")]
- [InlineData("POST", "api/Admin/ParameterAttribute/3", "PostUserNameFromBody")]
[InlineData("DELETE", "api/Admin/ParameterAttribute/3?name=user", "DeleteUserWithNullableIdAndName")]
[InlineData("DELETE", "api/Admin/ParameterAttribute?address=userStreet", "DeleteUser")]
public async Task LegacyActionSelection_ModelBindingParameterAttribute_AreAppliedWhenSelectingActions(string httpMethod, string requestUrl, string expectedActionName)
{
// Arrange
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
+ request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ var data = Assert.Single(response.Headers.GetValues("ActionSelection"));
+ var result = JsonConvert.DeserializeObject(data);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(expectedActionName, result.ActionName);
+ }
+
+ [Theory]
+ [InlineData("api/Admin/ParameterAttribute/3?name=user", "PostUserNameFromUri")]
+ [InlineData("api/Admin/ParameterAttribute/3", "PostUserNameFromBody")]
+ public async Task LegacyActionSelection_Post_ModelBindingParameterAttribute_AreAppliedWhenSelectingActions(string requestUrl, string expectedActionName)
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + requestUrl);
+ request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
// Act
var response = await Client.SendAsync(request);
diff --git a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_PassThroughController.cs b/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_PassThroughController.cs
index 49aaf03767..ce405047c8 100644
--- a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_PassThroughController.cs
+++ b/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_PassThroughController.cs
@@ -10,9 +10,9 @@ namespace BasicWebSite.Controllers.ActionConstraints
public class ConsumesAttribute_PassThroughController : Controller
{
[Consumes("application/json")]
- public Product CreateProduct([FromBody] Product_Json jsonInput)
+ public IActionResult CreateProduct(Product_Json jsonInput)
{
- return jsonInput;
+ return Content("ConsumesAttribute_PassThrough_Product_Json");
}
}
}
\ No newline at end of file
diff --git a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_WithFallbackActionController.cs b/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_WithFallbackActionController.cs
index a8380a849e..eb92870096 100644
--- a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_WithFallbackActionController.cs
+++ b/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_WithFallbackActionController.cs
@@ -10,20 +10,22 @@ namespace BasicWebSite.Controllers.ActionConstraints
public class ConsumesAttribute_WithFallbackActionController : Controller
{
[Consumes("application/json")]
- public Product CreateProduct([FromBody] Product_Json jsonInput)
+ [ActionName("CreateProduct")]
+ public IActionResult CreateProductJson()
{
- return jsonInput;
+ return Content("CreateProduct_Product_Json");
}
[Consumes("application/xml")]
- public Product CreateProduct([FromBody] Product_Xml xmlInput)
+ [ActionName("CreateProduct")]
+ public IActionResult CreateProductXml()
{
- return xmlInput;
+ return Content("CreateProduct_Product_Xml");
}
- public Product CreateProduct([FromBody] Product_Text defaultInput)
+ public IActionResult CreateProduct()
{
- return defaultInput;
+ return Content("CreateProduct_Product_Text");
}
}
}
\ No newline at end of file
diff --git a/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs b/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs
index 006e332857..5957e233db 100644
--- a/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs
+++ b/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs
@@ -44,12 +44,14 @@ namespace FiltersWebSite.Controllers
public void OnResultExecuting(ResultExecutingContext context)
{
- var options = context.HttpContext.RequestServices.GetRequiredService>();
- var jsonFormatter = options.Value.OutputFormatters.OfType().Single();
-
// Update the output formatter collection to only return JSON.
- var result = (ObjectResult)context.Result;
- result.Formatters.Add(jsonFormatter);
+ if (context.Result is ObjectResult)
+ {
+ var options = context.HttpContext.RequestServices.GetRequiredService>();
+ var jsonFormatter = options.Value.OutputFormatters.OfType().Single();
+ var result = (ObjectResult)context.Result;
+ result.Formatters.Add(jsonFormatter);
+ }
}
}
}
diff --git a/test/WebSites/FormatterWebSite/Controllers/InputFormatterController.cs b/test/WebSites/FormatterWebSite/Controllers/InputFormatterController.cs
index 383d6155e3..2625658818 100644
--- a/test/WebSites/FormatterWebSite/Controllers/InputFormatterController.cs
+++ b/test/WebSites/FormatterWebSite/Controllers/InputFormatterController.cs
@@ -9,35 +9,6 @@ namespace FormatterWebSite.Controllers
{
public class InputFormatterController : Controller
{
- [HttpPost]
- public object ActionHandlesError([FromBody] DummyClass dummy)
- {
- if (!ModelState.IsValid)
- {
- // Body model binder normally reports errors for parameters using the empty name.
- var parameterBindingErrors = ModelState["dummy"]?.Errors ?? ModelState[string.Empty]?.Errors;
- if (parameterBindingErrors != null && 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;
- }
-
public IActionResult ReturnInput([FromBody] string test)
{
if (!ModelState.IsValid)
diff --git a/test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs b/test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs
index d74b9b032b..88b4e0e7c0 100644
--- a/test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs
+++ b/test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs
@@ -33,10 +33,6 @@ namespace FormatterWebSite.Controllers
[HttpPost]
public IActionResult ReturnInput([FromBody]DummyClass dummyObject)
{
- if (!ModelState.IsValid)
- {
- return new HttpStatusCodeResult(StatusCodes.Status400BadRequest);
- }
return Content(dummyObject.SampleInt.ToString());
}