From fec1268bf36be370e32c6f8f5732c6b27c6ac1c1 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 16 Mar 2016 15:07:13 -0700 Subject: [PATCH] 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. --- .../Filters/ResourceExecutingContext.cs | 30 +------ .../Internal/FilterActionInvoker.cs | 21 ++--- .../BasicTests.cs | 29 ------- .../FiltersTest.cs | 53 ------------ .../SpecificFormattersController.cs | 86 ------------------- .../Controllers/JsonOnlyController.cs | 58 ------------- 6 files changed, 10 insertions(+), 267 deletions(-) delete mode 100644 test/WebSites/BasicWebSite/Controllers/SpecificFormattersController.cs delete mode 100644 test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs index 34b96c96e3..770acb2bd2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs @@ -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 { /// - /// A context for resource filters. Allows modification of services and values used for - /// model binding. + /// A context for resource filters. /// public class ResourceExecutingContext : FilterContext { @@ -23,34 +19,14 @@ namespace Microsoft.AspNetCore.Mvc.Filters : base(actionContext, filters) { } - - /// - /// Gets or sets the list of instances used by model binding. - /// - public virtual FormatterCollection InputFormatters { get; set; } - - /// - /// Gets or sets the list of instances used by model binding. - /// - public virtual IList ModelBinders { get; set; } - + /// /// Gets or sets the result of the action to be executed. /// /// /// Setting to a non-null 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. /// public virtual IActionResult Result { get; set; } - - /// - /// Gets or sets the list of instances used by model binding. - /// - public IList ValueProviderFactories { get; set; } - - /// - /// Gets or sets the list of instances used by model binding. - /// - public IList ValidatorProviders { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterActionInvoker.cs index 3a9ab93d62..a81f0febce 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterActionInvoker.cs @@ -255,15 +255,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { _cursor.Reset(); - var context = new ResourceExecutingContext(Context, _filters); - - context.InputFormatters = new FormatterCollection( - new CopyOnWriteList(_inputFormatters)); - context.ModelBinders = new CopyOnWriteList(_modelBinders); - context.ValidatorProviders = new CopyOnWriteList(_modelValidatorProviders); - context.ValueProviderFactories = new CopyOnWriteList(_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( + new CopyOnWriteList(_inputFormatters)); + Context.ModelBinders = new CopyOnWriteList(_modelBinders); + Context.ValidatorProviders = new CopyOnWriteList(_modelValidatorProviders); var valueProviders = new List(); 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; diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs index de5b720615..516031ea03 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs @@ -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()); - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs index 9b0ba5a8fa..5d68855e51 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs @@ -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 = - "" + - "10" + - ""; - - // 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); - } } } \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Controllers/SpecificFormattersController.cs b/test/WebSites/BasicWebSite/Controllers/SpecificFormattersController.cs deleted file mode 100644 index ce802de203..0000000000 --- a/test/WebSites/BasicWebSite/Controllers/SpecificFormattersController.cs +++ /dev/null @@ -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(); - var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); - var logger = loggerFactory.CreateLogger(); - 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(); - objectResult.Formatters.Add(new JsonOutputFormatter(_serializerSettings)); - } - } - } - } -} diff --git a/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs b/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs deleted file mode 100644 index 5957e233db..0000000000 --- a/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs +++ /dev/null @@ -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() - .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>(); - var jsonFormatter = options.Value.OutputFormatters.OfType().Single(); - var result = (ObjectResult)context.Result; - result.Formatters.Add(jsonFormatter); - } - } - } - } -} \ No newline at end of file