Use type metadata for `ModelBinderProviderContext.BindingInfo`
- #4652 - previously ignored for top-level models - `ModelBinderProviderContext.BindingInfo` is now never `null` - similarly, use type metadata (as well as parameter info) for `ModelBindingContext.BinderModelName` - previously ignored when overridden in `ControllerArgumentBinder`
This commit is contained in:
parent
9963359087
commit
e63f094a5f
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public abstract IModelBinder CreateBinder(ModelMetadata metadata);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BindingInfo"/>. May be <c>null</c>.
|
||||
/// Gets the <see cref="BindingInfo"/>.
|
||||
/// </summary>
|
||||
public abstract BindingInfo BindingInfo { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
|
||||
var binder = _modelBinderFactory.CreateBinder(new ModelBinderFactoryContext()
|
||||
{
|
||||
BindingInfo = parameter.BindingInfo,
|
||||
Metadata = metadata,
|
||||
CacheToken = parameter,
|
||||
});
|
||||
|
||||
var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
|
||||
controllerContext,
|
||||
valueProvider,
|
||||
|
|
@ -163,10 +170,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
parameter.BindingInfo,
|
||||
parameter.Name);
|
||||
|
||||
if (parameter.BindingInfo?.BinderModelName != null)
|
||||
var parameterModelName = parameter.BindingInfo?.BinderModelName ?? metadata.BinderModelName;
|
||||
if (parameterModelName != null)
|
||||
{
|
||||
// The name was set explicitly, always use that as the prefix.
|
||||
modelBindingContext.ModelName = parameter.BindingInfo.BinderModelName;
|
||||
modelBindingContext.ModelName = parameterModelName;
|
||||
}
|
||||
else if (modelBindingContext.ValueProvider.ContainsPrefix(parameter.Name))
|
||||
{
|
||||
|
|
@ -179,13 +187,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
modelBindingContext.ModelName = string.Empty;
|
||||
}
|
||||
|
||||
var binder = _modelBinderFactory.CreateBinder(new ModelBinderFactoryContext()
|
||||
{
|
||||
BindingInfo = parameter.BindingInfo,
|
||||
Metadata = metadata,
|
||||
CacheToken = parameter,
|
||||
});
|
||||
|
||||
await binder.BindModelAsync(modelBindingContext);
|
||||
|
||||
var modelBindingResult = modelBindingContext.Result;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.BindingInfo?.BinderType != null)
|
||||
if (context.BindingInfo.BinderType != null)
|
||||
{
|
||||
return new BinderTypeModelBinder(context.BindingInfo.BinderType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.BindingInfo?.BindingSource != null &&
|
||||
if (context.BindingInfo.BindingSource != null &&
|
||||
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body))
|
||||
{
|
||||
return new BodyModelBinder(_formatters, _readerFactory);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.BindingInfo?.BindingSource != null &&
|
||||
if (context.BindingInfo.BindingSource != null &&
|
||||
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Header))
|
||||
{
|
||||
// We only support strings and collections of strings. Some cases can fail
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.BindingInfo?.BindingSource != null &&
|
||||
if (context.BindingInfo.BindingSource != null &&
|
||||
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Services))
|
||||
{
|
||||
return new ServicesModelBinder();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Internal
|
|||
{
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified
|
||||
/// <paramref name="modelBinderFactory"/> and the specified <paramref name="valueProvider"/> and executes
|
||||
/// <paramref name="modelBinderFactory"/> and the specified <paramref name="valueProvider"/> and executes
|
||||
/// validation using the specified <paramref name="objectModelValidator"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
|
|
@ -476,8 +476,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Internal
|
|||
|
||||
if (expression.NodeType != ExpressionType.MemberAccess)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatInvalid_IncludePropertyExpression(
|
||||
expression.NodeType));
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatInvalid_IncludePropertyExpression(expression.NodeType));
|
||||
}
|
||||
|
||||
var memberExpression = (MemberExpression)expression;
|
||||
|
|
@ -488,7 +488,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Internal
|
|||
{
|
||||
// Chained expressions and non parameter based expressions are not supported.
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatInvalid_IncludePropertyExpression(expression.NodeType));
|
||||
Resources.FormatInvalid_IncludePropertyExpression(expression.NodeType));
|
||||
}
|
||||
|
||||
return memberInfo.Name;
|
||||
|
|
@ -496,8 +496,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Internal
|
|||
else
|
||||
{
|
||||
// Fields are also not supported.
|
||||
throw new InvalidOperationException(Resources.FormatInvalid_IncludePropertyExpression(
|
||||
expression.NodeType));
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatInvalid_IncludePropertyExpression(expression.NodeType));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
||||
|
|
@ -152,7 +151,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
{
|
||||
_factory = factory;
|
||||
Metadata = factoryContext.Metadata;
|
||||
BindingInfo = factoryContext.BindingInfo;
|
||||
BindingInfo = new BindingInfo
|
||||
{
|
||||
BinderModelName = factoryContext.BindingInfo?.BinderModelName ?? Metadata.BinderModelName,
|
||||
BinderType = factoryContext.BindingInfo?.BinderType ?? Metadata.BinderType,
|
||||
BindingSource = factoryContext.BindingInfo?.BindingSource ?? Metadata.BindingSource,
|
||||
PropertyFilterProvider =
|
||||
factoryContext.BindingInfo?.PropertyFilterProvider ?? Metadata.PropertyFilterProvider,
|
||||
};
|
||||
|
||||
MetadataProvider = _factory._metadataProvider;
|
||||
Stack = new List<KeyValuePair<Key, PlaceholderBinder>>();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Moq;
|
||||
|
|
@ -573,6 +574,135 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.Equal("Hello", controller.StringProperty);
|
||||
}
|
||||
|
||||
public static TheoryData BindModelAsyncData
|
||||
{
|
||||
get
|
||||
{
|
||||
var emptyBindingInfo = new BindingInfo();
|
||||
var bindingInfoWithName = new BindingInfo
|
||||
{
|
||||
BinderModelName = "bindingInfoName",
|
||||
BinderType = typeof(Person),
|
||||
};
|
||||
|
||||
// parameterBindingInfo, metadataBinderModelName, parameterName, expectedBinderModelName
|
||||
return new TheoryData<BindingInfo, string, string, string>
|
||||
{
|
||||
// If the parameter name is not a prefix match, it is ignored. But name is required to create a
|
||||
// ModelBindingContext.
|
||||
{ null, null, "parameterName", string.Empty },
|
||||
{ emptyBindingInfo, null, "parameterName", string.Empty },
|
||||
{ bindingInfoWithName, null, "parameterName", "bindingInfoName" },
|
||||
{ null, "modelBinderName", "parameterName", "modelBinderName" },
|
||||
{ null, null, "parameterName", string.Empty },
|
||||
// Parameter's BindingInfo has highest precedence
|
||||
{ bindingInfoWithName, "modelBinderName", "parameterName", "bindingInfoName" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BindModelAsyncData))]
|
||||
public async Task BindModelAsync_PassesExpectedBindingInfoAndMetadata_IfPrefixDoesNotMatch(
|
||||
BindingInfo parameterBindingInfo,
|
||||
string metadataBinderModelName,
|
||||
string parameterName,
|
||||
string expectedModelName)
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Person>().BindingDetails(binding =>
|
||||
{
|
||||
binding.BinderModelName = metadataBinderModelName;
|
||||
});
|
||||
|
||||
var metadata = metadataProvider.GetMetadataForType(typeof(Person));
|
||||
var modelBinder = new Mock<IModelBinder>();
|
||||
modelBinder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Callback((ModelBindingContext context) =>
|
||||
{
|
||||
Assert.Equal(expectedModelName, context.ModelName, StringComparer.Ordinal);
|
||||
})
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
|
||||
var parameterDescriptor = new ParameterDescriptor
|
||||
{
|
||||
BindingInfo = parameterBindingInfo,
|
||||
Name = parameterName,
|
||||
ParameterType = typeof(Person),
|
||||
};
|
||||
|
||||
var factory = new Mock<IModelBinderFactory>(MockBehavior.Strict);
|
||||
factory
|
||||
.Setup(f => f.CreateBinder(It.IsAny<ModelBinderFactoryContext>()))
|
||||
.Callback((ModelBinderFactoryContext context) =>
|
||||
{
|
||||
// Confirm expected data is passed through to ModelBindingFactory.
|
||||
Assert.Same(parameterDescriptor.BindingInfo, context.BindingInfo);
|
||||
Assert.Same(parameterDescriptor, context.CacheToken);
|
||||
Assert.Equal(metadata, context.Metadata);
|
||||
})
|
||||
.Returns(modelBinder.Object);
|
||||
|
||||
var argumentBinder = new ControllerArgumentBinder(metadataProvider, factory.Object, CreateMockValidator());
|
||||
var controllerContext = GetControllerContext();
|
||||
controllerContext.ActionDescriptor.Parameters.Add(parameterDescriptor);
|
||||
|
||||
// Act & Assert
|
||||
await argumentBinder.BindModelAsync(parameterDescriptor, controllerContext);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_PassesExpectedBindingInfoAndMetadata_IfPrefixMatches()
|
||||
{
|
||||
// Arrange
|
||||
var expectedModelName = "expectedName";
|
||||
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
var metadata = metadataProvider.GetMetadataForType(typeof(Person));
|
||||
var modelBinder = new Mock<IModelBinder>();
|
||||
modelBinder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Callback((ModelBindingContext context) =>
|
||||
{
|
||||
Assert.Equal(expectedModelName, context.ModelName, StringComparer.Ordinal);
|
||||
})
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
|
||||
var parameterDescriptor = new ParameterDescriptor
|
||||
{
|
||||
Name = expectedModelName,
|
||||
ParameterType = typeof(Person),
|
||||
};
|
||||
|
||||
var factory = new Mock<IModelBinderFactory>(MockBehavior.Strict);
|
||||
factory
|
||||
.Setup(f => f.CreateBinder(It.IsAny<ModelBinderFactoryContext>()))
|
||||
.Callback((ModelBinderFactoryContext context) =>
|
||||
{
|
||||
// Confirm expected data is passed through to ModelBindingFactory.
|
||||
Assert.Null(context.BindingInfo);
|
||||
Assert.Same(parameterDescriptor, context.CacheToken);
|
||||
Assert.Equal(metadata, context.Metadata);
|
||||
})
|
||||
.Returns(modelBinder.Object);
|
||||
|
||||
var argumentBinder = new ControllerArgumentBinder(metadataProvider, factory.Object, CreateMockValidator());
|
||||
var valueProvider = new SimpleValueProvider
|
||||
{
|
||||
{ expectedModelName, new object() },
|
||||
};
|
||||
var valueProviderFactory = new SimpleValueProviderFactory(valueProvider);
|
||||
|
||||
var controllerContext = GetControllerContext();
|
||||
controllerContext.ActionDescriptor.Parameters.Add(parameterDescriptor);
|
||||
controllerContext.ValueProviderFactories.Insert(0, valueProviderFactory);
|
||||
|
||||
// Act & Assert
|
||||
await argumentBinder.BindModelAsync(parameterDescriptor, controllerContext);
|
||||
}
|
||||
|
||||
private static ControllerContext GetControllerContext(ControllerActionDescriptor descriptor = null)
|
||||
{
|
||||
var context = new ControllerContext()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -235,6 +236,112 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
Assert.Same(result1, result2);
|
||||
}
|
||||
|
||||
public static TheoryData BindingInfoData
|
||||
{
|
||||
get
|
||||
{
|
||||
var propertyFilterProvider = Mock.Of<IPropertyFilterProvider>();
|
||||
|
||||
var emptyBindingInfo = new BindingInfo();
|
||||
var halfBindingInfo = new BindingInfo
|
||||
{
|
||||
BinderModelName = "expected name",
|
||||
BinderType = typeof(Widget),
|
||||
};
|
||||
var fullBindingInfo = new BindingInfo
|
||||
{
|
||||
BinderModelName = "expected name",
|
||||
BinderType = typeof(Widget),
|
||||
BindingSource = BindingSource.Services,
|
||||
PropertyFilterProvider = propertyFilterProvider,
|
||||
};
|
||||
|
||||
var emptyBindingMetadata = new BindingMetadata();
|
||||
var differentBindingMetadata = new BindingMetadata
|
||||
{
|
||||
BinderModelName = "not the expected name",
|
||||
BinderType = typeof(WidgetId),
|
||||
BindingSource = BindingSource.ModelBinding,
|
||||
PropertyFilterProvider = Mock.Of<IPropertyFilterProvider>(),
|
||||
};
|
||||
var secondHalfBindingMetadata = new BindingMetadata
|
||||
{
|
||||
BindingSource = BindingSource.Services,
|
||||
PropertyFilterProvider = propertyFilterProvider,
|
||||
};
|
||||
var fullBindingMetadata = new BindingMetadata
|
||||
{
|
||||
BinderModelName = "expected name",
|
||||
BinderType = typeof(Widget),
|
||||
BindingSource = BindingSource.Services,
|
||||
PropertyFilterProvider = propertyFilterProvider,
|
||||
};
|
||||
|
||||
// parameterBindingInfo, bindingMetadata, expectedInfo
|
||||
return new TheoryData<BindingInfo, BindingMetadata, BindingInfo>
|
||||
{
|
||||
{ emptyBindingInfo, emptyBindingMetadata, emptyBindingInfo },
|
||||
{ fullBindingInfo, emptyBindingMetadata, fullBindingInfo },
|
||||
{ emptyBindingInfo, fullBindingMetadata, fullBindingInfo },
|
||||
// Resulting BindingInfo combines two inputs
|
||||
{ halfBindingInfo, secondHalfBindingMetadata, fullBindingInfo },
|
||||
// Parameter information has precedence over type metadata
|
||||
{ fullBindingInfo, differentBindingMetadata, fullBindingInfo },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BindingInfoData))]
|
||||
public void CreateBinder_PassesExpectedBindingInfo(
|
||||
BindingInfo parameterBindingInfo,
|
||||
BindingMetadata bindingMetadata,
|
||||
BindingInfo expectedInfo)
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider.ForType<Employee>().BindingDetails(binding =>
|
||||
{
|
||||
binding.BinderModelName = bindingMetadata.BinderModelName;
|
||||
binding.BinderType = bindingMetadata.BinderType;
|
||||
binding.BindingSource = bindingMetadata.BindingSource;
|
||||
if (bindingMetadata.PropertyFilterProvider != null)
|
||||
{
|
||||
binding.PropertyFilterProvider = bindingMetadata.PropertyFilterProvider;
|
||||
}
|
||||
});
|
||||
|
||||
var modelBinder = Mock.Of<IModelBinder>();
|
||||
var modelBinderProvider = new TestModelBinderProvider(context =>
|
||||
{
|
||||
Assert.Equal(typeof(Employee), context.Metadata.ModelType);
|
||||
|
||||
Assert.NotNull(context.BindingInfo);
|
||||
Assert.Equal(expectedInfo.BinderModelName, context.BindingInfo.BinderModelName, StringComparer.Ordinal);
|
||||
Assert.Equal(expectedInfo.BinderType, context.BindingInfo.BinderType);
|
||||
Assert.Equal(expectedInfo.BindingSource, context.BindingInfo.BindingSource);
|
||||
Assert.Same(expectedInfo.PropertyFilterProvider, context.BindingInfo.PropertyFilterProvider);
|
||||
|
||||
return modelBinder;
|
||||
});
|
||||
|
||||
var options = new TestOptionsManager<MvcOptions>();
|
||||
options.Value.ModelBinderProviders.Insert(0, modelBinderProvider);
|
||||
|
||||
var factory = new ModelBinderFactory(metadataProvider, options);
|
||||
var factoryContext = new ModelBinderFactoryContext
|
||||
{
|
||||
BindingInfo = parameterBindingInfo,
|
||||
Metadata = metadataProvider.GetMetadataForType(typeof(Employee)),
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var result = factory.CreateBinder(factoryContext);
|
||||
|
||||
// Confirm our IModelBinderProvider was called.
|
||||
Assert.Same(modelBinder, result);
|
||||
}
|
||||
|
||||
private class Widget
|
||||
{
|
||||
public WidgetId Id { get; set; }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
// Has to be internal because TestModelMetadataProvider is 'shared' code.
|
||||
internal static readonly TestModelMetadataProvider CachedMetadataProvider = new TestModelMetadataProvider();
|
||||
|
||||
private readonly List<Func<ModelMetadata, IModelBinder>> _binderCreators =
|
||||
private readonly List<Func<ModelMetadata, IModelBinder>> _binderCreators =
|
||||
new List<Func<ModelMetadata, IModelBinder>>();
|
||||
|
||||
public TestModelBinderProviderContext(Type modelType)
|
||||
|
|
@ -31,7 +31,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
public TestModelBinderProviderContext(ModelMetadata metadata, BindingInfo bindingInfo)
|
||||
{
|
||||
Metadata = metadata;
|
||||
BindingInfo = bindingInfo;
|
||||
BindingInfo = bindingInfo ?? new BindingInfo
|
||||
{
|
||||
BinderModelName = metadata.BinderModelName,
|
||||
BinderType = metadata.BinderType,
|
||||
BindingSource = metadata.BindingSource,
|
||||
PropertyFilterProvider = metadata.PropertyFilterProvider,
|
||||
};
|
||||
|
||||
MetadataProvider = CachedMetadataProvider;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
public class BinderTypeBasedModelBinderIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
[InlineData(typeof(NullModelNotSetModelBinder), false)]
|
||||
public async Task BindParameter_WithModelBinderType_NullData_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -134,6 +133,112 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
public string Street { get; set; }
|
||||
}
|
||||
|
||||
public static TheoryData<BindingInfo> NullAndEmptyBindingInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<BindingInfo>
|
||||
{
|
||||
null,
|
||||
new BindingInfo(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the metadata is honored when a [ModelBinder] attribute is associated with an action parameter's
|
||||
// type. This should behave identically to such an attribute on an action parameter. (Tests such as
|
||||
// BindParameter_WithData_WithPrefix_GetsBound cover associating [ModelBinder] with an action parameter.)
|
||||
//
|
||||
// This is a regression test for aspnet/Mvc#4652
|
||||
[Theory]
|
||||
[MemberData(nameof(NullAndEmptyBindingInfo))]
|
||||
public async Task BinderTypeOnParameterType_WithData_EmptyPrefix_GetsBound(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "Parameter1",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(Address),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext();
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
// ModelBindingResult
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
// Model
|
||||
var address = Assert.IsType<Address>(modelBindingResult.Model);
|
||||
Assert.Equal("SomeStreet", address.Street);
|
||||
|
||||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
var kvp = Assert.Single(modelState);
|
||||
Assert.Equal("Street", kvp.Key);
|
||||
var entry = kvp.Value;
|
||||
Assert.NotNull(entry);
|
||||
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||
Assert.NotNull(entry.RawValue); // Value is set by test model binder, no need to validate it.
|
||||
}
|
||||
|
||||
private class Person3
|
||||
{
|
||||
[ModelBinder(BinderType = typeof(Address3ModelBinder))]
|
||||
public Address3 Address { get; set; }
|
||||
}
|
||||
|
||||
private class Address3
|
||||
{
|
||||
public string Street { get; set; }
|
||||
}
|
||||
|
||||
// Make sure the metadata is honored when a [ModelBinder] attribute is associated with a property in the type
|
||||
// hierarchy of an action parameter. (Tests such as BindProperty_WithData_EmptyPrefix_GetsBound cover
|
||||
// associating [ModelBinder] with a class somewhere in the type hierarchy of an action parameter.)
|
||||
[Theory]
|
||||
[MemberData(nameof(NullAndEmptyBindingInfo))]
|
||||
public async Task BinderTypeOnProperty_WithData_EmptyPrefix_GetsBound(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "Parameter1",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(Person3),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext();
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
// ModelBindingResult
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
// Model
|
||||
var person = Assert.IsType<Person3>(modelBindingResult.Model);
|
||||
Assert.NotNull(person.Address);
|
||||
Assert.Equal("SomeStreet", person.Address.Street);
|
||||
|
||||
// ModelState
|
||||
Assert.True(modelState.IsValid);
|
||||
var kvp = Assert.Single(modelState);
|
||||
Assert.Equal("Address.Street", kvp.Key);
|
||||
var entry = kvp.Value;
|
||||
Assert.NotNull(entry);
|
||||
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||
Assert.NotNull(entry.RawValue); // Value is set by test model binder, no need to validate it.
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindProperty_WithData_EmptyPrefix_GetsBound()
|
||||
{
|
||||
|
|
@ -237,6 +342,34 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
}
|
||||
}
|
||||
|
||||
private class Address3ModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
Debug.Assert(bindingContext.Result == ModelBindingResult.Failed());
|
||||
|
||||
if (bindingContext.ModelType != typeof(Address3))
|
||||
{
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
var address = new Address3 { Street = "SomeStreet" };
|
||||
|
||||
bindingContext.ModelState.SetModelValue(
|
||||
ModelNames.CreatePropertyModelName(bindingContext.ModelName, "Street"),
|
||||
new string[] { address.Street },
|
||||
address.Street);
|
||||
|
||||
bindingContext.Result = ModelBindingResult.Success(address);
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class SuccessModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
|
|
|
|||
|
|
@ -1,6 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
|
|
@ -15,18 +16,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
{
|
||||
public class BodyValidationIntegrationTests
|
||||
{
|
||||
private class Person
|
||||
{
|
||||
[FromBody]
|
||||
[Required]
|
||||
public Address Address { get; set; }
|
||||
}
|
||||
|
||||
private class Address
|
||||
{
|
||||
public string Street { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ModelMetadataTypeAttribute_ValidBaseClass_NoModelStateErrors()
|
||||
{
|
||||
|
|
@ -354,6 +343,18 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Equal("Product must be made in the USA if it is not named.", modelStateErrors[""]);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
[FromBody]
|
||||
[Required]
|
||||
public Address Address { get; set; }
|
||||
}
|
||||
|
||||
private class Address
|
||||
{
|
||||
public string Street { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FromBodyAndRequiredOnProperty_EmptyBody_AddsModelStateError()
|
||||
{
|
||||
|
|
@ -690,6 +691,107 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.Empty(modelState);
|
||||
}
|
||||
|
||||
private class Person6
|
||||
{
|
||||
public Address6 Address { get; set; }
|
||||
}
|
||||
|
||||
private class Address6
|
||||
{
|
||||
public string Street { get; set; }
|
||||
}
|
||||
|
||||
// [FromBody] cannot be associated with a type. But a [FromBody] or [ModelBinder] subclass or custom
|
||||
// IBindingSourceMetadata implementation might not have the same restriction. Make sure the metadata is honored
|
||||
// when such an attribute is associated with a class somewhere in the type hierarchy of an action parameter.
|
||||
[Theory]
|
||||
[MemberData(
|
||||
nameof(BinderTypeBasedModelBinderIntegrationTest.NullAndEmptyBindingInfo),
|
||||
MemberType = typeof(BinderTypeBasedModelBinderIntegrationTest))]
|
||||
public async Task FromBodyOnPropertyType_WithData_Succeeds(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
var inputText = "{ \"Street\" : \"someStreet\" }";
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForProperty<Person6>(nameof(Person6.Address))
|
||||
.BindingDetails(binding => binding.BindingSource = BindingSource.Body);
|
||||
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter-name",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(Person6),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||
request =>
|
||||
{
|
||||
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(inputText));
|
||||
request.ContentType = "application/json";
|
||||
});
|
||||
testContext.MetadataProvider = metadataProvider;
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
var person = Assert.IsType<Person6>(modelBindingResult.Model);
|
||||
Assert.NotNull(person.Address);
|
||||
Assert.Equal("someStreet", person.Address.Street, StringComparer.Ordinal);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
Assert.Empty(modelState);
|
||||
}
|
||||
|
||||
// [FromBody] cannot be associated with a type. But a [FromBody] or [ModelBinder] subclass or custom
|
||||
// IBindingSourceMetadata implementation might not have the same restriction. Make sure the metadata is honored
|
||||
// when such an attribute is associated with an action parameter's type.
|
||||
[Theory]
|
||||
[MemberData(
|
||||
nameof(BinderTypeBasedModelBinderIntegrationTest.NullAndEmptyBindingInfo),
|
||||
MemberType = typeof(BinderTypeBasedModelBinderIntegrationTest))]
|
||||
public async Task FromBodyOnParameterType_WithData_Succeeds(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
var inputText = "{ \"Street\" : \"someStreet\" }";
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForType<Address6>()
|
||||
.BindingDetails(binding => binding.BindingSource = BindingSource.Body);
|
||||
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter-name",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(Address6),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||
request =>
|
||||
{
|
||||
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(inputText));
|
||||
request.ContentType = "application/json";
|
||||
});
|
||||
testContext.MetadataProvider = metadataProvider;
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
var address = Assert.IsType<Address6>(modelBindingResult.Model);
|
||||
Assert.Equal("someStreet", address.Street, StringComparer.Ordinal);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
Assert.Empty(modelState);
|
||||
}
|
||||
|
||||
private Dictionary<string, string> CreateValidationDictionary(ModelStateDictionary modelState)
|
||||
{
|
||||
var result = new Dictionary<string, string>();
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
{
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
var allowedBindingSource = context.BindingInfo?.BindingSource;
|
||||
var allowedBindingSource = context.BindingInfo.BindingSource;
|
||||
if (allowedBindingSource?.CanAcceptDataFrom(BindAddressAttribute.Source) == true)
|
||||
{
|
||||
// Binding Sources are opt-in. This model either didn't specify one or specified something
|
||||
|
|
|
|||
|
|
@ -1932,6 +1932,200 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
Assert.False(modelState.IsValid);
|
||||
}
|
||||
|
||||
private class Person12
|
||||
{
|
||||
public Address12 Address { get; set; }
|
||||
}
|
||||
|
||||
[ModelBinder(Name = "HomeAddress")]
|
||||
private class Address12
|
||||
{
|
||||
public string Street { get; set; }
|
||||
}
|
||||
|
||||
// Make sure the metadata is honored when a [ModelBinder] attribute is associated with a class somewhere in the
|
||||
// type hierarchy of an action parameter. This should behave identically to such an attribute on a property in
|
||||
// the type hierarchy.
|
||||
[Theory]
|
||||
[MemberData(
|
||||
nameof(BinderTypeBasedModelBinderIntegrationTest.NullAndEmptyBindingInfo),
|
||||
MemberType = typeof(BinderTypeBasedModelBinderIntegrationTest))]
|
||||
public async Task ModelNameOnPropertyType_WithData_Succeeds(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter-name",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(Person12),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||
request => request.QueryString = new QueryString("?HomeAddress.Street=someStreet"));
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
var person = Assert.IsType<Person12>(modelBindingResult.Model);
|
||||
Assert.NotNull(person.Address);
|
||||
Assert.Equal("someStreet", person.Address.Street, StringComparer.Ordinal);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
var kvp = Assert.Single(modelState);
|
||||
Assert.Equal("HomeAddress.Street", kvp.Key);
|
||||
var entry = kvp.Value;
|
||||
Assert.NotNull(entry);
|
||||
Assert.Empty(entry.Errors);
|
||||
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||
}
|
||||
|
||||
// Make sure the metadata is honored when a [ModelBinder] attribute is associated with an action parameter's
|
||||
// type. This should behave identically to such an attribute on an action parameter.
|
||||
[Theory]
|
||||
[MemberData(
|
||||
nameof(BinderTypeBasedModelBinderIntegrationTest.NullAndEmptyBindingInfo),
|
||||
MemberType = typeof(BinderTypeBasedModelBinderIntegrationTest))]
|
||||
public async Task ModelNameOnParameterType_WithData_Succeeds(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter-name",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(Address12),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||
request => request.QueryString = new QueryString("?HomeAddress.Street=someStreet"));
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
var address = Assert.IsType<Address12>(modelBindingResult.Model);
|
||||
Assert.Equal("someStreet", address.Street, StringComparer.Ordinal);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
var kvp = Assert.Single(modelState);
|
||||
Assert.Equal("HomeAddress.Street", kvp.Key);
|
||||
var entry = kvp.Value;
|
||||
Assert.NotNull(entry);
|
||||
Assert.Empty(entry.Errors);
|
||||
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||
}
|
||||
|
||||
private class Person13
|
||||
{
|
||||
public Address13 Address { get; set; }
|
||||
}
|
||||
|
||||
[Bind("Street")]
|
||||
private class Address13
|
||||
{
|
||||
public int Number { get; set; }
|
||||
|
||||
public string Street { get; set; }
|
||||
|
||||
public string City { get; set; }
|
||||
|
||||
public string State { get; set; }
|
||||
}
|
||||
|
||||
// Make sure the metadata is honored when a [Bind] attribute is associated with a class somewhere in the type
|
||||
// hierarchy of an action parameter. This should behave identically to such an attribute on a property in the
|
||||
// type hierarchy. (Test is similar to ModelNameOnPropertyType_WithData_Succeeds() but covers implementing
|
||||
// IPropertyFilterProvider, not IModelNameProvider.)
|
||||
[Theory]
|
||||
[MemberData(
|
||||
nameof(BinderTypeBasedModelBinderIntegrationTest.NullAndEmptyBindingInfo),
|
||||
MemberType = typeof(BinderTypeBasedModelBinderIntegrationTest))]
|
||||
public async Task BindAttributeOnPropertyType_WithData_Succeeds(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter-name",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(Person13),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||
request => request.QueryString = new QueryString(
|
||||
"?Address.Number=23&Address.Street=someStreet&Address.City=Redmond&Address.State=WA"));
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
var person = Assert.IsType<Person13>(modelBindingResult.Model);
|
||||
Assert.NotNull(person.Address);
|
||||
Assert.Null(person.Address.City);
|
||||
Assert.Equal(0, person.Address.Number);
|
||||
Assert.Null(person.Address.State);
|
||||
Assert.Equal("someStreet", person.Address.Street, StringComparer.Ordinal);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
var kvp = Assert.Single(modelState);
|
||||
Assert.Equal("Address.Street", kvp.Key);
|
||||
var entry = kvp.Value;
|
||||
Assert.NotNull(entry);
|
||||
Assert.Empty(entry.Errors);
|
||||
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||
}
|
||||
|
||||
// Make sure the metadata is honored when a [Bind] attribute is associated with an action parameter's type.
|
||||
// This should behave identically to such an attribute on an action parameter. (Test is similar
|
||||
// to ModelNameOnParameterType_WithData_Succeeds() but covers implementing IPropertyFilterProvider, not
|
||||
// IModelNameProvider.)
|
||||
[Theory]
|
||||
[MemberData(
|
||||
nameof(BinderTypeBasedModelBinderIntegrationTest.NullAndEmptyBindingInfo),
|
||||
MemberType = typeof(BinderTypeBasedModelBinderIntegrationTest))]
|
||||
public async Task BindAttributeOnParameterType_WithData_Succeeds(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter-name",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(Address13),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||
request => request.QueryString = new QueryString("?Number=23&Street=someStreet&City=Redmond&State=WA"));
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
var address = Assert.IsType<Address13>(modelBindingResult.Model);
|
||||
Assert.Null(address.City);
|
||||
Assert.Equal(0, address.Number);
|
||||
Assert.Null(address.State);
|
||||
Assert.Equal("someStreet", address.Street, StringComparer.Ordinal);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
var kvp = Assert.Single(modelState);
|
||||
Assert.Equal("Street", kvp.Key);
|
||||
var entry = kvp.Value;
|
||||
Assert.NotNull(entry);
|
||||
Assert.Empty(entry.Errors);
|
||||
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||
}
|
||||
|
||||
private static void SetJsonBodyContent(HttpRequest request, string content)
|
||||
{
|
||||
var stream = new MemoryStream(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(content));
|
||||
|
|
|
|||
|
|
@ -183,5 +183,89 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
|||
() => argumentBinder.BindModelAsync(parameter, testContext));
|
||||
Assert.Contains(typeof(IActionResult).FullName, exception.Message);
|
||||
}
|
||||
|
||||
private class Person
|
||||
{
|
||||
public JsonOutputFormatter Service { get; set; }
|
||||
}
|
||||
|
||||
// [FromServices] cannot be associated with a type. But a [FromServices] or [ModelBinder] subclass or custom
|
||||
// IBindingSourceMetadata implementation might not have the same restriction. Make sure the metadata is honored
|
||||
// when such an attribute is associated with a type somewhere in the type hierarchy of an action parameter.
|
||||
[Theory]
|
||||
[MemberData(
|
||||
nameof(BinderTypeBasedModelBinderIntegrationTest.NullAndEmptyBindingInfo),
|
||||
MemberType = typeof(BinderTypeBasedModelBinderIntegrationTest))]
|
||||
public async Task FromServicesOnPropertyType_WithData_Succeeds(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
// Similar to a custom IBindingSourceMetadata implementation or [ModelBinder] subclass on a custom service.
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForProperty<Person>(nameof(Person.Service))
|
||||
.BindingDetails(binding => binding.BindingSource = BindingSource.Services);
|
||||
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter-name",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(Person),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext();
|
||||
testContext.MetadataProvider = metadataProvider;
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
var person = Assert.IsType<Person>(modelBindingResult.Model);
|
||||
Assert.NotNull(person.Service);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
Assert.Empty(modelState);
|
||||
}
|
||||
|
||||
// [FromServices] cannot be associated with a type. But a [FromServices] or [ModelBinder] subclass or custom
|
||||
// IBindingSourceMetadata implementation might not have the same restriction. Make sure the metadata is honored
|
||||
// when such an attribute is associated with an action parameter's type.
|
||||
[Theory]
|
||||
[MemberData(
|
||||
nameof(BinderTypeBasedModelBinderIntegrationTest.NullAndEmptyBindingInfo),
|
||||
MemberType = typeof(BinderTypeBasedModelBinderIntegrationTest))]
|
||||
public async Task FromServicesOnParameterType_WithData_Succeeds(BindingInfo bindingInfo)
|
||||
{
|
||||
// Arrange
|
||||
// Similar to a custom IBindingSourceMetadata implementation or [ModelBinder] subclass on a custom service.
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
metadataProvider
|
||||
.ForType<JsonOutputFormatter>()
|
||||
.BindingDetails(binding => binding.BindingSource = BindingSource.Services);
|
||||
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter-name",
|
||||
BindingInfo = bindingInfo,
|
||||
ParameterType = typeof(JsonOutputFormatter),
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext();
|
||||
testContext.MetadataProvider = metadataProvider;
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
Assert.IsType<JsonOutputFormatter>(modelBindingResult.Model);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
Assert.Empty(modelState);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue