aspnetcore/test/Microsoft.AspNetCore.Mvc.Co.../ApplicationModels/InferParameterBindingInfoCo...

1140 lines
46 KiB
C#

// 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.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
public class InferParameterBindingInfoConventionTest
{
[Fact]
public void Apply_DoesNotInferBindingSourceForParametersWithBindingInfo()
{
// Arrange
var actionName = nameof(ParameterWithBindingInfo.Action);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterWithBindingInfo), actionName);
// Act
convention.Apply(action.Controller);
// Assert
var parameterModel = Assert.Single(action.Parameters);
Assert.NotNull(parameterModel.BindingInfo);
Assert.Same(BindingSource.Custom, parameterModel.BindingInfo.BindingSource);
}
[Fact]
public void InferParameterBindingSources_Throws_IfMultipleParametersAreInferredAsBodyBound()
{
// Arrange
var actionName = nameof(MultipleFromBodyController.MultipleInferred);
var expected =
$@"Action '{typeof(MultipleFromBodyController).FullName}.{actionName} ({typeof(MultipleFromBodyController).Assembly.GetName().Name})' " +
"has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:" +
Environment.NewLine + "TestModel a" +
Environment.NewLine + "Car b";
var convention = GetConvention();
var action = GetActionModel(typeof(MultipleFromBodyController), actionName);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => convention.InferParameterBindingSources(action));
Assert.Equal(expected, ex.Message);
}
[Fact]
public void InferParameterBindingSources_Throws_IfMultipleParametersAreInferredOrSpecifiedAsBodyBound()
{
// Arrange
var actionName = nameof(MultipleFromBodyController.InferredAndSpecified);
var expected =
$@"Action '{typeof(MultipleFromBodyController).FullName}.{actionName} ({typeof(MultipleFromBodyController).Assembly.GetName().Name})' " +
"has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:" +
Environment.NewLine + "TestModel a" +
Environment.NewLine + "int b";
var convention = GetConvention();
var action = GetActionModel(typeof(MultipleFromBodyController), actionName);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => convention.InferParameterBindingSources(action));
Assert.Equal(expected, ex.Message);
}
[Fact]
public void InferParameterBindingSources_Throws_IfMultipleParametersAreFromBody()
{
// Arrange
var actionName = nameof(MultipleFromBodyController.MultipleSpecified);
var expected =
$@"Action '{typeof(MultipleFromBodyController).FullName}.{actionName} ({typeof(MultipleFromBodyController).Assembly.GetName().Name})' " +
"has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:" +
Environment.NewLine + "decimal a" +
Environment.NewLine + "int b";
var convention = GetConvention();
var action = GetActionModel(typeof(MultipleFromBodyController), actionName);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => convention.InferParameterBindingSources(action));
Assert.Equal(expected, ex.Message);
}
[Fact]
public void InferParameterBindingSources_InfersSources()
{
// Arrange
var actionName = nameof(ParameterBindingController.ComplexTypeModelWithCancellationToken);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var convention = GetConvention(modelMetadataProvider);
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.InferParameterBindingSources(action);
// Assert
Assert.Collection(
action.Parameters,
parameter =>
{
Assert.Equal("model", parameter.Name);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Body, bindingInfo.BindingSource);
},
parameter =>
{
Assert.Equal("cancellationToken", parameter.Name);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Equal(BindingSource.Special, bindingInfo.BindingSource);
});
}
[Fact]
public void InferParameterBindingSources_DoesNotInferSources_IfSuppressInferBindingSourcesForParametersIsSet()
{
// Arrange
var actionName = nameof(ParameterBindingController.ComplexTypeModelWithCancellationToken);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var convention = GetConvention(modelMetadataProvider);
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
convention.SuppressInferBindingSourcesForParameters = true;
// Act
convention.InferParameterBindingSources(action);
// Assert
Assert.Collection(
action.Parameters,
parameter =>
{
Assert.Equal("model", parameter.Name);
Assert.Null(parameter.BindingInfo);
},
parameter =>
{
Assert.Equal("cancellationToken", parameter.Name);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Equal(BindingSource.Special, bindingInfo.BindingSource);
});
}
[Fact]
public void Apply_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinder_AndExplicitName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ModelBinderOnParameterController.ModelBinderAttributeWithExplicitModelName);
var convention = GetConvention();
var action = GetActionModel(typeof(ModelBinderOnParameterController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Query, bindingInfo.BindingSource);
Assert.Equal("top", bindingInfo.BinderModelName);
}
[Fact]
public void Apply_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinderType()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ModelBinderOnParameterController.ModelBinderType);
var convention = GetConvention();
var action = GetActionModel(typeof(ModelBinderOnParameterController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Custom, bindingInfo.BindingSource);
Assert.Null(bindingInfo.BinderModelName);
}
[Fact]
public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinderType_AndExplicitModelName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ModelBinderOnParameterController.ModelBinderTypeWithExplicitModelName);
var convention = GetConvention();
var action = GetActionModel(typeof(ModelBinderOnParameterController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Custom, bindingInfo.BindingSource);
Assert.Equal("foo", bindingInfo.BinderModelName);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRouteAsSimpleToken()
{
// Arrange
var actionName = nameof(ParameterBindingController.SimpleRouteToken);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRouteAsOptionalToken()
{
// Arrange
var actionName = nameof(ParameterBindingController.OptionalRouteToken);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRouteAsConstrainedToken()
{
// Arrange
var actionName = nameof(ParameterBindingController.ConstrainedRouteToken);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInAbsoluteRoute()
{
// Arrange
var actionName = nameof(ParameterBindingController.AbsoluteRoute);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoutes_MulitpleRoutes()
{
// Arrange
var actionName = nameof(ParameterBindingController.ParameterInMultipleRoutes);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoute()
{
// Arrange
var actionName = nameof(ParameterBindingController.ParameterNotInAllRoutes);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInControllerRoute()
{
// Arrange
var actionName = nameof(ParameterInController.ActionWithoutRoute);
var parameter = GetParameterModel(typeof(ParameterInController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInControllerRoute_AndActionHasRoute()
{
// Arrange
var actionName = nameof(ParameterInController.ActionWithRoute);
var parameter = GetParameterModel(typeof(ParameterInController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAllActionRoutes()
{
// Arrange
var actionName = nameof(ParameterInController.MultipleRoute);
var parameter = GetParameterModel(typeof(ParameterInController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_DoesNotReturnPath_IfActionRouteOverridesControllerRoute()
{
// Arrange
var actionName = nameof(ParameterInController.AbsoluteRoute);
var parameter = GetParameterModel(typeof(ParameterInController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Query, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterPresentInNonOverriddenControllerRoute()
{
// Arrange
var actionName = nameof(ParameterInController.MultipleRouteWithOverride);
var parameter = GetParameterModel(typeof(ParameterInController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterExistsInRoute_OnControllersWithoutSelectors()
{
// Arrange
var actionName = nameof(ParameterBindingNoRoutesOnController.SimpleRoute);
var parameter = GetParameterModel(typeof(ParameterBindingNoRoutesOnController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterExistsInAllRoutes_OnControllersWithoutSelectors()
{
// Arrange
var actionName = nameof(ParameterBindingNoRoutesOnController.ParameterInMultipleRoutes);
var parameter = GetParameterModel(typeof(ParameterBindingNoRoutesOnController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Path, result);
}
[Fact]
public void InferBindingSourceForParameter_DoesNotReturnPath_IfNeitherActionNorControllerHasTemplate()
{
// Arrange
var actionName = nameof(ParameterBindingNoRoutesOnController.NoRouteTemplate);
var parameter = GetParameterModel(typeof(ParameterBindingNoRoutesOnController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Query, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsBodyForComplexTypes()
{
// Arrange
var actionName = nameof(ParameterBindingController.ComplexTypeModel);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Body, result);
}
[Fact]
public void InferParameterBindingSources_SetsCorrectBindingSourceForComplexTypesWithCancellationToken()
{
// Arrange
var actionName = nameof(ParameterBindingController.ComplexTypeModelWithCancellationToken);
// 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 convention = GetConvention();
// Act
convention.InferParameterBindingSources(actionModel);
// Assert
var model = GetParameterModel<TestModel>(actionModel);
Assert.Same(BindingSource.Body, model.BindingInfo.BindingSource);
var cancellationToken = GetParameterModel<CancellationToken>(actionModel);
Assert.Same(BindingSource.Special, cancellationToken.BindingInfo.BindingSource);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsBodyForSimpleTypes()
{
// Arrange
var actionName = nameof(ParameterBindingController.SimpleTypeModel);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Query, result);
}
[Fact]
public void PreservesBindingSourceInference_ForFromQueryParameter_WithDefaultName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromQuery);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Query, bindingInfo.BindingSource);
Assert.Null(bindingInfo.BinderModelName);
}
[Fact]
public void PreservesBindingSourceInference_ForFromQueryParameter_WithCustomName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromQueryWithCustomName);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Query, bindingInfo.BindingSource);
Assert.Equal("top", bindingInfo.BinderModelName);
}
[Fact]
public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_WithDefaultName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromQueryOnComplexType);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Query, bindingInfo.BindingSource);
}
[Fact]
public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_WithCustomName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromQueryOnComplexTypeWithCustomName);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Query, bindingInfo.BindingSource);
Assert.Equal("gps", bindingInfo.BinderModelName);
}
[Fact]
public void PreservesBindingSourceInference_ForFromQueryParameterOnCollectionType()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromQueryOnCollectionType);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Query, bindingInfo.BindingSource);
Assert.Null(bindingInfo.BinderModelName);
}
[Fact]
public void PreservesBindingSourceInference_ForFromQueryOnArrayType()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromQueryOnArrayType);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Query, bindingInfo.BindingSource);
Assert.Null(bindingInfo.BinderModelName);
}
[Fact]
public void PreservesBindingSourceInference_FromQueryOnArrayTypeWithCustomName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromQueryOnArrayTypeWithCustomName);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Query, bindingInfo.BindingSource);
Assert.Equal("ids", bindingInfo.BinderModelName);
}
[Fact]
public void PreservesBindingSourceInference_ForFromRouteParameter_WithDefaultName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromRoute);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Path, bindingInfo.BindingSource);
Assert.Null(bindingInfo.BinderModelName);
}
[Fact]
public void PreservesBindingSourceInference_ForFromRouteParameter_WithCustomName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromRouteWithCustomName);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Path, bindingInfo.BindingSource);
Assert.Equal("top", bindingInfo.BinderModelName);
}
[Fact]
public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_WithDefaultName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromRouteOnComplexType);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Path, bindingInfo.BindingSource);
}
[Fact]
public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_WithCustomName()
{
// Arrange
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.FromRouteOnComplexTypeWithCustomName);
var convention = GetConvention();
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Path, bindingInfo.BindingSource);
Assert.Equal("gps", bindingInfo.BinderModelName);
}
[Fact]
public void PreservesBindingSourceInference_ForParameterWithRequestPredicateAndPropertyFilterProvider()
{
// Arrange
var expectedPredicate = CustomRequestPredicateAndPropertyFilterProviderAttribute.RequestPredicateStatic;
var expectedPropertyFilter = CustomRequestPredicateAndPropertyFilterProviderAttribute.PropertyFilterStatic;
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionName = nameof(ParameterBindingController.ParameterWithRequestPredicateProvider);
var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider);
var convention = GetConvention();
// Act
convention.Apply(action.Controller);
// Assert
var parameter = Assert.Single(action.Parameters);
var bindingInfo = parameter.BindingInfo;
Assert.NotNull(bindingInfo);
Assert.Same(BindingSource.Query, bindingInfo.BindingSource);
Assert.Same(expectedPredicate, bindingInfo.RequestPredicate);
Assert.Same(expectedPropertyFilter, bindingInfo.PropertyFilterProvider.PropertyFilter);
Assert.Null(bindingInfo.BinderModelName);
}
[Fact]
public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider()
{
// Arrange
var controller = GetControllerModel(typeof(ControllerWithBoundProperty));
var convention = GetConvention();
// Act
convention.InferBoundPropertyModelPrefixes(controller);
// Assert
var property = Assert.Single(controller.ControllerProperties, p => p.Name == nameof(ControllerWithBoundProperty.TestProperty));
Assert.Equal(string.Empty, property.BindingInfo.BinderModelName);
}
[Fact]
public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForCollectionTypeFromValueProvider()
{
// Arrange
var controller = GetControllerModel(typeof(ControllerWithBoundCollectionProperty));
var convention = GetConvention();
// Act
convention.InferBoundPropertyModelPrefixes(controller);
// Assert
var property = Assert.Single(controller.ControllerProperties);
Assert.Null(property.BindingInfo.BinderModelName);
}
[Fact]
public void InferParameterModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider()
{
// Arrange
var action = GetActionModel(typeof(ControllerWithBoundProperty), nameof(ControllerWithBoundProperty.SomeAction));
var convention = GetConvention();
// Act
convention.InferParameterModelPrefixes(action);
// Assert
var parameter = Assert.Single(action.Parameters);
Assert.Equal(string.Empty, parameter.BindingInfo.BinderModelName);
}
[Fact]
public void InferParameterModelPrefixes_DoesNotSetModelPrefix_ForFormFileParametersAnnotatedWithFromForm()
{
// Arrange
var action = GetActionModel(
typeof(ParameterBindingController),
nameof(ParameterBindingController.FromFormFormFileParameters),
TestModelMetadataProvider.CreateDefaultProvider());
var convention = GetConvention();
// Act
convention.InferParameterModelPrefixes(action);
// Assert
Assert.Collection(
action.Parameters,
parameter =>
{
Assert.Equal("p1", parameter.Name);
Assert.Null(parameter.BindingInfo.BinderModelName);
},
parameter =>
{
Assert.Equal("p2", parameter.Name);
Assert.Null(parameter.BindingInfo.BinderModelName);
},
parameter =>
{
Assert.Equal("p3", parameter.Name);
Assert.Null(parameter.BindingInfo.BinderModelName);
});
}
[Fact]
public void InferParameterModelPrefixes_DoesNotSetModelPrefix_ForFormFileParameters()
{
// Arrange
var action = GetActionModel(
typeof(ParameterBindingController),
nameof(ParameterBindingController.FormFileParameters),
TestModelMetadataProvider.CreateDefaultProvider());
var convention = GetConvention();
// Act
convention.InferParameterModelPrefixes(action);
// Assert
Assert.Collection(
action.Parameters,
parameter =>
{
Assert.Equal("p1", parameter.Name);
Assert.Null(parameter.BindingInfo.BinderModelName);
},
parameter =>
{
Assert.Equal("p2", parameter.Name);
Assert.Null(parameter.BindingInfo.BinderModelName);
},
parameter =>
{
Assert.Equal("p3", parameter.Name);
Assert.Null(parameter.BindingInfo.BinderModelName);
});
}
[Fact]
public void InferBoundPropertyModelPrefixes_DoesNotSetModelPrefix_ForFormFileCollectionPropertiesAnnotatedWithFromForm()
{
// Arrange
var controller = GetControllerModel(typeof(ControllerWithBoundProperty));
var convention = GetConvention();
// Act
convention.InferBoundPropertyModelPrefixes(controller);
// Assert
var parameter = Assert.Single(controller.ControllerProperties, p => p.Name == nameof(ControllerWithBoundProperty.Files));
Assert.Null(parameter.BindingInfo.BinderModelName);
}
private static InferParameterBindingInfoConvention GetConvention(
IModelMetadataProvider modelMetadataProvider = null)
{
var loggerFactory = NullLoggerFactory.Instance;
modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider();
return new InferParameterBindingInfoConvention(modelMetadataProvider);
}
private static ApplicationModelProviderContext GetContext(
Type type,
IModelMetadataProvider modelMetadataProvider = null)
{
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
var mvcOptions = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true });
modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider();
var convention = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider);
convention.OnProvidersExecuting(context);
return context;
}
private static ControllerModel GetControllerModel(
Type controllerType,
IModelMetadataProvider modelMetadataProvider = null)
{
var context = GetContext(controllerType, modelMetadataProvider);
return Assert.Single(context.Result.Controllers);
}
private static ActionModel GetActionModel(
Type controllerType,
string actionName,
IModelMetadataProvider modelMetadataProvider = null)
{
var context = GetContext(controllerType, modelMetadataProvider);
var controller = Assert.Single(context.Result.Controllers);
return Assert.Single(controller.Actions, m => m.ActionName == actionName);
}
private static ParameterModel GetParameterModel(Type controllerType, string actionName)
{
var action = GetActionModel(controllerType, actionName);
return Assert.Single(action.Parameters);
}
private static ParameterModel GetParameterModel<T>(ActionModel action)
{
return Assert.Single(action.Parameters.Where(x => typeof(T).IsAssignableFrom(x.ParameterType)));
}
[ApiController]
[Route("[controller]/[action]")]
private class ParameterBindingController
{
[HttpGet("{parameter}")]
public IActionResult ActionWithBoundParameter([FromBody] object parameter) => null;
[HttpGet("{id}")]
public IActionResult SimpleRouteToken(int id) => null;
[HttpPost("optional/{id?}")]
public IActionResult OptionalRouteToken(int id) => null;
[HttpDelete("delete-by-status/{status:int?}")]
public IActionResult ConstrainedRouteToken(object status) => null;
[HttpPut("/absolute-route/{status:int}")]
public IActionResult AbsoluteRoute(object status) => null;
[HttpPost("multiple/{id}")]
[HttpPut("multiple/{id}")]
public IActionResult ParameterInMultipleRoutes(int id) => null;
[HttpPatch("patchroute")]
[HttpPost("multiple/{id}")]
[HttpPut("multiple/{id}")]
public IActionResult ParameterNotInAllRoutes(int id) => null;
[HttpPut("put-action/{id}")]
public IActionResult ComplexTypeModel(TestModel model) => null;
[HttpPut("put-action/{id}")]
public IActionResult SimpleTypeModel(ConvertibleFromString model) => null;
[HttpPost("form-file")]
public IActionResult FormFileParameter(IFormFile formFile) => null;
[HttpPost("form-file-collection")]
public IActionResult FormFileCollectionParameter(IFormFileCollection formFiles) => null;
[HttpPost("form-file-sequence")]
public IActionResult FormFileSequenceParameter(IFormFile[] formFiles) => null;
[HttpPost]
public IActionResult FromFormParameter([FromForm] string parameter) => null;
[HttpPost]
[Consumes("application/json")]
public IActionResult ActionWithConsumesAttribute([FromForm] string parameter) => null;
[HttpPut("cancellation")]
public IActionResult ComplexTypeModelWithCancellationToken(TestModel model, CancellationToken cancellationToken) => null;
[HttpGet("parameter-with-model-binder-attribute")]
public IActionResult ModelBinderAttribute([ModelBinder(Name = "top")] int value) => null;
[HttpGet("parameter-with-fromquery")]
public IActionResult FromQuery([FromQuery] int value) => null;
[HttpGet("parameter-with-fromquery-and-customname")]
public IActionResult FromQueryWithCustomName([FromQuery(Name = "top")] int value) => null;
[HttpGet("parameter-with-fromquery-on-complextype")]
public IActionResult FromQueryOnComplexType([FromQuery] GpsCoordinates gpsCoordinates) => null;
[HttpGet("parameter-with-fromquery-on-complextype-and-customname")]
public IActionResult FromQueryOnComplexTypeWithCustomName([FromQuery(Name = "gps")] GpsCoordinates gpsCoordinates) => null;
[HttpGet("parameter-with-fromquery-on-collection-type")]
public IActionResult FromQueryOnCollectionType([FromQuery] ICollection<int> value) => null;
[HttpGet("parameter-with-fromquery-on-array-type")]
public IActionResult FromQueryOnArrayType([FromQuery] int[] value) => null;
[HttpGet("parameter-with-fromquery-on-array-type-customname")]
public IActionResult FromQueryOnArrayTypeWithCustomName([FromQuery(Name = "ids")] int[] value) => null;
[HttpGet("parameter-with-fromroute")]
public IActionResult FromRoute([FromRoute] int value) => null;
[HttpGet("parameter-with-fromroute-and-customname")]
public IActionResult FromRouteWithCustomName([FromRoute(Name = "top")] int value) => null;
[HttpGet("parameter-with-fromroute-on-complextype")]
public IActionResult FromRouteOnComplexType([FromRoute] GpsCoordinates gpsCoordinates) => null;
[HttpGet("parameter-with-fromroute-on-complextype-and-customname")]
public IActionResult FromRouteOnComplexTypeWithCustomName([FromRoute(Name = "gps")] GpsCoordinates gpsCoordinates) => null;
[HttpGet]
public IActionResult ParameterWithRequestPredicateProvider([CustomRequestPredicateAndPropertyFilterProvider] int value) => null;
public IActionResult FromFormFormFileParameters([FromForm] IFormFile p1, [FromForm] IFormFile[] p2, [FromForm] IFormFileCollection p3) => null;
public IActionResult FormFileParameters(IFormFile p1, IFormFile[] p2, IFormFileCollection p3) => null;
}
[ApiController]
[Route("[controller]/[action]")]
private class ModelBinderOnParameterController
{
[HttpGet]
public IActionResult ModelBinderAttributeWithExplicitModelName([ModelBinder(Name = "top")] int value) => null;
[HttpGet]
public IActionResult ModelBinderType([ModelBinder(typeof(TestModelBinder))] string name) => null;
[HttpGet]
public IActionResult ModelBinderTypeWithExplicitModelName([ModelBinder(typeof(TestModelBinder), Name = "foo")] string name) => null;
}
[ApiController]
[Route("/route1/[controller]/[action]/{id}")]
[Route("/route2/[controller]/[action]/{id?}")]
private class ParameterInController
{
[HttpGet]
public IActionResult ActionWithoutRoute(int id) => null;
[HttpGet("stuff/{status}")]
public IActionResult ActionWithRoute(int id) => null;
[HttpGet("/absolute-route")]
public IActionResult AbsoluteRoute(int id) => null;
[HttpPut]
[HttpPost("stuff/{status}")]
public IActionResult MultipleRoute(int id) => null;
[HttpPut]
[HttpPost("~/stuff/{status}")]
public IActionResult MultipleRouteWithOverride(int id) => null;
}
[ApiController]
private class ParameterBindingNoRoutesOnController
{
[HttpGet("{parameter}")]
public IActionResult SimpleRoute(int parameter) => null;
[HttpGet]
public IActionResult NoRouteTemplate(int id) => null;
[HttpPost("multiple/{id}")]
[HttpPut("multiple/{id}")]
public IActionResult ParameterInMultipleRoutes(int id) => null;
}
private class TestModel { }
[TypeConverter(typeof(ConvertibleFromStringConverter))]
private class ConvertibleFromString { }
private class ConvertibleFromStringConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
=> sourceType == typeof(string);
}
private class CustomRequestPredicateAndPropertyFilterProviderAttribute : Attribute, IRequestPredicateProvider, IPropertyFilterProvider
{
public static Func<ActionContext, bool> RequestPredicateStatic => (c) => true;
public static Func<ModelMetadata, bool> PropertyFilterStatic => (c) => true;
public Func<ActionContext, bool> RequestPredicate => RequestPredicateStatic;
public Func<ModelMetadata, bool> PropertyFilter => PropertyFilterStatic;
}
private class GpsCoordinates
{
public long Latitude { get; set; }
public long Longitude { get; set; }
}
private class TestModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
throw new NotImplementedException();
}
}
private class ControllerWithBoundProperty
{
[FromQuery]
public TestModel TestProperty { get; set; }
[FromForm]
public IList<IFormFile> Files { get; set; }
public IActionResult SomeAction([FromQuery] TestModel test) => null;
}
private class ControllerWithBoundCollectionProperty
{
[FromQuery]
public List<int> TestProperty { get; set; }
public IActionResult SomeAction([FromQuery] List<int> test) => null;
}
private class Car { }
private class MultipleFromBodyController
{
public IActionResult MultipleInferred(TestModel a, Car b) => null;
public IActionResult InferredAndSpecified(TestModel a, [FromBody] int b) => null;
public IActionResult MultipleSpecified([FromBody] decimal a, [FromBody] int b) => null;
}
private class ParameterWithBindingInfo
{
[HttpGet("test")]
public IActionResult Action([ModelBinder(typeof(object))] Car car) => null;
}
}
}