From a16504b94157351dd6698a5e1f87d08b03f06c0e Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Mon, 26 Mar 2018 14:44:52 +0200 Subject: [PATCH 1/2] Added failing test for #7546 --- .../ApiBehaviorApplicationModelProvider.cs | 3 +- ...ApiBehaviorApplicationModelProviderTest.cs | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index 7bc6c6892d..adc39963a7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -139,7 +139,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal actionModel.Filters.Add(_modelStateInvalidFilter); } - private void InferParameterBindingSources(ActionModel actionModel) + // Internal for unit testing + internal void InferParameterBindingSources(ActionModel actionModel) { if (_modelMetadataProvider == null || _apiBehaviorOptions.SuppressInferBindingSourcesForParameters) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index a9a01f5aba..445706ec8a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -5,6 +5,7 @@ using System; using System.ComponentModel; using System.Linq; using System.Reflection; +using System.Threading; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -383,6 +384,31 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Same(BindingSource.Body, result); } + [Fact] + public void InferParameterBindingSources_SetsCorrectBindingSourceForComplexTypesWithCancellationToken() + { + // Arrange + var actionName = nameof(ParameterBindingController.ComplexTypeModelWithCancellationToken); + var actionModel = GetActionModel(typeof(ParameterBindingController), actionName); + + // Go through MvcOptions and MvcCoreMvcOptionsSetup to make + // sure we pick up the proper ModelMetadataDetailsProviders from the options. + var options = new MvcOptions(); + new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory()).Configure(options); + var metadataProvider = TestModelMetadataProvider.CreateProvider(options.ModelMetadataDetailsProviders); + var provider = GetProvider(modelMetadataProvider: metadataProvider); + + // Act + provider.InferParameterBindingSources(actionModel); + + // Assert + var model = GetParameterModel(actionModel); + Assert.Same(BindingSource.Body, model.BindingInfo.BindingSource); + + var cancellationToken = GetParameterModel(actionModel); + Assert.Same(BindingSource.Special, cancellationToken.BindingInfo.BindingSource); + } + [Fact] public void InferBindingSourceForParameter_ReturnsBodyForSimpleTypes() { @@ -535,6 +561,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal return Assert.Single(action.Parameters); } + private static ParameterModel GetParameterModel(ActionModel action) + { + return Assert.Single(action.Parameters.Where(x => typeof(T).IsAssignableFrom(x.ParameterType))); + } + [ApiController] [Route("TestApi")] private class TestApiController : Controller @@ -612,6 +643,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal [HttpPost] [Consumes("application/json")] public IActionResult ActionWithConsumesAttribute([FromForm] string parameter) => null; + + [HttpPut("cancellation")] + public IActionResult ComplexTypeModelWithCancellationToken(TestModel model, CancellationToken cancellationToken) => null; } [ApiController] From c515cece8ed8a39b8163c3ba35024bcc79716ca0 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 27 Mar 2018 16:53:45 -0700 Subject: [PATCH 2/2] Use ModelMetadataProvider to infer BindingSource on application model --- .../ModelBinding/BindingInfo.cs | 80 +++++++ .../ModelBinding/BindingSource.cs | 2 +- .../DefaultApiDescriptionProvider.cs | 8 +- .../DefaultApplicationModelProvider.cs | 75 +++---- .../Internal/MvcCoreMvcOptionsSetup.cs | 41 ++-- .../Metadata/DefaultModelMetadataProvider.cs | 1 - .../ModelBinding/ModelBinderFactory.cs | 18 +- .../ModelBinding/ParameterBinder.cs | 4 +- .../Properties/AssemblyInfo.cs | 1 + .../DefaultPageApplicationModelProvider.cs | 45 +++- ...ft.AspNetCore.Mvc.Abstractions.Test.csproj | 1 + .../ModelBinding/BindingInfoTest.cs | 197 ++++++++++++++++++ .../Internal/ActionSelectorTest.cs | 6 +- ...ApiBehaviorApplicationModelProviderTest.cs | 27 ++- ...thorizationApplicationModelProviderTest.cs | 53 ++--- ...ControllerActionDescriptorProviderTests.cs | 8 +- .../DefaultApplicationModelProviderTest.cs | 158 +++++++++++++- .../CorsApplicationModelProviderTest.cs | 61 ++---- .../ApiBehaviorTest.cs | 11 +- .../AuthorizeFilterIntegrationTest.cs | 20 +- ...izationPageApplicationModelProviderTest.cs | 5 +- ...DefaultPageApplicationModelProviderTest.cs | 101 ++++++--- ...CacheFilterApplicationModelProviderTest.cs | 5 +- .../MvcOptionsSetupTest.cs | 11 + .../TestModelMetadataProvider.cs | 6 +- .../TempDataApplicationModelProviderTest.cs | 21 +- .../ApiControllerActionDiscoveryTest.cs | 10 +- .../Controllers/ContactApiController.cs | 7 +- 28 files changed, 758 insertions(+), 225 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/BindingInfoTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs index d47cda1bc2..8ebd959b56 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs @@ -65,6 +65,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Constructs a new instance of from the given . + /// + /// This overload does not account for specified via . Consider using + /// overload, or + /// on the result of this method to to get a more accurate instance. + /// /// /// A collection of attributes which are used to construct /// @@ -134,6 +139,81 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding return isBindingInfoPresent ? bindingInfo : null; } + /// + /// Constructs a new instance of from the given and . + /// + /// A collection of attributes which are used to construct . + /// The . + /// A new instance of if any binding metadata was discovered; otherwise or . + public static BindingInfo GetBindingInfo(IEnumerable attributes, ModelMetadata modelMetadata) + { + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + if (modelMetadata == null) + { + throw new ArgumentNullException(nameof(modelMetadata)); + } + + var bindingInfo = GetBindingInfo(attributes); + var isBindingInfoPresent = bindingInfo != null; + + if (bindingInfo == null) + { + bindingInfo = new BindingInfo(); + } + + isBindingInfoPresent |= bindingInfo.TryApplyBindingInfo(modelMetadata); + + return isBindingInfoPresent ? bindingInfo : null; + } + + /// + /// Applies binding metadata from the specified . + /// + /// Uses values from if no value is already available. + /// + /// + /// The . + /// if any binding metadata from was applied; + /// otherwise. + public bool TryApplyBindingInfo(ModelMetadata modelMetadata) + { + if (modelMetadata == null) + { + throw new ArgumentNullException(nameof(modelMetadata)); + } + + var isBindingInfoPresent = false; + if (BinderModelName == null && modelMetadata.BinderModelName != null) + { + isBindingInfoPresent = true; + BinderModelName = modelMetadata.BinderModelName; + } + + if (BinderType == null && modelMetadata.BinderType != null) + { + isBindingInfoPresent = true; + BinderType = modelMetadata.BinderType; + } + + if (BindingSource == null && modelMetadata.BindingSource != null) + { + isBindingInfoPresent = true; + BindingSource = modelMetadata.BindingSource; + } + + if (PropertyFilterProvider == null && modelMetadata.PropertyFilterProvider != null) + { + isBindingInfoPresent = true; + PropertyFilterProvider = modelMetadata.PropertyFilterProvider; + } + + return isBindingInfoPresent; + } + private class CompositePropertyFilterProvider : IPropertyFilterProvider { private readonly IEnumerable _providers; diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs index 87791e85da..995ba57c4d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs @@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding isFromRequest: false); /// - /// A for and . + /// A for , , and . /// public static readonly BindingSource FormFile = new BindingSource( "FormFile", diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index 50734ccded..5da3f8a1cb 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -618,8 +618,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer return new ApiParameterDescriptionContext { ModelMetadata = metadata, - BinderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName, - BindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource, + BinderModelName = bindingInfo?.BinderModelName, + BindingSource = bindingInfo?.BindingSource, PropertyName = propertyName ?? metadata.Name, }; } @@ -716,9 +716,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { var propertyMetadata = modelMetadata.Properties[i]; var key = new PropertyKey(propertyMetadata, source); + var bindingInfo = BindingInfo.GetBindingInfo(Enumerable.Empty(), propertyMetadata); + var propertyContext = ApiParameterDescriptionContext.GetContext( propertyMetadata, - bindingInfo: null, + bindingInfo: bindingInfo, propertyName: null); if (Visited.Add(key)) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs index b9a3e830c7..5de286610d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApplicationModels; @@ -19,11 +18,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal { public class DefaultApplicationModelProvider : IApplicationModelProvider { - private readonly ICollection _globalFilters; + private readonly MvcOptions _mvcOptions; + private readonly IModelMetadataProvider _modelMetadataProvider; - public DefaultApplicationModelProvider(IOptions mvcOptionsAccessor) + public DefaultApplicationModelProvider( + IOptions mvcOptionsAccessor, + IModelMetadataProvider modelMetadataProvider) { - _globalFilters = mvcOptionsAccessor.Value.Filters; + _mvcOptions = mvcOptionsAccessor.Value; + _modelMetadataProvider = modelMetadataProvider; } /// @@ -37,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(context)); } - foreach (var filter in _globalFilters) + foreach (var filter in _mvcOptions.Filters) { context.Result.Filters.Add(filter); } @@ -118,9 +121,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal do { routeAttributes = currentTypeInfo - .GetCustomAttributes(inherit: false) - .OfType() - .ToArray(); + .GetCustomAttributes(inherit: false) + .OfType() + .ToArray(); if (routeAttributes.Length > 0) { @@ -213,24 +216,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(propertyInfo)); } - // CoreCLR returns IEnumerable from GetCustomAttributes - the OfType - // is needed to so that the result of ToArray() is object var attributes = propertyInfo.GetCustomAttributes(inherit: true); - var propertyModel = new PropertyModel(propertyInfo, attributes); - var bindingInfo = BindingInfo.GetBindingInfo(attributes); - if (bindingInfo != null) - { - propertyModel.BindingInfo = bindingInfo; - } - else if (IsFormFileType(propertyInfo.PropertyType)) - { - propertyModel.BindingInfo = new BindingInfo - { - BindingSource = BindingSource.FormFile, - }; - } - propertyModel.PropertyName = propertyInfo.Name; + + var modelMetadata = _modelMetadataProvider.GetMetadataForProperty(propertyInfo.DeclaringType, propertyInfo.Name); + var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + + var propertyModel = new PropertyModel(propertyInfo, attributes) + { + PropertyName = propertyInfo.Name, + BindingInfo = bindingInfo, + }; return propertyModel; } @@ -433,25 +429,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(parameterInfo)); } - // CoreCLR returns IEnumerable from GetCustomAttributes - the OfType - // is needed to so that the result of ToArray() is object var attributes = parameterInfo.GetCustomAttributes(inherit: true); - var parameterModel = new ParameterModel(parameterInfo, attributes); - var bindingInfo = BindingInfo.GetBindingInfo(attributes); - if (bindingInfo != null) + BindingInfo bindingInfo; + if (_mvcOptions.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase) { - parameterModel.BindingInfo = bindingInfo; + var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameterInfo); + bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); } - else if (IsFormFileType(parameterInfo.ParameterType)) + else { - parameterModel.BindingInfo = new BindingInfo - { - BindingSource = BindingSource.FormFile, - }; + // GetMetadataForParameter should only be used if the user has opted in to the 2.1 behavior. + bindingInfo = BindingInfo.GetBindingInfo(attributes); } - parameterModel.ParameterName = parameterInfo.Name; + var parameterModel = new ParameterModel(parameterInfo, attributes) + { + ParameterName = parameterInfo.Name, + BindingInfo = bindingInfo, + }; return parameterModel; } @@ -670,12 +666,5 @@ namespace Microsoft.AspNetCore.Mvc.Internal list.Add(item); } } - - private static bool IsFormFileType(Type parameterType) - { - return parameterType == typeof(IFormFile) || - parameterType == typeof(IFormFileCollection) || - typeof(IEnumerable).IsAssignableFrom(parameterType); - } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs index d33813cd1e..f03e2ca661 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Threading; using Microsoft.AspNetCore.Http; @@ -76,28 +77,34 @@ namespace Microsoft.AspNetCore.Mvc.Internal options.ValueProviderFactories.Add(new JQueryFormValueProviderFactory()); // Set up metadata providers - - // Don't bind the Type class by default as it's expensive. A user can override this behavior - // by altering the collection of providers. - options.ModelMetadataDetailsProviders.Add(new ExcludeBindingMetadataProvider(typeof(Type))); - - options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider()); - options.ModelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider()); - - options.ModelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(CancellationToken), BindingSource.Special)); - options.ModelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFile), BindingSource.FormFile)); - options.ModelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormCollection), BindingSource.FormFile)); + ConfigureAdditionalModelMetadataDetailsProvider(options.ModelMetadataDetailsProviders); // Set up validators options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider()); + } + + internal static void ConfigureAdditionalModelMetadataDetailsProvider(IList modelMetadataDetailsProviders) + { + // Don't bind the Type class by default as it's expensive. A user can override this behavior + // by altering the collection of providers. + modelMetadataDetailsProviders.Add(new ExcludeBindingMetadataProvider(typeof(Type))); + + modelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider()); + modelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider()); + + modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(CancellationToken), BindingSource.Special)); + modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFile), BindingSource.FormFile)); + modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormCollection), BindingSource.FormFile)); + modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFileCollection), BindingSource.FormFile)); // Add types to be excluded from Validation - options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Type))); - options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Uri))); - options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(CancellationToken))); - options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IFormFile))); - options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IFormCollection))); - options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Stream))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Type))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Uri))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(CancellationToken))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IFormFile))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IFormCollection))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IFormFileCollection))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Stream))); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs index e71563e60a..5b79143002 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; -using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Options; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs index 82f74e6e58..216fc7c803 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs @@ -233,14 +233,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { _factory = factory; Metadata = factoryContext.Metadata; - BindingInfo = new BindingInfo + BindingInfo bindingInfo; + if (factoryContext.BindingInfo != null) { - BinderModelName = factoryContext.BindingInfo?.BinderModelName ?? Metadata.BinderModelName, - BinderType = factoryContext.BindingInfo?.BinderType ?? Metadata.BinderType, - BindingSource = factoryContext.BindingInfo?.BindingSource ?? Metadata.BindingSource, - PropertyFilterProvider = - factoryContext.BindingInfo?.PropertyFilterProvider ?? Metadata.PropertyFilterProvider, - }; + bindingInfo = new BindingInfo(factoryContext.BindingInfo); + } + else + { + bindingInfo = new BindingInfo(); + } + + bindingInfo.TryApplyBindingInfo(Metadata); + BindingInfo = bindingInfo; MetadataProvider = _factory._metadataProvider; Visited = new Dictionary(); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs index 0662d57a79..90a2458f2a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs @@ -310,10 +310,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding // This is to avoid adding validation errors for an 'empty' prefix when a simple // type fails to bind. The fix for #7503 uncovered this issue, and was likely the // original problem being worked around that regressed #7503. - string modelName = modelBindingContext.ModelName; + var modelName = modelBindingContext.ModelName; if (string.IsNullOrEmpty(modelBindingContext.ModelName) && - (parameter.BindingInfo?.BinderModelName ?? metadata.BinderModelName) == null) + parameter.BindingInfo?.BinderModelName == null) { // If we get here then this is a fallback case. The model name wasn't explicitly set // and we ended up with an empty prefix. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs index df50c7d842..abaed09181 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs @@ -5,5 +5,6 @@ using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Mvc.Formatters; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: TypeForwardedTo(typeof(InputFormatterException))] diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs index 0db3f1e45d..138c14cbe8 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { @@ -17,6 +18,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private const string ModelPropertyName = "Model"; private readonly PageHandlerPageFilter _pageHandlerPageFilter = new PageHandlerPageFilter(); private readonly PageHandlerResultFilter _pageHandlerResultFilter = new PageHandlerResultFilter(); + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly MvcOptions _options; + + public DefaultPageApplicationModelProvider( + IModelMetadataProvider modelMetadataProvider, + IOptions options) + { + _modelMetadataProvider = modelMetadataProvider; + _options = options.Value; + } /// public int Order => -1000; @@ -217,9 +228,22 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal throw new ArgumentNullException(nameof(parameter)); } - return new PageParameterModel(parameter, parameter.GetCustomAttributes(inherit: true)) + var attributes = parameter.GetCustomAttributes(inherit: true); + + BindingInfo bindingInfo; + if (_options.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase) { - BindingInfo = BindingInfo.GetBindingInfo(parameter.GetCustomAttributes()), + var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameter); + bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + } + else + { + bindingInfo = BindingInfo.GetBindingInfo(attributes); + } + + return new PageParameterModel(parameter, attributes) + { + BindingInfo = bindingInfo, ParameterName = parameter.Name, }; } @@ -237,12 +261,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal } var propertyAttributes = property.GetCustomAttributes(inherit: true); - var handlerAttributes = property.DeclaringType.GetCustomAttributes(inherit: true); - // Look for binding info on the handler if nothing is specified on the property. - // This allows BindProperty attributes on handlers to apply to properties. - var bindingInfo = BindingInfo.GetBindingInfo(propertyAttributes) ?? - BindingInfo.GetBindingInfo(handlerAttributes); + var propertyMetadata = _modelMetadataProvider.GetMetadataForProperty(property.DeclaringType, property.Name); + var bindingInfo = BindingInfo.GetBindingInfo(propertyAttributes, propertyMetadata); + + if (bindingInfo == null) + { + // Look for binding info on the handler if nothing is specified on the property. + // This allows BindProperty attributes on handlers to apply to properties. + var handlerType = property.DeclaringType; + var handlerAttributes = handlerType.GetCustomAttributes(inherit: true); + var handlerMetadata = _modelMetadataProvider.GetMetadataForType(property.DeclaringType); + bindingInfo = BindingInfo.GetBindingInfo(handlerAttributes, handlerMetadata); + } var model = new PagePropertyModel(property, propertyAttributes) { diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj index 998eb7e89f..1ca6c1dee7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj @@ -6,6 +6,7 @@ + diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/BindingInfoTest.cs b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/BindingInfoTest.cs new file mode 100644 index 0000000000..3c83f948d8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/BindingInfoTest.cs @@ -0,0 +1,197 @@ +// 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 Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + public class BindingInfoTest + { + [Fact] + public void GetBindingInfo_WithAttributes_ConstructsBindingInfo() + { + // Arrange + var attributes = new object[] + { + new FromQueryAttribute { Name = "Test" }, + }; + + // Act + var bindingInfo = BindingInfo.GetBindingInfo(attributes); + + // Assert + Assert.NotNull(bindingInfo); + Assert.Same("Test", bindingInfo.BinderModelName); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + } + + [Fact] + public void GetBindingInfo_ReadsPropertyPredicateProvider() + { + // Arrange + var bindAttribute = new BindAttribute(include: "SomeProperty"); + var attributes = new object[] + { + bindAttribute, + }; + + // Act + var bindingInfo = BindingInfo.GetBindingInfo(attributes); + + // Assert + Assert.NotNull(bindingInfo); + Assert.Same(bindAttribute, bindingInfo.PropertyFilterProvider); + } + + [Fact] + public void GetBindingInfo_ReadsRequestPredicateProvider() + { + // Arrange + var attributes = new object[] + { + new BindPropertyAttribute { Name = "PropertyPrefix", SupportsGet = true, }, + }; + + // Act + var bindingInfo = BindingInfo.GetBindingInfo(attributes); + + // Assert + Assert.NotNull(bindingInfo); + Assert.Same("PropertyPrefix", bindingInfo.BinderModelName); + Assert.NotNull(bindingInfo.RequestPredicate); + } + + [Fact] + public void GetBindingInfo_ReturnsNull_IfNoBindingAttributesArePresent() + { + // Arrange + var attributes = new object[] { new ControllerAttribute(), new BindNeverAttribute(), }; + + // Act + var bindingInfo = BindingInfo.GetBindingInfo(attributes); + + // Assert + Assert.Null(bindingInfo); + } + + [Fact] + public void GetBindingInfo_WithAttributesAndModelMetadata_UsesValuesFromBindingInfo_IfAttributesPresent() + { + // Arrange + var attributes = new object[] + { + new ModelBinderAttribute { BinderType = typeof(object), Name = "Test" }, + }; + var modelType = typeof(Guid); + var provider = new TestModelMetadataProvider(); + provider.ForType(modelType).BindingDetails(metadata => + { + metadata.BindingSource = BindingSource.Special; + metadata.BinderType = typeof(string); + metadata.BinderModelName = "Different"; + }); + var modelMetadata = provider.GetMetadataForType(modelType); + + // Act + var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + + // Assert + Assert.NotNull(bindingInfo); + Assert.Same(typeof(object), bindingInfo.BinderType); + Assert.Same("Test", bindingInfo.BinderModelName); + } + + [Fact] + public void GetBindingInfo_WithAttributesAndModelMetadata_UsesBinderNameFromModelMetadata_WhenNotFoundViaAttributes() + { + // Arrange + var attributes = new object[] { new ModelBinderAttribute(typeof(object)), new ControllerAttribute(), new BindNeverAttribute(), }; + var modelType = typeof(Guid); + var provider = new TestModelMetadataProvider(); + provider.ForType(modelType).BindingDetails(metadata => + { + metadata.BindingSource = BindingSource.Special; + metadata.BinderType = typeof(string); + metadata.BinderModelName = "Different"; + }); + var modelMetadata = provider.GetMetadataForType(modelType); + + // Act + var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + + // Assert + Assert.NotNull(bindingInfo); + Assert.Same(typeof(object), bindingInfo.BinderType); + Assert.Same("Different", bindingInfo.BinderModelName); + Assert.Same(BindingSource.Custom, bindingInfo.BindingSource); + } + + [Fact] + public void GetBindingInfo_WithAttributesAndModelMetadata_UsesModelBinderFromModelMetadata_WhenNotFoundViaAttributes() + { + // Arrange + var attributes = new object[] { new ControllerAttribute(), new BindNeverAttribute(), }; + var modelType = typeof(Guid); + var provider = new TestModelMetadataProvider(); + provider.ForType(modelType).BindingDetails(metadata => + { + metadata.BinderType = typeof(string); + }); + var modelMetadata = provider.GetMetadataForType(modelType); + + // Act + var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + + // Assert + Assert.NotNull(bindingInfo); + Assert.Same(typeof(string), bindingInfo.BinderType); + } + + [Fact] + public void GetBindingInfo_WithAttributesAndModelMetadata_UsesBinderSourceFromModelMetadata_WhenNotFoundViaAttributes() + { + // Arrange + var attributes = new object[] { new BindPropertyAttribute(), new ControllerAttribute(), new BindNeverAttribute(), }; + var modelType = typeof(Guid); + var provider = new TestModelMetadataProvider(); + provider.ForType(modelType).BindingDetails(metadata => + { + metadata.BindingSource = BindingSource.Services; + }); + var modelMetadata = provider.GetMetadataForType(modelType); + + // Act + var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + + // Assert + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Services, bindingInfo.BindingSource); + } + + [Fact] + public void GetBindingInfo_WithAttributesAndModelMetadata_UsesPropertyPredicateProviderFromModelMetadata_WhenNotFoundViaAttributes() + { + // Arrange + var attributes = new object[] { new ModelBinderAttribute(typeof(object)), new ControllerAttribute(), new BindNeverAttribute(), }; + var modelAttributes = new ModelAttributes(Enumerable.Empty(), null, null); + var propertyFilterProvider = Mock.Of(); + var modelType = typeof(Guid); + var provider = new TestModelMetadataProvider(); + provider.ForType(modelType).BindingDetails(metadata => + { + metadata.PropertyFilterProvider = propertyFilterProvider; + }); + var modelMetadata = provider.GetMetadataForType(modelType); + + // Act + var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + + // Assert + Assert.NotNull(bindingInfo); + Assert.Same(propertyFilterProvider, bindingInfo.PropertyFilterProvider); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs index 6076c380dd..d58a50f6b9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; @@ -790,7 +791,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure var manager = GetApplicationManager(controllerTypes); - var modelProvider = new DefaultApplicationModelProvider(options); + var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); var provider = new ControllerActionDescriptorProvider( manager, @@ -964,8 +965,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure { foreach (var item in context.Results) { - var marker = item.Metadata as BooleanConstraintMarker; - if (marker != null) + if (item.Metadata is BooleanConstraintMarker marker) { Assert.Null(item.Constraint); item.Constraint = new BooleanConstraint() { Pass = marker.Pass }; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index 445706ec8a..1fae855ed9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -389,14 +389,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal { // Arrange var actionName = nameof(ParameterBindingController.ComplexTypeModelWithCancellationToken); - var actionModel = GetActionModel(typeof(ParameterBindingController), actionName); - // Go through MvcOptions and MvcCoreMvcOptionsSetup to make - // sure we pick up the proper ModelMetadataDetailsProviders from the options. - var options = new MvcOptions(); - new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory()).Configure(options); - var metadataProvider = TestModelMetadataProvider.CreateProvider(options.ModelMetadataDetailsProviders); - var provider = GetProvider(modelMetadataProvider: metadataProvider); + // Use the default set of ModelMetadataProviders so we get metadata details for CancellationToken. + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var controllerModel = Assert.Single(context.Result.Controllers); + var actionModel = Assert.Single(controllerModel.Actions, m => m.ActionName == actionName); + + var provider = GetProvider(); // Act provider.InferParameterBindingSources(actionModel); @@ -529,16 +529,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal }; var optionsAccessor = Options.Create(options); - modelMetadataProvider = modelMetadataProvider ?? new TestModelMetadataProvider(); var loggerFactory = NullLoggerFactory.Instance; - + modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider(); return new ApiBehaviorApplicationModelProvider(optionsAccessor, modelMetadataProvider, loggerFactory); } - private static ApplicationModelProviderContext GetContext(Type type) + private static ApplicationModelProviderContext GetContext( + Type type, + IModelMetadataProvider modelMetadataProvider = null) { var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); - new DefaultApplicationModelProvider(Options.Create(new MvcOptions())).OnProvidersExecuting(context); + var mvcOptions = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }); + modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider(); + var provider = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider); + provider.OnProvidersExecuting(context); + return context; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs index a3ff38646f..5015aaa56a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs @@ -1,7 +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; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -22,10 +23,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal { // Arrange var provider = new AuthorizationApplicationModelProvider(new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()))); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(AccountController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var controllerType = typeof(AccountController); + var context = CreateProviderContext(controllerType); // Act provider.OnProvidersExecuting(context); @@ -44,10 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal options.Value.AddPolicy("Derived", policy => policy.RequireClaim("Derived")); var provider = new AuthorizationApplicationModelProvider(new DefaultAuthorizationPolicyProvider(options)); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(DerivedController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = CreateProviderContext(typeof(DerivedController)); // Act provider.OnProvidersExecuting(context); @@ -71,10 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { // Arrange var provider = new AuthorizationApplicationModelProvider(new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()))); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(AnonymousController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = CreateProviderContext(typeof(AnonymousController)); // Act provider.OnProvidersExecuting(context); @@ -100,10 +93,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal var policyProvider = new DefaultAuthorizationPolicyProvider(authOptions); var provider = new AuthorizationApplicationModelProvider(policyProvider); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); + var context = CreateProviderContext(typeof(BaseController)); // Act - var action = GetBaseControllerActionModel(provider, defaultProvider); + var action = GetBaseControllerActionModel(provider); // Assert var authorizationFilter = Assert.IsType(Assert.Single(action.Filters)); @@ -128,10 +121,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal .Verifiable(); var provider = new AuthorizationApplicationModelProvider(authorizationPolicyProviderMock.Object); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); // Act - var action = GetBaseControllerActionModel(provider, defaultProvider); + var action = GetBaseControllerActionModel(provider); // Assert var actionFilter = Assert.IsType(Assert.Single(action.Filters)); @@ -148,10 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal new DefaultAuthorizationPolicyProvider( Options.Create(new AuthorizationOptions()) )); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(NoAuthController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = CreateProviderContext(typeof(NoAuthController)); // Act provider.OnProvidersExecuting(context); @@ -163,16 +152,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Empty(action.Filters); } - private ActionModel GetBaseControllerActionModel( - IApplicationModelProvider authorizationApplicationModelProvider, - IApplicationModelProvider applicationModelProvider) + private ActionModel GetBaseControllerActionModel(AuthorizationApplicationModelProvider authorizationApplicationModelProvider) { - var context = new ApplicationModelProviderContext(new[] { typeof(BaseController).GetTypeInfo() }); - applicationModelProvider.OnProvidersExecuting(context); - var authorizeData = new List - { - new AuthorizeAttribute("POLICY") - }; + var context = CreateProviderContext(typeof(BaseController)); authorizationApplicationModelProvider.OnProvidersExecuting(context); @@ -183,6 +165,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal return action; } + private static ApplicationModelProviderContext CreateProviderContext(Type controllerType) + { + var defaultProvider = new DefaultApplicationModelProvider( + Options.Create(new MvcOptions()), + TestModelMetadataProvider.CreateDefaultProvider()); + + var context = new ApplicationModelProviderContext(new[] { controllerType.GetTypeInfo() }); + defaultProvider.OnProvidersExecuting(context); + return context; + } + private class BaseController { [Authorize(Policy = "Base")] diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs index 7e3ba9bd38..0afae5ae78 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs @@ -709,7 +709,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var manager = GetApplicationManager(new[] { controllerTypeInfo }); var options = Options.Create(new MvcOptions()); options.Value.Conventions.Add(new TestRoutingConvention()); - var modelProvider = new DefaultApplicationModelProvider(options); + var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); var provider = new ControllerActionDescriptorProvider( manager, new[] { modelProvider }, @@ -1397,7 +1397,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var manager = GetApplicationManager(new[] { controllerTypeInfo }); - var modelProvider = new DefaultApplicationModelProvider(options); + var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); var provider = new ControllerActionDescriptorProvider( manager, @@ -1413,7 +1413,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var options = Options.Create(new MvcOptions()); var manager = GetApplicationManager(controllerTypeInfos); - var modelProvider = new DefaultApplicationModelProvider(options); + var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); var provider = new ControllerActionDescriptorProvider( manager, @@ -1432,7 +1432,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var manager = GetApplicationManager(new[] { controllerTypeInfo }); - var modelProvider = new DefaultApplicationModelProvider(options); + var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); var provider = new ControllerActionDescriptorProvider( manager, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs index 5605b698ac..90a038b4c7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.Options; using Xunit; @@ -74,6 +75,49 @@ namespace Microsoft.AspNetCore.Mvc.Internal }); } + [Fact] + public void OnProvidersExecuting_ReadsBindingSourceForPropertiesFromModelMetadata() + { + // Arrange + var detailsProvider = new BindingSourceMetadataProvider(typeof(string), BindingSource.Services); + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(new[] { detailsProvider }); + var typeInfo = typeof(ModelBinderController).GetTypeInfo(); + var provider = new TestApplicationModelProvider(Options.Create(new MvcOptions()), modelMetadataProvider); + + var context = new ApplicationModelProviderContext(new[] { typeInfo }); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controllerModel = Assert.Single(context.Result.Controllers); + Assert.Collection( + controllerModel.ControllerProperties.OrderBy(p => p.PropertyName), + property => + { + Assert.Equal(nameof(ModelBinderController.Bound), property.PropertyName); + Assert.Equal(BindingSource.Query, property.BindingInfo.BindingSource); + Assert.Same(controllerModel, property.Controller); + + var attribute = Assert.Single(property.Attributes); + Assert.IsType(attribute); + }, + property => + { + Assert.Equal(nameof(ModelBinderController.FormFile), property.PropertyName); + Assert.Equal(BindingSource.FormFile, property.BindingInfo.BindingSource); + Assert.Same(controllerModel, property.Controller); + + Assert.Empty(property.Attributes); + }, + property => + { + Assert.Equal(nameof(ModelBinderController.Unbound), property.PropertyName); + Assert.Equal(BindingSource.Services, property.BindingInfo.BindingSource); + Assert.Same(controllerModel, property.Controller); + }); + } + [Fact] public void OnProvidersExecuting_AddsBindingSources_ForActionParameters() { @@ -88,7 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var controllerModel = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controllerModel.Actions); + var action = Assert.Single(controllerModel.Actions, a => a.ActionMethod.Name == nameof(ModelBinderController.PostAction)); Assert.Collection( action.Parameters, parameter => @@ -116,6 +160,107 @@ namespace Microsoft.AspNetCore.Mvc.Internal }); } + [Fact] + public void OnProvidersExecuting_AddsBindingSources_ForActionParameters_WithLegacyValidationBehavior() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var options = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = false }); + var builder = new TestApplicationModelProvider(options, modelMetadataProvider); + var typeInfo = typeof(ModelBinderController).GetTypeInfo(); + + var context = new ApplicationModelProviderContext(new[] { typeInfo }); + + // Act + builder.OnProvidersExecuting(context); + + // Assert + var controllerModel = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controllerModel.Actions, a => a.ActionMethod.Name == nameof(ModelBinderController.PostAction)); + Assert.Collection( + action.Parameters, + parameter => + { + Assert.Equal("fromQuery", parameter.ParameterName); + Assert.Equal(BindingSource.Query, parameter.BindingInfo.BindingSource); + Assert.Same(action, parameter.Action); + + var attribute = Assert.Single(parameter.Attributes); + Assert.IsType(attribute); + }, + parameter => + { + Assert.Equal("formFileCollection", parameter.ParameterName); + // BindingSource for IFormFileCollection comes from ModelMetadata which we are not using here. + Assert.Null(parameter.BindingInfo); + Assert.Same(action, parameter.Action); + + Assert.Empty(parameter.Attributes); + }, + parameter => + { + Assert.Equal("unbound", parameter.ParameterName); + Assert.Null(parameter.BindingInfo); + Assert.Same(action, parameter.Action); + }); + } + + [Fact] + public void OnProvidersExecuting_AddsBindingSources_ForActionParameters_ReadFromModelMetadata() + { + // Arrange + var options = new MvcOptions { AllowValidatingTopLevelNodes = true }; + var detailsProvider = new BindingSourceMetadataProvider(typeof(Guid), BindingSource.Special); + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(new[] { detailsProvider }); + + var provider = new TestApplicationModelProvider(Options.Create(options), modelMetadataProvider); + var typeInfo = typeof(ModelBinderController).GetTypeInfo(); + + var context = new ApplicationModelProviderContext(new[] { typeInfo }); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controllerModel = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controllerModel.Actions, a => a.ActionName == nameof(ModelBinderController.PostAction1)); + Assert.Collection( + action.Parameters, + parameter => + { + Assert.Equal("guid", parameter.ParameterName); + Assert.Equal(BindingSource.Special, parameter.BindingInfo.BindingSource); + }); + } + + [Fact] + public void OnProvidersExecuting_UsesBindingSourceSpecifiedOnParameter() + { + // Arrange + var options = new MvcOptions(); + var detailsProvider = new BindingSourceMetadataProvider(typeof(Guid), BindingSource.Special); + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(new[] { detailsProvider }); + + var provider = new TestApplicationModelProvider(Options.Create(options), modelMetadataProvider); + var typeInfo = typeof(ModelBinderController).GetTypeInfo(); + + var context = new ApplicationModelProviderContext(new[] { typeInfo }); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controllerModel = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controllerModel.Actions, a => a.ActionName == nameof(ModelBinderController.PostAction2)); + Assert.Collection( + action.Parameters, + parameter => + { + Assert.Equal("fromQuery", parameter.ParameterName); + Assert.Equal(BindingSource.Query, parameter.BindingInfo.BindingSource); + }); + } + // This class has a filter attribute, but doesn't implement any filter interfaces, // so ControllerFilter is not present. [Fact] @@ -1363,6 +1508,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal public IFormFile FormFile { get; set; } public IActionResult PostAction([FromQuery] string fromQuery, IFormFileCollection formFileCollection, string unbound) => null; + + public IActionResult PostAction1(Guid guid) => null; + + public IActionResult PostAction2([FromQuery] Guid fromQuery) => null; } public class SomeFiltersController : IAsyncActionFilter, IResultFilter @@ -1404,13 +1553,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal private class TestApplicationModelProvider : DefaultApplicationModelProvider { public TestApplicationModelProvider() - : this(Options.Create(new MvcOptions())) + : this(Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }), TestModelMetadataProvider.CreateDefaultProvider()) { } public TestApplicationModelProvider( - IOptions options) - : base(options) + IOptions options, + IModelMetadataProvider modelMetadataProvider) + : base(options, modelMetadataProvider) { } diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs index fb3eecb51a..3b9ab90213 100644 --- a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; @@ -23,10 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new [] { typeof(CorsController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetProviderContext(typeof(CorsController)); // Act corsProvider.OnProvidersExecuting(context); @@ -45,10 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(DisableCorsController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetProviderContext(typeof(DisableCorsController)); // Act corsProvider.OnProvidersExecuting(context); @@ -67,10 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(CustomCorsFilterController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetProviderContext(typeof(CustomCorsFilterController)); // Act corsProvider.OnProvidersExecuting(context); @@ -88,10 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(EnableCorsController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetProviderContext(typeof(EnableCorsController)); // Act corsProvider.OnProvidersExecuting(context); @@ -110,10 +99,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(DisableCorsActionController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetProviderContext(typeof(DisableCorsActionController)); // Act corsProvider.OnProvidersExecuting(context); @@ -132,10 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(CustomCorsFilterOnActionController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetProviderContext(typeof(CustomCorsFilterOnActionController)); // Act corsProvider.OnProvidersExecuting(context); @@ -153,12 +136,10 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); + var context = GetProviderContext(typeof(RegularController)); - var context = new ApplicationModelProviderContext(new[] { typeof(RegularController).GetTypeInfo() }); context.Result.Filters.Add( new CorsAuthorizationFilter(Mock.Of(), Mock.Of(), Mock.Of())); - defaultProvider.OnProvidersExecuting(context); // Act corsProvider.OnProvidersExecuting(context); @@ -176,11 +157,8 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(RegularController).GetTypeInfo() }); + var context = GetProviderContext(typeof(RegularController)); context.Result.Filters.Add(new DisableCorsAuthorizationFilter()); - defaultProvider.OnProvidersExecuting(context); // Act corsProvider.OnProvidersExecuting(context); @@ -198,11 +176,8 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(RegularController).GetTypeInfo() }); + var context = GetProviderContext(typeof(RegularController)); context.Result.Filters.Add(new CustomCorsFilterAttribute()); - defaultProvider.OnProvidersExecuting(context); // Act corsProvider.OnProvidersExecuting(context); @@ -220,10 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal { // Arrange var corsProvider = new CorsApplicationModelProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - var context = new ApplicationModelProviderContext(new[] { typeof(RegularController).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetProviderContext(typeof(RegularController)); // Act corsProvider.OnProvidersExecuting(context); @@ -236,6 +208,17 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal Assert.IsNotType(constraint); } + private static ApplicationModelProviderContext GetProviderContext(Type controllerType) + { + var context = new ApplicationModelProviderContext(new[] { controllerType.GetTypeInfo() }); + var provider = new DefaultApplicationModelProvider( + Options.Create(new MvcOptions()), + TestModelMetadataProvider.CreateDefaultProvider()); + provider.OnProvidersExecuting(context); + + return context; + } + private class EnableCorsController { [EnableCors("policy")] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs index 78e1d637c6..3b5d399c2b 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs @@ -88,7 +88,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public async Task ActionsWithApiBehavior_InferFromBodyParameters() + public Task ActionsWithApiBehavior_InferFromBodyParameters() + => ActionsWithApiBehaviorInferFromBodyParameters("ActionWithInferredFromBodyParameter"); + + [Fact] + public Task ActionsWithApiBehavior_InferFromBodyParameters_DoNotConsiderCancellationTokenSourceParameter() + => ActionsWithApiBehaviorInferFromBodyParameters("ActionWithInferredFromBodyParameterAndCancellationToken"); + + private async Task ActionsWithApiBehaviorInferFromBodyParameters(string action) { // Arrange var input = new Contact @@ -98,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }; // Act - var response = await Client.PostAsJsonAsync("/contact/ActionWithInferredFromBodyParameter", input); + var response = await Client.PostAsJsonAsync($"/contact/{action}", input); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs index 31dcf75a53..f19a67d45b 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs @@ -13,11 +13,12 @@ using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.Options; using Xunit; namespace Microsoft.AspNetCore.Mvc.IntegrationTests @@ -31,13 +32,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task AuthorizeFilter_CalledTwiceWithNonDefaultProvider() { // Arrange - var applicationModelProviderContext = new ApplicationModelProviderContext( - new[] { typeof(AuthorizeController).GetTypeInfo() }); + var applicationModelProviderContext = GetProviderContext(typeof(AuthorizeController)); var policyProvider = new TestAuthorizationPolicyProvider(); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); - - defaultProvider.OnProvidersExecuting(applicationModelProviderContext); var controller = Assert.Single(applicationModelProviderContext.Result.Controllers); var action = Assert.Single(controller.Actions); @@ -64,6 +61,17 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests return httpContext; } + private static ApplicationModelProviderContext GetProviderContext(Type controllerType) + { + var context = new ApplicationModelProviderContext(new[] { controllerType.GetTypeInfo() }); + var provider = new DefaultApplicationModelProvider( + Options.Create(new MvcOptions()), + TestModelMetadataProvider.CreateDefaultProvider()); + provider.OnProvidersExecuting(context); + + return context; + } + private static IServiceProvider GetServices() { var serviceCollection = new ServiceCollection(); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs index ecaad15494..85b51820df 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Options; using Xunit; @@ -160,7 +161,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private static PageApplicationModelProviderContext GetApplicationProviderContext(TypeInfo typeInfo) { - var defaultProvider = new DefaultPageApplicationModelProvider(); + var defaultProvider = new DefaultPageApplicationModelProvider( + TestModelMetadataProvider.CreateDefaultProvider(), + Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true })); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); defaultProvider.OnProvidersExecuting(context); return context; diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs index 196cf7f2eb..99aeded947 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.Extensions.Options; using Xunit; namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal @@ -21,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_ThrowsIfPageDoesNotDeriveFromValidBaseType() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(InvalidPageWithWrongBaseClass).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); @@ -62,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_ThrowsIfModelPropertyDoesNotExistOnPage() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithoutModelProperty).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); @@ -85,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_ThrowsIfModelPropertyIsNotPublic() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithNonVisibleModel).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); @@ -110,7 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_ThrowsIfModelPropertyIsStatic() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithStaticModel).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); @@ -135,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DiscoversPropertiesFromPage_IfModelTypeDoesNotHaveAttribute() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithModelWithoutPageModelAttribute).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); @@ -189,7 +190,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DiscoversPropertiesFromPageModel_IfModelHasAttribute() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithModelWithPageModelAttribute).GetTypeInfo(); var modelType = typeof(ModelWithPageModelAttribute); var descriptor = new PageActionDescriptor(); @@ -233,7 +234,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DiscoversProperties_FromAllSubTypesThatDeclaresBindProperty() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(BindPropertyAttributeOnBaseModelPage).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); @@ -287,7 +288,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DiscoversHandlersFromPage() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithModelWithoutHandlers).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); @@ -329,7 +330,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DiscoversPropertiesFromModel() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithModel).GetTypeInfo(); var modelType = typeof(TestPageModel); var descriptor = new PageActionDescriptor(); @@ -363,7 +364,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DiscoversBindingInfoFromHandler() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithBindPropertyModel).GetTypeInfo(); var modelType = typeof(ModelWithBindProperty); var descriptor = new PageActionDescriptor(); @@ -410,7 +411,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DiscoversHandlersFromModel() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithModel).GetTypeInfo(); var modelType = typeof(TestPageModel); var descriptor = new PageActionDescriptor(); @@ -438,7 +439,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_EmptyPage() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(EmptyPage).GetTypeInfo(); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); @@ -459,7 +460,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_EmptyPageModel() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(EmptyPageWithPageModel).GetTypeInfo(); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); @@ -528,7 +529,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void CreateDescriptor_FindsHandlerMethod_OnModel() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithHandlerThatGetsIgnored).GetTypeInfo(); var modelType = typeof(ModelWithHandler); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); @@ -575,7 +576,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_FindsHandlerMethodOnPage_WhenModelIsNotAnnotatedWithPageModelAttribute() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithHandler).GetTypeInfo(); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); @@ -626,7 +627,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateHandlerMethods_DiscoversHandlersFromBaseType() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(InheritsMethods).GetTypeInfo(); var baseType = typeof(TestSetPageModel); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -677,7 +678,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateHandlerMethods_IgnoresNonPublicMethods() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(ProtectedModel).GetTypeInfo(); var baseType = typeof(TestSetPageModel); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -705,7 +706,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateHandlerMethods_IgnoreGenericTypeParameters() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(GenericClassModel).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -728,7 +729,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateHandlerMethods_IgnoresStaticMethods() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageModelWithStaticHandler).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageModelWithStaticHandler.OnGet), BindingFlags.Public | BindingFlags.Instance); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -758,7 +759,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateHandlerMethods_IgnoresAbstractMethods() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageModelWithAbstractMethod).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageModelWithAbstractMethod.OnGet), BindingFlags.Public | BindingFlags.Instance); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -786,7 +787,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateHandlerMethods_IgnoresMethodWithNonHandlerAttribute() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageWithNonHandlerMethod).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageWithNonHandlerMethod.OnGet), BindingFlags.Public | BindingFlags.Instance); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -817,7 +818,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void CreateHandlerModel_ParsesMethod() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(PageModelWithHandlerNames).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -851,7 +852,42 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void CreateHandlerMethods_AddsParameterDescriptors() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); + var typeInfo = typeof(PageWithHandlerParameters).GetTypeInfo(); + var expected = typeInfo.GetMethod(nameof(PageWithHandlerParameters.OnPost)); + var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); + + // Act + provider.PopulateHandlerMethods(pageModel); + + // Assert + var handlerMethods = pageModel.HandlerMethods; + var handler = Assert.Single(handlerMethods); + + Assert.Collection( + handler.Parameters, + p => + { + Assert.NotNull(p.ParameterInfo); + Assert.Equal(typeof(string), p.ParameterInfo.ParameterType); + Assert.Equal("name", p.ParameterName); + }, + p => + { + Assert.NotNull(p.ParameterInfo); + Assert.Equal(typeof(int), p.ParameterInfo.ParameterType); + Assert.Equal("id", p.ParameterName); + Assert.Equal("personId", p.BindingInfo.BinderModelName); + }); + } + + [Fact] + public void CreateHandlerMethods_WithLegacyValidationBehavior_AddsParameterDescriptors() + { + // Arrange + var provider = new DefaultPageApplicationModelProvider( + TestModelMetadataProvider.CreateDefaultProvider(), + Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = false })); var typeInfo = typeof(PageWithHandlerParameters).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageWithHandlerParameters.OnPost)); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -895,7 +931,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateHandlerProperties_UsesPropertyHelpers_ToFindProperties() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(HidesAProperty).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -928,7 +964,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateHandlerProperties_SupportsGet_OnProperty() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(ModelSupportsGetOnProperty).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -1039,7 +1075,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateFilters_AddsIFilterMetadataAttributesToModel() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(FilterModel).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true)); @@ -1063,7 +1099,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateFilters_AddsPageHandlerPageFilter_IfPageImplementsIAsyncPageFilter() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(ModelImplementingAsyncPageFilter).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true)); @@ -1093,7 +1129,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateFilters_AddsPageHandlerPageFilter_IfPageImplementsIPageFilter() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(ModelImplementingPageFilter).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true)); @@ -1128,7 +1164,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void PopulateFilters_AddsPageHandlerPageFilter_ForModelDerivingFromTypeImplementingPageFilter() { // Arrange - var provider = new DefaultPageApplicationModelProvider(); + var provider = CreateProvider(); var typeInfo = typeof(DerivedFromPageModel).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true)); @@ -1144,5 +1180,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal [ServiceFilter(typeof(IServiceProvider))] private class DerivedFromPageModel : PageModel { } + + private static DefaultPageApplicationModelProvider CreateProvider() + { + return new DefaultPageApplicationModelProvider( + TestModelMetadataProvider.CreateDefaultProvider(), + Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true })); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs index f2ccc1ad94..cf6acab03f 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; @@ -136,7 +137,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private static PageApplicationModelProviderContext GetApplicationProviderContext(TypeInfo typeInfo) { - var defaultProvider = new DefaultPageApplicationModelProvider(); + var defaultProvider = new DefaultPageApplicationModelProvider( + TestModelMetadataProvider.CreateDefaultProvider(), + Options.Create(new MvcOptions())); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); defaultProvider.OnProvidersExecuting(context); return context; diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs index df3b617c95..eb9f5434a3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs @@ -183,6 +183,12 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(BindingSource.FormFile, formCollectionParameter.BindingSource); }, provider => + { + var formFileParameter = Assert.IsType(provider); + Assert.Equal(typeof(IFormFileCollection), formFileParameter.Type); + Assert.Equal(BindingSource.FormFile, formFileParameter.BindingSource); + }, + provider => { var excludeFilter = Assert.IsType(provider); Assert.Equal(typeof(Type), excludeFilter.Type); @@ -208,6 +214,11 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(typeof(IFormCollection), excludeFilter.Type); }, provider => + { + var excludeFilter = Assert.IsType(provider); + Assert.Equal(typeof(IFormFileCollection), excludeFilter.Type); + }, + provider => { var excludeFilter = Assert.IsType(provider); Assert.Equal(typeof(Stream), excludeFilter.Type); diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs index 62952ab4c6..73fe0c4c6b 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding // Creates a provider with all the defaults - includes data annotations public static ModelMetadataProvider CreateDefaultProvider(IStringLocalizerFactory stringLocalizerFactory = null) { - var detailsProviders = new IMetadataDetailsProvider[] + var detailsProviders = new List { new DefaultBindingMetadataProvider(), new DefaultValidationMetadataProvider(), @@ -29,6 +29,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding new DataMemberRequiredBindingMetadataProvider(), }; + MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProvider(detailsProviders); + var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions())); } @@ -45,6 +47,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding new DataMemberRequiredBindingMetadataProvider(), }; + MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProvider(detailsProviders); + detailsProviders.AddRange(providers); var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs index 459c398b9d..fb6ec71dc1 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Options; using Xunit; @@ -20,7 +21,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var type = typeof(TestController_NoTempDataProperties); var options = Options.Create(new MvcViewOptions()); var provider = new TempDataApplicationModelProvider(options); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); + var defaultProvider = new DefaultApplicationModelProvider( + Options.Create(new MvcOptions()), + TestModelMetadataProvider.CreateDefaultProvider()); var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); defaultProvider.OnProvidersExecuting(context); @@ -41,7 +44,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var options = Options.Create(new MvcViewOptions()); var provider = new TempDataApplicationModelProvider(options); var expected = $"The '{type.FullName}.Test' property with TempDataAttribute is invalid. A property using TempDataAttribute must have a public getter and setter."; - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); + var defaultProvider = new DefaultApplicationModelProvider( + Options.Create(new MvcOptions()), + TestModelMetadataProvider.CreateDefaultProvider()); var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); defaultProvider.OnProvidersExecuting(context); @@ -58,7 +63,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var type = typeof(TestController_NullableNonPrimitiveTempDataProperty); var options = Options.Create(new MvcViewOptions()); var provider = new TempDataApplicationModelProvider(options); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); + var defaultProvider = new DefaultApplicationModelProvider( + Options.Create(new MvcOptions()), + TestModelMetadataProvider.CreateDefaultProvider()); var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); defaultProvider.OnProvidersExecuting(context); @@ -78,7 +85,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var expected = typeof(TestController_OneTempDataProperty).GetProperty(nameof(TestController_OneTempDataProperty.Test2)); var options = Options.Create(new MvcViewOptions()); var provider = new TempDataApplicationModelProvider(options); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); + var defaultProvider = new DefaultApplicationModelProvider( + Options.Create(new MvcOptions()), + TestModelMetadataProvider.CreateDefaultProvider()); var context = new ApplicationModelProviderContext(new[] { typeof(TestController_OneTempDataProperty).GetTypeInfo() }); defaultProvider.OnProvidersExecuting(context); @@ -102,7 +111,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var expected = typeof(TestController_OneTempDataProperty).GetProperty(nameof(TestController_OneTempDataProperty.Test2)); var options = Options.Create(new MvcViewOptions { SuppressTempDataAttributePrefix = true }); var provider = new TempDataApplicationModelProvider(options); - var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); + var defaultProvider = new DefaultApplicationModelProvider( + Options.Create(new MvcOptions()), + TestModelMetadataProvider.CreateDefaultProvider()); var context = new ApplicationModelProviderContext(new[] { typeof(TestController_OneTempDataProperty).GetTypeInfo() }); defaultProvider.OnProvidersExecuting(context); diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs index 315a4568bc..fea8184296 100644 --- a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs @@ -379,22 +379,18 @@ namespace System.Web.Http var setup = new WebApiCompatShimOptionsSetup(); setup.Configure(options); - var optionsAccessor = new Mock>(); - optionsAccessor - .SetupGet(o => o.Value) - .Returns(options); - var authorizationOptionsAccessor = new Mock>(); authorizationOptionsAccessor .SetupGet(o => o.Value) .Returns(new AuthorizationOptions()); - var modelProvider = new DefaultApplicationModelProvider(optionsAccessor.Object); + var optionsAccessor = Options.Create(options); + var modelProvider = new DefaultApplicationModelProvider(optionsAccessor, TestModelMetadataProvider.CreateDefaultProvider()); var provider = new ControllerActionDescriptorProvider( manager, new[] { modelProvider }, - optionsAccessor.Object); + optionsAccessor); return provider; } diff --git a/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs b/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs index d2141e6dd5..e4c443954c 100644 --- a/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs +++ b/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using BasicWebSite.Models; using Microsoft.AspNetCore.Mvc; @@ -51,6 +52,10 @@ namespace BasicWebSite [HttpPost("ActionWithInferredFromBodyParameter")] public ActionResult ActionWithInferredFromBodyParameter(Contact contact) => contact; + [HttpPost(nameof(ActionWithInferredFromBodyParameterAndCancellationToken))] + public ActionResult ActionWithInferredFromBodyParameterAndCancellationToken(Contact contact, CancellationToken cts) + => contact; + [HttpPost("ActionWithInferredRouteAndQueryParameters/{name}/{id}")] public ActionResult ActionWithInferredRouteAndQueryParameter(int id, string name, string email) { @@ -68,4 +73,4 @@ namespace BasicWebSite return contact; } } -} \ No newline at end of file +}