diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Filters/ResourceExecutingContext.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Filters/ResourceExecutingContext.cs index 30281025da..f35aab6d79 100644 --- a/src/Microsoft.AspNet.Mvc.Abstractions/Filters/ResourceExecutingContext.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/Filters/ResourceExecutingContext.cs @@ -27,12 +27,12 @@ namespace Microsoft.AspNet.Mvc.Filters /// /// Gets or sets the list of instances used by model binding. /// - public virtual IList InputFormatters { get; set; } + public virtual FormatterCollection InputFormatters { get; set; } /// /// Gets or sets the list of instances used to format the response. /// - public virtual IList OutputFormatters { get; set; } + public virtual FormatterCollection OutputFormatters { get; set; } /// /// Gets or sets the list of instances used by model binding. @@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc.Filters public virtual IList ModelBinders { get; set; } /// - /// Gets or sets the result of the action to be executed. + /// Gets or sets the result of the action to be executed. /// /// /// Setting to a non-null value inside a resource filter will diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatterCollection.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/FormatterCollection.cs similarity index 60% rename from src/Microsoft.AspNet.Mvc.Core/Formatters/FormatterCollection.cs rename to src/Microsoft.AspNet.Mvc.Abstractions/Formatters/FormatterCollection.cs index 005c0ab677..1daa7112ee 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatterCollection.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/FormatterCollection.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using System.Collections.ObjectModel; namespace Microsoft.AspNet.Mvc.Formatters @@ -11,6 +12,23 @@ namespace Microsoft.AspNet.Mvc.Formatters /// The type of formatters in the collection. public class FormatterCollection : Collection { + /// + /// Initializes a new instance of the class that is empty. + /// + public FormatterCollection() + : base() + { + } + + /// + /// Initializes a new instance of the class + /// as a wrapper for the specified list. + /// + /// The list that is wrapped by the new collection. + public FormatterCollection(IList list) + : base(list) + { + } /// /// Removes all formatters of the specified type. diff --git a/src/Microsoft.AspNet.Mvc.Core/Controllers/FilterActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/Controllers/FilterActionInvoker.cs index 3bc87fff0f..27a1e40d7c 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controllers/FilterActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controllers/FilterActionInvoker.cs @@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.Controllers private ResultExecutingContext _resultExecutingContext; private ResultExecutedContext _resultExecutedContext; - + public FilterActionInvoker( ActionContext actionContext, IReadOnlyList filterProviders, @@ -334,8 +334,10 @@ namespace Microsoft.AspNet.Mvc.Controllers var context = new ResourceExecutingContext(ActionContext, _filters); - context.InputFormatters = new CopyOnWriteList(_inputFormatters); - context.OutputFormatters = new CopyOnWriteList(_outputFormatters); + context.InputFormatters = new FormatterCollection( + new CopyOnWriteList(_inputFormatters)); + context.OutputFormatters = new FormatterCollection( + new CopyOnWriteList(_outputFormatters)); context.ModelBinders = new CopyOnWriteList(_modelBinders); context.ValidatorProviders = new CopyOnWriteList(_modelValidatorProviders); context.ValueProviderFactories = new CopyOnWriteList(_valueProviderFactories); @@ -411,7 +413,7 @@ namespace Microsoft.AspNet.Mvc.Controllers { // Short-circuited by setting a result. Logger.ResourceFilterShortCircuited(item.Filter); - + await InvokeResultAsync(_resourceExecutingContext.Result); _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs index a2f847358b..f9b2a21629 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; +using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -346,5 +347,34 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Assert Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } + + [Fact] + public async Task ResourceFilter_CreatesSpecificInputAndOutputFormatters() + { + // Arrange + var input = "{\"fullName\":\"John Doe\"}"; + + // Act + var response = await Client.PostAsync( + "http://localhost/api/ActionUsingSpecificFormatters", + new StringContent(input, Encoding.UTF8, "application/json")); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(input, await response.Content.ReadAsStringAsync()); + + // Make sure it does not affect other actions + //Arrange + input = "{\"FullName\":\"John Doe\"}"; + + // Act + response = await Client.PostAsync( + "http://localhost/api/ActionUsingGlobalFormatters", + new StringContent(input, Encoding.UTF8, "application/json")); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(input, await response.Content.ReadAsStringAsync()); + } } } \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Controllers/SpecificFormattersController.cs b/test/WebSites/BasicWebSite/Controllers/SpecificFormattersController.cs new file mode 100644 index 0000000000..1cadb3e0fe --- /dev/null +++ b/test/WebSites/BasicWebSite/Controllers/SpecificFormattersController.cs @@ -0,0 +1,71 @@ +// 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.AspNet.Mvc; +using Microsoft.AspNet.Mvc.Filters; +using Microsoft.AspNet.Mvc.Formatters; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace BasicWebSite +{ + public class SpecificFormattersController : Controller + { + [HttpPost("api/ActionUsingSpecificFormatters")] + [CamelCaseJsonFormatters] + public IActionResult ActionUsingSpecificFormatters([FromBody] Person person) + { + if (!ModelState.IsValid) + { + return HttpBadRequest(ModelState); + } + + return Ok(person); + } + + [HttpPost("api/ActionUsingGlobalFormatters")] + public IActionResult ActionUsingGlobalFormatters([FromBody] Person person) + { + if (!ModelState.IsValid) + { + return HttpBadRequest(ModelState); + } + + return Ok(person); + } + + public class Person + { + public string FullName { get; set; } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + private class CamelCaseJsonFormattersAttribute : Attribute, IResourceFilter + { + private readonly JsonSerializerSettings _serializerSettings; + + public CamelCaseJsonFormattersAttribute() + { + _serializerSettings = new JsonSerializerSettings(); + _serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + } + + public void OnResourceExecuted(ResourceExecutedContext context) + { + } + + public void OnResourceExecuting(ResourceExecutingContext context) + { + // Do not modify existing json formatters as they would effect all controllers. + // Instead remove and add new formatters which only effects the controllers this + // attribute is decorated on. + context.InputFormatters.RemoveType(); + context.OutputFormatters.RemoveType(); + + context.InputFormatters.Add(new JsonInputFormatter(_serializerSettings)); + context.OutputFormatters.Add(new JsonOutputFormatter(_serializerSettings)); + } + } + } +}