Remove ModelBinding features from ResourceFilters

Removes all of the ModelBinding configuration support from Resource
Filters. We haven't come up with concrete scenarios around these features,
and don't want to paint ourselves into a corner with extensibility.
This commit is contained in:
Ryan Nowak 2016-03-16 15:07:13 -07:00
parent 4299a4d50b
commit fec1268bf3
6 changed files with 10 additions and 267 deletions

View File

@ -2,15 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNetCore.Mvc.Filters
{
/// <summary>
/// A context for resource filters. Allows modification of services and values used for
/// model binding.
/// A context for resource filters.
/// </summary>
public class ResourceExecutingContext : FilterContext
{
@ -23,34 +19,14 @@ namespace Microsoft.AspNetCore.Mvc.Filters
: base(actionContext, filters)
{
}
/// <summary>
/// Gets or sets the list of <see cref="IInputFormatter"/> instances used by model binding.
/// </summary>
public virtual FormatterCollection<IInputFormatter> InputFormatters { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="IModelBinder"/> instances used by model binding.
/// </summary>
public virtual IList<IModelBinder> ModelBinders { get; set; }
/// <summary>
/// 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
/// short-circuit execution of additional resource filtes and the action itself.
/// short-circuit execution of additional resource filters and the action itself.
/// </remarks>
public virtual IActionResult Result { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="IValueProviderFactory"/> instances used by model binding.
/// </summary>
public IList<IValueProviderFactory> ValueProviderFactories { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="IModelValidatorProvider"/> instances used by model binding.
/// </summary>
public IList<IModelValidatorProvider> ValidatorProviders { get; set; }
}
}

View File

@ -255,15 +255,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
_cursor.Reset();
var context = new ResourceExecutingContext(Context, _filters);
context.InputFormatters = new FormatterCollection<IInputFormatter>(
new CopyOnWriteList<IInputFormatter>(_inputFormatters));
context.ModelBinders = new CopyOnWriteList<IModelBinder>(_modelBinders);
context.ValidatorProviders = new CopyOnWriteList<IModelValidatorProvider>(_modelValidatorProviders);
context.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories);
_resourceExecutingContext = context;
_resourceExecutingContext = new ResourceExecutingContext(Context, _filters);
return InvokeResourceFilterAsync();
}
@ -362,16 +354,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
// We've reached the end of resource filters, so move to setting up state to invoke model
// binding.
Context.InputFormatters = _resourceExecutingContext.InputFormatters;
Context.ModelBinders = _resourceExecutingContext.ModelBinders;
Context.ValidatorProviders = _resourceExecutingContext.ValidatorProviders;
Context.InputFormatters = new FormatterCollection<IInputFormatter>(
new CopyOnWriteList<IInputFormatter>(_inputFormatters));
Context.ModelBinders = new CopyOnWriteList<IModelBinder>(_modelBinders);
Context.ValidatorProviders = new CopyOnWriteList<IModelValidatorProvider>(_modelValidatorProviders);
var valueProviders = new List<IValueProvider>();
var factoryContext = new ValueProviderFactoryContext(Context);
for (var i = 0; i < _resourceExecutingContext.ValueProviderFactories.Count; i++)
for (var i = 0; i < _valueProviderFactories.Count; i++)
{
var factory = _resourceExecutingContext.ValueProviderFactories[i];
var factory = _valueProviderFactories[i];
await factory.CreateValueProviderAsync(factoryContext);
}
Context.ValueProviders = factoryContext.ValueProviders;

View File

@ -350,34 +350,5 @@ namespace Microsoft.AspNetCore.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

@ -509,58 +509,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
Assert.Equal("\"someValue\"", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task ResourceFilter_ChangesOutputFormatters_JsonReturned()
{
// Arrange
var input = "{ sampleInt: 10 }";
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Json");
request.Content = new StringContent(input, Encoding.UTF8, "application/json");
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
Assert.Equal("\"10\"", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task ResourceFilter_ChangesInputFormatters_JsonAccepted()
{
// Arrange
var input = "{ sampleInt: 10 }";
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Json");
request.Content = new StringContent(input, Encoding.UTF8, "application/json");
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("\"10\"", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task ResourceFilter_ChangesInputFormatters_XmlDenied()
{
// Arrange
var input =
"<DummyClass xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite\">" +
"<SampleInt>10</SampleInt>" +
"</DummyClass>";
// 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");
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode);
}
}
}

View File

@ -1,86 +0,0 @@
// 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;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
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 BadRequest(ModelState);
}
return Ok(person);
}
[HttpPost("api/ActionUsingGlobalFormatters")]
public IActionResult ActionUsingGlobalFormatters([FromBody] Person person)
{
if (!ModelState.IsValid)
{
return BadRequest(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, IResultFilter
{
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>();
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<JsonInputFormatter>();
context.InputFormatters.Add(new JsonInputFormatter(logger ,_serializerSettings));
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
public void OnResultExecuting(ResultExecutingContext context)
{
var objectResult = context.Result as ObjectResult;
if (objectResult != null)
{
objectResult.Formatters.RemoveType<JsonOutputFormatter>();
objectResult.Formatters.Add(new JsonOutputFormatter(_serializerSettings));
}
}
}
}
}

View File

@ -1,58 +0,0 @@
// 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.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace FiltersWebSite.Controllers
{
[JsonOnly]
[Route("Json")]
public class JsonOnlyController : Controller
{
[HttpPost]
public string Post([FromBody] DummyClass dummy)
{
return (dummy?.SampleInt ?? 0).ToString();
}
private class JsonOnlyAttribute : Attribute, IResultFilter
{
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
// InputFormatters collection contains JsonInputFormatter and JsonPatchInputFormatter. Picking
// JsonInputFormatter by matching the type exactly rather than using OfType.
var jsonFormatter = context.InputFormatters.OfType<JsonInputFormatter>()
.Where(t => t.GetType() == typeof(JsonInputFormatter)).FirstOrDefault();
context.InputFormatters.Clear();
context.InputFormatters.Add(jsonFormatter);
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
public void OnResultExecuting(ResultExecutingContext context)
{
// Update the output formatter collection to only return JSON.
if (context.Result is ObjectResult)
{
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>();
var jsonFormatter = options.Value.OutputFormatters.OfType<JsonOutputFormatter>().Single();
var result = (ObjectResult)context.Result;
result.Formatters.Add(jsonFormatter);
}
}
}
}
}