[Fixes #3503] Removing formatters on a given type only works on collections in MVC options

This commit is contained in:
Kiran Challa 2015-11-15 05:05:14 -08:00
parent a65a6a7cab
commit 982c7abea8
5 changed files with 128 additions and 7 deletions

View File

@ -27,12 +27,12 @@ namespace Microsoft.AspNet.Mvc.Filters
/// <summary>
/// Gets or sets the list of <see cref="IInputFormatter"/> instances used by model binding.
/// </summary>
public virtual IList<IInputFormatter> InputFormatters { get; set; }
public virtual FormatterCollection<IInputFormatter> InputFormatters { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="IOutputFormatter"/> instances used to format the response.
/// </summary>
public virtual IList<IOutputFormatter> OutputFormatters { get; set; }
public virtual FormatterCollection<IOutputFormatter> OutputFormatters { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="IModelBinder"/> instances used by model binding.
@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc.Filters
public virtual IList<IModelBinder> ModelBinders { get; set; }
/// <summary>
/// Gets or sets the result of the action to be executed.
/// Gets or sets the result of the action to be executed.
/// </summary>
/// <remarks>
/// Setting <see cref="Result"/> to a non-<c>null</c> value inside a resource filter will

View File

@ -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
/// <typeparam name="TFormatter">The type of formatters in the collection.</typeparam>
public class FormatterCollection<TFormatter> : Collection<TFormatter>
{
/// <summary>
/// Initializes a new instance of the <see cref="FormatterCollection{TFormatter}"/> class that is empty.
/// </summary>
public FormatterCollection()
: base()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FormatterCollection{TFormatter}"/> class
/// as a wrapper for the specified list.
/// </summary>
/// <param name="list">The list that is wrapped by the new collection.</param>
public FormatterCollection(IList<TFormatter> list)
: base(list)
{
}
/// <summary>
/// Removes all formatters of the specified type.

View File

@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
private ResultExecutingContext _resultExecutingContext;
private ResultExecutedContext _resultExecutedContext;
public FilterActionInvoker(
ActionContext actionContext,
IReadOnlyList<IFilterProvider> filterProviders,
@ -334,8 +334,10 @@ namespace Microsoft.AspNet.Mvc.Controllers
var context = new ResourceExecutingContext(ActionContext, _filters);
context.InputFormatters = new CopyOnWriteList<IInputFormatter>(_inputFormatters);
context.OutputFormatters = new CopyOnWriteList<IOutputFormatter>(_outputFormatters);
context.InputFormatters = new FormatterCollection<IInputFormatter>(
new CopyOnWriteList<IInputFormatter>(_inputFormatters));
context.OutputFormatters = new FormatterCollection<IOutputFormatter>(
new CopyOnWriteList<IOutputFormatter>(_outputFormatters));
context.ModelBinders = new CopyOnWriteList<IModelBinder>(_modelBinders);
context.ValidatorProviders = new CopyOnWriteList<IModelValidatorProvider>(_modelValidatorProviders);
context.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_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)

View File

@ -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());
}
}
}

View File

@ -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<JsonInputFormatter>();
context.OutputFormatters.RemoveType<JsonOutputFormatter>();
context.InputFormatters.Add(new JsonInputFormatter(_serializerSettings));
context.OutputFormatters.Add(new JsonOutputFormatter(_serializerSettings));
}
}
}
}