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:
parent
4299a4d50b
commit
fec1268bf3
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue