Restore `ModelMetadata.PropertyName != null` behaviour
- #7413 part 2 of 2 - add `ModelMetadata.Name` and `ParameterName` - use `Name` instead of `PropertyName` in most cases - update `ModelMetadata.ContainerType` and other property use - choose using `MetadataKind` almost everywhere; support all possibilties - usually parameter metadata was possible but not handled - worst case was one or two potential NREs, especially `ContainerType.*` dereferences - improve `MvcCoreLoggerExtensions` metadata handling - add three new debug messages, one for type metadata and two for parameter metadata - update `ModelMetadata.ContainerMetadata`, `ContainerType` and `PropertyName` doc comments - no changes needed in Microsoft.AspNetCore.Mvc.ViewFeatures because parameters aren't viewed nits: - add missing `TestModelMetadataProvider.ForParameter(...)` method - remove unused `EmptyModelMetadataProvider` instances in `ModelMetadataTest` - refactor `ModelValidationResultComparer` out of DataAnnotationsModelValidatorTest` - take VS suggestions, mostly related to variable inlining and object initializers
This commit is contained in:
parent
f8e315d03d
commit
fc3a815e57
|
|
@ -38,12 +38,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the container type of this metadata if it represents a property, otherwise <c>null</c>.
|
/// Gets the type containing the property if this metadata is for a property; <see langword="null"/> otherwise.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Type ContainerType => Identity.ContainerType;
|
public Type ContainerType => Identity.ContainerType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the metadata of the container type that the current instance is part of.
|
/// Gets the metadata for <see cref="ContainerType"/> if this metadata is for a property;
|
||||||
|
/// <see langword="null"/> otherwise.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual ModelMetadata ContainerMetadata
|
public virtual ModelMetadata ContainerMetadata
|
||||||
{
|
{
|
||||||
|
|
@ -64,9 +65,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public Type ModelType => Identity.ModelType;
|
public Type ModelType => Identity.ModelType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the property name represented by the current instance.
|
/// Gets the name of the parameter or property if this metadata is for a parameter or property;
|
||||||
|
/// <see langword="null"/> otherwise i.e. if this is the metadata for a type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PropertyName => Identity.Name;
|
public string Name => Identity.Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the parameter if this metadata is for a parameter; <see langword="null"/> otherwise.
|
||||||
|
/// </summary>
|
||||||
|
public string ParameterName => MetadataKind == ModelMetadataKind.Parameter ? Identity.Name : null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the property if this metadata is for a property; <see langword="null"/> otherwise.
|
||||||
|
/// </summary>
|
||||||
|
public string PropertyName => MetadataKind == ModelMetadataKind.Property ? Identity.Name : null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the key for the current instance.
|
/// Gets the key for the current instance.
|
||||||
|
|
@ -384,12 +396,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <see cref="GetDisplayName()"/> will return the first of the following expressions which has a
|
/// <see cref="GetDisplayName()"/> will return the first of the following expressions which has a
|
||||||
/// non-<c>null</c> value: <c>DisplayName</c>, <c>PropertyName</c>, <c>ModelType.Name</c>.
|
/// non-<see langword="null"/> value: <see cref="DisplayName"/>, <see cref="Name"/>, or <c>ModelType.Name</c>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <returns>The display name.</returns>
|
/// <returns>The display name.</returns>
|
||||||
public string GetDisplayName()
|
public string GetDisplayName()
|
||||||
{
|
{
|
||||||
return DisplayName ?? PropertyName ?? ModelType.Name;
|
return DisplayName ?? Name ?? ModelType.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -470,13 +482,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
private string DebuggerToString()
|
private string DebuggerToString()
|
||||||
{
|
{
|
||||||
if (Identity.MetadataKind == ModelMetadataKind.Type)
|
switch (MetadataKind)
|
||||||
{
|
{
|
||||||
return $"ModelMetadata (Type: '{ModelType.Name}')";
|
case ModelMetadataKind.Parameter:
|
||||||
}
|
return $"ModelMetadata (Parameter: '{ParameterName}' Type: '{ModelType.Name}')";
|
||||||
else
|
case ModelMetadataKind.Property:
|
||||||
{
|
return $"ModelMetadata (Property: '{ContainerType.Name}.{PropertyName}' Type: '{ModelType.Name}')";
|
||||||
return $"ModelMetadata (Property: '{ContainerType.Name}.{PropertyName}' Type: '{ModelType.Name}')";
|
case ModelMetadataKind.Type:
|
||||||
|
return $"ModelMetadata (Type: '{ModelType.Name}')";
|
||||||
|
default:
|
||||||
|
return $"Unsupported MetadataKind '{MetadataKind}'.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This method allows adding the <paramref name="exception"/> to the current <see cref="ModelStateDictionary"/>
|
/// This method allows adding the <paramref name="exception"/> to the current <see cref="ModelStateDictionary"/>
|
||||||
/// when <see cref="ModelMetadata"/> is not available or the exact <paramref name="exception"/>
|
/// when <see cref="ModelMetadata"/> is not available or the exact <paramref name="exception"/>
|
||||||
/// must be maintained for later use (even if it is for example a <see cref="FormatException"/>).
|
/// must be maintained for later use (even if it is for example a <see cref="FormatException"/>).
|
||||||
/// Where <see cref="ModelMetadata"/> is available, use <see cref="AddModelError(string, Exception, ModelMetadata)"/> instead.
|
/// Where <see cref="ModelMetadata"/> is available, use <see cref="AddModelError(string, Exception, ModelMetadata)"/> instead.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
|
|
||||||
|
|
@ -620,7 +620,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
ModelMetadata = metadata,
|
ModelMetadata = metadata,
|
||||||
BinderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName,
|
BinderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName,
|
||||||
BindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource,
|
BindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource,
|
||||||
PropertyName = propertyName ?? metadata.PropertyName
|
PropertyName = propertyName ?? metadata.Name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters.Internal;
|
using Microsoft.AspNetCore.Mvc.Formatters.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
@ -104,7 +105,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private static readonly Action<ILogger, MethodInfo, Exception> _unableToInferParameterSources;
|
private static readonly Action<ILogger, MethodInfo, Exception> _unableToInferParameterSources;
|
||||||
private static readonly Action<ILogger, IModelBinderProvider[], Exception> _registeredModelBinderProviders;
|
private static readonly Action<ILogger, IModelBinderProvider[], Exception> _registeredModelBinderProviders;
|
||||||
private static readonly Action<ILogger, string, Type, string, Type, Exception> _foundNoValueForPropertyInRequest;
|
private static readonly Action<ILogger, string, Type, string, Type, Exception> _foundNoValueForPropertyInRequest;
|
||||||
private static readonly Action<ILogger, string, string, Type, Exception> _foundNoValueInRequest;
|
private static readonly Action<ILogger, string, string, Type, Exception> _foundNoValueForParameterInRequest;
|
||||||
|
private static readonly Action<ILogger, string, Type, Exception> _foundNoValueInRequest;
|
||||||
private static readonly Action<ILogger, string, Type, Exception> _noPublicSettableProperties;
|
private static readonly Action<ILogger, string, Type, Exception> _noPublicSettableProperties;
|
||||||
private static readonly Action<ILogger, Type, Exception> _cannotBindToComplexType;
|
private static readonly Action<ILogger, Type, Exception> _cannotBindToComplexType;
|
||||||
private static readonly Action<ILogger, string, Type, Exception> _cannotBindToFilesCollectionDueToUnsupportedContentType;
|
private static readonly Action<ILogger, string, Type, Exception> _cannotBindToFilesCollectionDueToUnsupportedContentType;
|
||||||
|
|
@ -115,6 +117,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private static readonly Action<ILogger, string, string, string, string, string, string, Exception> _attemptingToBindCollectionUsingIndices;
|
private static readonly Action<ILogger, string, string, string, string, string, string, Exception> _attemptingToBindCollectionUsingIndices;
|
||||||
private static readonly Action<ILogger, string, string, string, string, string, string, Exception> _attemptingToBindCollectionOfKeyValuePair;
|
private static readonly Action<ILogger, string, string, string, string, string, string, Exception> _attemptingToBindCollectionOfKeyValuePair;
|
||||||
private static readonly Action<ILogger, string, string, string, Exception> _noKeyValueFormatForDictionaryModelBinder;
|
private static readonly Action<ILogger, string, string, string, Exception> _noKeyValueFormatForDictionaryModelBinder;
|
||||||
|
private static readonly Action<ILogger, string, Type, string, Exception> _attemptingToBindParameterModel;
|
||||||
|
private static readonly Action<ILogger, string, Type, Exception> _doneAttemptingToBindParameterModel;
|
||||||
private static readonly Action<ILogger, Type, string, Type, string, Exception> _attemptingToBindPropertyModel;
|
private static readonly Action<ILogger, Type, string, Type, string, Exception> _attemptingToBindPropertyModel;
|
||||||
private static readonly Action<ILogger, Type, string, Type, Exception> _doneAttemptingToBindPropertyModel;
|
private static readonly Action<ILogger, Type, string, Type, Exception> _doneAttemptingToBindPropertyModel;
|
||||||
private static readonly Action<ILogger, Type, string, Exception> _attemptingToBindModel;
|
private static readonly Action<ILogger, Type, string, Exception> _attemptingToBindModel;
|
||||||
|
|
@ -475,7 +479,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
15,
|
15,
|
||||||
"Could not find a value in the request with name '{ModelName}' for binding property '{PropertyContainerType}.{ModelFieldName}' of type '{ModelType}'.");
|
"Could not find a value in the request with name '{ModelName}' for binding property '{PropertyContainerType}.{ModelFieldName}' of type '{ModelType}'.");
|
||||||
|
|
||||||
_foundNoValueInRequest = LoggerMessage.Define<string, string, Type>(
|
_foundNoValueForParameterInRequest = LoggerMessage.Define<string, string, Type>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
16,
|
16,
|
||||||
"Could not find a value in the request with name '{ModelName}' for binding parameter '{ModelFieldName}' of type '{ModelType}'.");
|
"Could not find a value in the request with name '{ModelName}' for binding parameter '{ModelFieldName}' of type '{ModelType}'.");
|
||||||
|
|
@ -610,6 +614,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
43,
|
43,
|
||||||
"Could not create a binder for type '{ModelType}' as this binder only supports 'System.String' type or a collection of 'System.String'.");
|
"Could not create a binder for type '{ModelType}' as this binder only supports 'System.String' type or a collection of 'System.String'.");
|
||||||
|
|
||||||
|
_attemptingToBindParameterModel = LoggerMessage.Define<string, Type, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
44,
|
||||||
|
"Attempting to bind parameter '{ParameterName}' of type '{ModelType}' using the name '{ModelName}' in request data ...");
|
||||||
|
|
||||||
|
_doneAttemptingToBindParameterModel = LoggerMessage.Define<string, Type>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
45,
|
||||||
|
"Done attempting to bind parameter '{ParameterName}' of type '{ModelType}'.");
|
||||||
|
|
||||||
|
_foundNoValueInRequest = LoggerMessage.Define<string, Type>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
46,
|
||||||
|
"Could not find a value in the request with name '{ModelName}' of type '{ModelType}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable<IOutputFormatter> outputFormatters)
|
public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable<IOutputFormatter> outputFormatters)
|
||||||
|
|
@ -1157,26 +1176,32 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelMetadata = bindingContext.ModelMetadata;
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
var isProperty = modelMetadata.ContainerType != null;
|
switch (modelMetadata.MetadataKind)
|
||||||
|
|
||||||
if (isProperty)
|
|
||||||
{
|
{
|
||||||
_foundNoValueForPropertyInRequest(
|
case ModelMetadataKind.Parameter:
|
||||||
logger,
|
_foundNoValueForParameterInRequest(
|
||||||
bindingContext.ModelName,
|
logger,
|
||||||
modelMetadata.ContainerType,
|
bindingContext.ModelName,
|
||||||
modelMetadata.PropertyName,
|
modelMetadata.ParameterName,
|
||||||
bindingContext.ModelType,
|
bindingContext.ModelType,
|
||||||
null);
|
null);
|
||||||
}
|
break;
|
||||||
else
|
case ModelMetadataKind.Property:
|
||||||
{
|
_foundNoValueForPropertyInRequest(
|
||||||
_foundNoValueInRequest(
|
logger,
|
||||||
logger,
|
bindingContext.ModelName,
|
||||||
bindingContext.ModelName,
|
modelMetadata.ContainerType,
|
||||||
modelMetadata.PropertyName,
|
modelMetadata.PropertyName,
|
||||||
bindingContext.ModelType,
|
bindingContext.ModelType,
|
||||||
null);
|
null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Type:
|
||||||
|
_foundNoValueInRequest(
|
||||||
|
logger,
|
||||||
|
bindingContext.ModelName,
|
||||||
|
bindingContext.ModelType,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1218,89 +1243,237 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelMetadata = bindingContext.ModelMetadata;
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
var isProperty = modelMetadata.ContainerType != null;
|
switch (modelMetadata.MetadataKind)
|
||||||
|
|
||||||
if (isProperty)
|
|
||||||
{
|
{
|
||||||
_attemptingToBindPropertyModel(
|
case ModelMetadataKind.Parameter:
|
||||||
logger,
|
_attemptingToBindParameterModel(
|
||||||
modelMetadata.ContainerType,
|
logger,
|
||||||
modelMetadata.PropertyName,
|
modelMetadata.ParameterName,
|
||||||
modelMetadata.ModelType,
|
modelMetadata.ModelType,
|
||||||
bindingContext.ModelName,
|
bindingContext.ModelName,
|
||||||
null);
|
null);
|
||||||
}
|
break;
|
||||||
else
|
case ModelMetadataKind.Property:
|
||||||
{
|
_attemptingToBindPropertyModel(
|
||||||
_attemptingToBindModel(logger, bindingContext.ModelType, bindingContext.ModelName, null);
|
logger,
|
||||||
|
modelMetadata.ContainerType,
|
||||||
|
modelMetadata.PropertyName,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
bindingContext.ModelName,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Type:
|
||||||
|
_attemptingToBindModel(logger, bindingContext.ModelType, bindingContext.ModelName, null);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DoneAttemptingToBindModel(this ILogger logger, ModelBindingContext bindingContext)
|
public static void DoneAttemptingToBindModel(this ILogger logger, ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
|
if (!logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var modelMetadata = bindingContext.ModelMetadata;
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
var isProperty = modelMetadata.ContainerType != null;
|
switch (modelMetadata.MetadataKind)
|
||||||
|
|
||||||
if (isProperty)
|
|
||||||
{
|
{
|
||||||
_doneAttemptingToBindPropertyModel(
|
case ModelMetadataKind.Parameter:
|
||||||
logger,
|
_doneAttemptingToBindParameterModel(
|
||||||
modelMetadata.ContainerType,
|
logger,
|
||||||
modelMetadata.PropertyName,
|
modelMetadata.ParameterName,
|
||||||
modelMetadata.ModelType,
|
modelMetadata.ModelType,
|
||||||
null);
|
null);
|
||||||
}
|
break;
|
||||||
else
|
case ModelMetadataKind.Property:
|
||||||
{
|
_doneAttemptingToBindPropertyModel(
|
||||||
_doneAttemptingToBindModel(logger, bindingContext.ModelType, bindingContext.ModelName, null);
|
logger,
|
||||||
|
modelMetadata.ContainerType,
|
||||||
|
modelMetadata.PropertyName,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Type:
|
||||||
|
_doneAttemptingToBindModel(logger, bindingContext.ModelType, bindingContext.ModelName, null);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AttemptingToBindParameterOrProperty(this ILogger logger, ParameterDescriptor parameter, ModelBindingContext bindingContext)
|
public static void AttemptingToBindParameterOrProperty(
|
||||||
|
this ILogger logger,
|
||||||
|
ParameterDescriptor parameter,
|
||||||
|
ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (parameter is ControllerBoundPropertyDescriptor propertyDescriptor)
|
if (!logger.IsEnabled(LogLevel.Debug))
|
||||||
{
|
{
|
||||||
_attemptingToBindProperty(logger, propertyDescriptor.PropertyInfo.DeclaringType, parameter.Name, bindingContext.ModelType, null);
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
|
switch (modelMetadata.MetadataKind)
|
||||||
{
|
{
|
||||||
_attemptingToBindParameter(logger, parameter.Name, bindingContext.ModelType, null);
|
case ModelMetadataKind.Parameter:
|
||||||
|
_attemptingToBindParameter(logger, modelMetadata.ParameterName, modelMetadata.ModelType, null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Property:
|
||||||
|
_attemptingToBindProperty(
|
||||||
|
logger,
|
||||||
|
modelMetadata.ContainerType,
|
||||||
|
modelMetadata.PropertyName,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Type:
|
||||||
|
if (parameter is ControllerParameterDescriptor parameterDescriptor)
|
||||||
|
{
|
||||||
|
_attemptingToBindParameter(
|
||||||
|
logger,
|
||||||
|
parameterDescriptor.ParameterInfo.Name,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Likely binding a page handler parameter. Due to various special cases, parameter.Name may
|
||||||
|
// be empty. No way to determine actual name.
|
||||||
|
_attemptingToBindParameter(logger, parameter.Name, modelMetadata.ModelType, null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DoneAttemptingToBindParameterOrProperty(this ILogger logger, ParameterDescriptor parameter, ModelBindingContext bindingContext)
|
public static void DoneAttemptingToBindParameterOrProperty(
|
||||||
|
this ILogger logger,
|
||||||
|
ParameterDescriptor parameter,
|
||||||
|
ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (parameter is ControllerBoundPropertyDescriptor propertyDescriptor)
|
if (!logger.IsEnabled(LogLevel.Debug))
|
||||||
{
|
{
|
||||||
_doneAttemptingToBindProperty(logger, propertyDescriptor.PropertyInfo.DeclaringType, parameter.Name, bindingContext.ModelType, null);
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
|
switch (modelMetadata.MetadataKind)
|
||||||
{
|
{
|
||||||
_doneAttemptingToBindParameter(logger, parameter.Name, bindingContext.ModelType, null);
|
case ModelMetadataKind.Parameter:
|
||||||
|
_doneAttemptingToBindParameter(logger, modelMetadata.ParameterName, modelMetadata.ModelType, null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Property:
|
||||||
|
_doneAttemptingToBindProperty(
|
||||||
|
logger,
|
||||||
|
modelMetadata.ContainerType,
|
||||||
|
modelMetadata.PropertyName,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Type:
|
||||||
|
if (parameter is ControllerParameterDescriptor parameterDescriptor)
|
||||||
|
{
|
||||||
|
_doneAttemptingToBindParameter(
|
||||||
|
logger,
|
||||||
|
parameterDescriptor.ParameterInfo.Name,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Likely binding a page handler parameter. Due to various special cases, parameter.Name may
|
||||||
|
// be empty. No way to determine actual name.
|
||||||
|
_doneAttemptingToBindParameter(logger, parameter.Name, modelMetadata.ModelType, null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AttemptingToValidateParameterOrProperty(this ILogger logger, ParameterDescriptor parameter, ModelBindingContext bindingContext)
|
public static void AttemptingToValidateParameterOrProperty(
|
||||||
|
this ILogger logger,
|
||||||
|
ParameterDescriptor parameter,
|
||||||
|
ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (parameter is ControllerBoundPropertyDescriptor propertyDescriptor)
|
if (!logger.IsEnabled(LogLevel.Debug))
|
||||||
{
|
{
|
||||||
_attemptingToValidateProperty(logger, propertyDescriptor.PropertyInfo.DeclaringType, parameter.Name, bindingContext.ModelType, null);
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
|
switch (modelMetadata.MetadataKind)
|
||||||
{
|
{
|
||||||
_attemptingToValidateParameter(logger, parameter.Name, bindingContext.ModelType, null);
|
case ModelMetadataKind.Parameter:
|
||||||
|
_attemptingToValidateParameter(logger, modelMetadata.ParameterName, modelMetadata.ModelType, null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Property:
|
||||||
|
_attemptingToValidateProperty(
|
||||||
|
logger,
|
||||||
|
modelMetadata.ContainerType,
|
||||||
|
modelMetadata.PropertyName,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Type:
|
||||||
|
if (parameter is ControllerParameterDescriptor parameterDescriptor)
|
||||||
|
{
|
||||||
|
_attemptingToValidateParameter(
|
||||||
|
logger,
|
||||||
|
parameterDescriptor.ParameterInfo.Name,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Likely binding a page handler parameter. Due to various special cases, parameter.Name may
|
||||||
|
// be empty. No way to determine actual name. This case is less likely than for binding logging
|
||||||
|
// (above). Should occur only with a legacy IModelMetadataProvider implementation.
|
||||||
|
_attemptingToValidateParameter(logger, parameter.Name, modelMetadata.ModelType, null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DoneAttemptingToValidateParameterOrProperty(this ILogger logger, ParameterDescriptor parameter, ModelBindingContext bindingContext)
|
public static void DoneAttemptingToValidateParameterOrProperty(
|
||||||
|
this ILogger logger,
|
||||||
|
ParameterDescriptor parameter,
|
||||||
|
ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (parameter is ControllerBoundPropertyDescriptor propertyDescriptor)
|
if (!logger.IsEnabled(LogLevel.Debug))
|
||||||
{
|
{
|
||||||
_doneAttemptingToValidateProperty(logger, propertyDescriptor.PropertyInfo.DeclaringType, parameter.Name, bindingContext.ModelType, null);
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
|
switch (modelMetadata.MetadataKind)
|
||||||
{
|
{
|
||||||
_doneAttemptingToValidateParameter(logger, parameter.Name, bindingContext.ModelType, null);
|
case ModelMetadataKind.Parameter:
|
||||||
|
_doneAttemptingToValidateParameter(
|
||||||
|
logger,
|
||||||
|
modelMetadata.ParameterName,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Property:
|
||||||
|
_doneAttemptingToValidateProperty(
|
||||||
|
logger,
|
||||||
|
modelMetadata.ContainerType,
|
||||||
|
modelMetadata.PropertyName,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
case ModelMetadataKind.Type:
|
||||||
|
if (parameter is ControllerParameterDescriptor parameterDescriptor)
|
||||||
|
{
|
||||||
|
_doneAttemptingToValidateParameter(
|
||||||
|
logger,
|
||||||
|
parameterDescriptor.ParameterInfo.Name,
|
||||||
|
modelMetadata.ModelType,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Likely binding a page handler parameter. Due to various special cases, parameter.Name may
|
||||||
|
// be empty. No way to determine actual name. This case is less likely than for binding logging
|
||||||
|
// (above). Should occur only with a legacy IModelMetadataProvider implementation.
|
||||||
|
_doneAttemptingToValidateParameter(logger, parameter.Name, modelMetadata.ModelType, null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
|
|
@ -357,24 +358,32 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
// application developer should know that this was an invalid type to try to bind to.
|
// application developer should know that this was an invalid type to try to bind to.
|
||||||
if (_modelCreator == null)
|
if (_modelCreator == null)
|
||||||
{
|
{
|
||||||
// The following check causes the ComplexTypeModelBinder to NOT participate in binding structs as
|
// The following check causes the ComplexTypeModelBinder to NOT participate in binding structs as
|
||||||
// reflection does not provide information about the implicit parameterless constructor for a struct.
|
// reflection does not provide information about the implicit parameterless constructor for a struct.
|
||||||
// This binder would eventually fail to construct an instance of the struct as the Linq's NewExpression
|
// This binder would eventually fail to construct an instance of the struct as the Linq's NewExpression
|
||||||
// compile fails to construct it.
|
// compile fails to construct it.
|
||||||
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
|
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
|
||||||
if (modelTypeInfo.IsAbstract || modelTypeInfo.GetConstructor(Type.EmptyTypes) == null)
|
if (modelTypeInfo.IsAbstract || modelTypeInfo.GetConstructor(Type.EmptyTypes) == null)
|
||||||
{
|
{
|
||||||
if (bindingContext.IsTopLevelObject)
|
var metadata = bindingContext.ModelMetadata;
|
||||||
|
switch (metadata.MetadataKind)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
case ModelMetadataKind.Parameter:
|
||||||
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_TopLevelObject(modelTypeInfo.FullName));
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForParameter(
|
||||||
|
modelTypeInfo.FullName,
|
||||||
|
metadata.ParameterName));
|
||||||
|
case ModelMetadataKind.Property:
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForProperty(
|
||||||
|
modelTypeInfo.FullName,
|
||||||
|
metadata.PropertyName,
|
||||||
|
bindingContext.ModelMetadata.ContainerType.FullName));
|
||||||
|
case ModelMetadataKind.Type:
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForType(
|
||||||
|
modelTypeInfo.FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForProperty(
|
|
||||||
modelTypeInfo.FullName,
|
|
||||||
bindingContext.ModelName,
|
|
||||||
bindingContext.ModelMetadata.ContainerType.FullName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_modelCreator = Expression
|
_modelCreator = Expression
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
typeof(IModelBinderProvider).FullName));
|
typeof(IModelBinderProvider).FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
IModelBinder binder;
|
if (TryGetCachedBinder(context.Metadata, context.CacheToken, out var binder))
|
||||||
if (TryGetCachedBinder(context.Metadata, context.CacheToken, out binder))
|
|
||||||
{
|
{
|
||||||
return binder;
|
return binder;
|
||||||
}
|
}
|
||||||
|
|
@ -106,8 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
// so that all intermediate results can be cached.
|
// so that all intermediate results can be cached.
|
||||||
private IModelBinder CreateBinderCoreCached(DefaultModelBinderProviderContext providerContext, object token)
|
private IModelBinder CreateBinderCoreCached(DefaultModelBinderProviderContext providerContext, object token)
|
||||||
{
|
{
|
||||||
IModelBinder binder;
|
if (TryGetCachedBinder(providerContext.Metadata, token, out var binder))
|
||||||
if (TryGetCachedBinder(providerContext.Metadata, token, out binder))
|
|
||||||
{
|
{
|
||||||
return binder;
|
return binder;
|
||||||
}
|
}
|
||||||
|
|
@ -145,8 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
// PlaceholderBinder because that would result in lots of unnecessary indirection and allocations.
|
// PlaceholderBinder because that would result in lots of unnecessary indirection and allocations.
|
||||||
var visited = providerContext.Visited;
|
var visited = providerContext.Visited;
|
||||||
|
|
||||||
IModelBinder binder;
|
if (visited.TryGetValue(key, out var binder))
|
||||||
if (visited.TryGetValue(key, out binder))
|
|
||||||
{
|
{
|
||||||
if (binder != null)
|
if (binder != null)
|
||||||
{
|
{
|
||||||
|
|
@ -178,8 +175,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the PlaceholderBinder was created, then it means we recursed. Hook it up to the 'real' binder.
|
// If the PlaceholderBinder was created, then it means we recursed. Hook it up to the 'real' binder.
|
||||||
var placeholderBinder = visited[key] as PlaceholderBinder;
|
if (visited[key] is PlaceholderBinder placeholderBinder)
|
||||||
if (placeholderBinder != null)
|
|
||||||
{
|
{
|
||||||
// It's also possible that user code called into `CreateBinder` but then returned null, we don't
|
// It's also possible that user code called into `CreateBinder` but then returned null, we don't
|
||||||
// want to create something that will null-ref later so just hook this up to the no-op binder.
|
// want to create something that will null-ref later so just hook this up to the no-op binder.
|
||||||
|
|
@ -347,13 +343,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (_metadata.MetadataKind == ModelMetadataKind.Type)
|
switch (_metadata.MetadataKind)
|
||||||
{
|
{
|
||||||
return $"{_token} (Type: '{_metadata.ModelType.Name}')";
|
case ModelMetadataKind.Parameter:
|
||||||
}
|
return $"{_token} (Parameter: '{_metadata.ParameterName}' Type: '{_metadata.ModelType.Name}')";
|
||||||
else
|
case ModelMetadataKind.Property:
|
||||||
{
|
return $"{_token} (Property: '{_metadata.ContainerType.Name}.{_metadata.PropertyName}' " +
|
||||||
return $"{_token} (Property: '{_metadata.ContainerType.Name}.{_metadata.PropertyName}' Type: '{_metadata.ModelType.Name}')";
|
$"Type: '{_metadata.ModelType.Name}')";
|
||||||
|
case ModelMetadataKind.Type:
|
||||||
|
return $"{_token} (Type: '{_metadata.ModelType.Name}')";
|
||||||
|
default:
|
||||||
|
return $"Unsupported MetadataKind '{_metadata.MetadataKind}'.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
ModelState = actionContext.ModelState;
|
ModelState = actionContext.ModelState;
|
||||||
CurrentPath = new ValidationStack();
|
CurrentPath = new ValidationStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IModelValidatorProvider ValidatorProvider { get; }
|
protected IModelValidatorProvider ValidatorProvider { get; }
|
||||||
protected IModelMetadataProvider MetadataProvider { get; }
|
protected IModelMetadataProvider MetadataProvider { get; }
|
||||||
protected ValidatorCache Cache { get; }
|
protected ValidatorCache Cache { get; }
|
||||||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
protected IValidationStrategy Strategy { get; set; }
|
protected IValidationStrategy Strategy { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates whether validation of a complex type should be performed if validation fails for any of its children. The default behavior is false.
|
/// Indicates whether validation of a complex type should be performed if validation fails for any of its children. The default behavior is false.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ValidateComplexTypesIfChildValidationFails { get; set; }
|
public bool ValidateComplexTypesIfChildValidationFails { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -146,11 +146,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
var result = results[i];
|
var result = results[i];
|
||||||
var key = ModelNames.CreatePropertyModelName(Key, result.MemberName);
|
var key = ModelNames.CreatePropertyModelName(Key, result.MemberName);
|
||||||
|
|
||||||
// If this is a top-level parameter/property, the key would be empty,
|
// If this is a top-level parameter/property, the key would be empty.
|
||||||
// so use the name of the top-level property
|
// So, use the name of the top-level property/property.
|
||||||
if (string.IsNullOrEmpty(key) && Metadata.PropertyName != null)
|
if (string.IsNullOrEmpty(key) && Metadata.Name != null)
|
||||||
{
|
{
|
||||||
key = Metadata.PropertyName;
|
key = Metadata.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelState.TryAddModelError(key, result.Message);
|
ModelState.TryAddModelError(key, result.Message);
|
||||||
|
|
@ -306,8 +306,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidationStateEntry entry;
|
ValidationState.TryGetValue(model, out var entry);
|
||||||
ValidationState.TryGetValue(model, out entry);
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1273,16 +1273,16 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
|
/// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string ComplexTypeModelBinder_NoParameterlessConstructor_TopLevelObject
|
internal static string ComplexTypeModelBinder_NoParameterlessConstructor_ForType
|
||||||
{
|
{
|
||||||
get => GetString("ComplexTypeModelBinder_NoParameterlessConstructor_TopLevelObject");
|
get => GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForType");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
|
/// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatComplexTypeModelBinder_NoParameterlessConstructor_TopLevelObject(object p0)
|
internal static string FormatComplexTypeModelBinder_NoParameterlessConstructor_ForType(object p0)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ComplexTypeModelBinder_NoParameterlessConstructor_TopLevelObject"), p0);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForType"), p0);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, set the '{1}' property to a non-null value in the '{2}' constructor.
|
/// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, set the '{1}' property to a non-null value in the '{2}' constructor.
|
||||||
|
|
@ -1438,6 +1438,20 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
internal static string FormatApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional(object p0, object p1)
|
internal static string FormatApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional(object p0, object p1)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional"), p0, p1);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional"), p0, p1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the '{1}' parameter a non-null default value.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ComplexTypeModelBinder_NoParameterlessConstructor_ForParameter
|
||||||
|
{
|
||||||
|
get => GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForParameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the '{1}' parameter a non-null default value.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatComplexTypeModelBinder_NoParameterlessConstructor_ForParameter(object p0, object p1)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForParameter"), p0, p1);
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,7 @@
|
||||||
<value>'{0}' and '{1}' are out of bounds for the string.</value>
|
<value>'{0}' and '{1}' are out of bounds for the string.</value>
|
||||||
<comment>'{0}' and '{1}' are the parameters which combine to be out of bounds.</comment>
|
<comment>'{0}' and '{1}' are the parameters which combine to be out of bounds.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="ComplexTypeModelBinder_NoParameterlessConstructor_TopLevelObject" xml:space="preserve">
|
<data name="ComplexTypeModelBinder_NoParameterlessConstructor_ForType" xml:space="preserve">
|
||||||
<value>Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.</value>
|
<value>Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ComplexTypeModelBinder_NoParameterlessConstructor_ForProperty" xml:space="preserve">
|
<data name="ComplexTypeModelBinder_NoParameterlessConstructor_ForProperty" xml:space="preserve">
|
||||||
|
|
@ -436,4 +436,7 @@
|
||||||
<data name="ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional" xml:space="preserve">
|
<data name="ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional" xml:space="preserve">
|
||||||
<value>Assembly '{0}' declared as a related assembly by assembly '{1}' cannot define additional related assemblies.</value>
|
<value>Assembly '{0}' declared as a related assembly by assembly '{1}' cannot define additional related assemblies.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ComplexTypeModelBinder_NoParameterlessConstructor_ForParameter" xml:space="preserve">
|
||||||
|
<value>Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the '{1}' parameter a non-null default value.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadata = validationContext.ModelMetadata;
|
var metadata = validationContext.ModelMetadata;
|
||||||
var memberName = metadata.PropertyName;
|
var memberName = metadata.Name;
|
||||||
var container = validationContext.Container;
|
var container = validationContext.Container;
|
||||||
|
|
||||||
var context = new ValidationContext(
|
var context = new ValidationContext(
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
var messageProvider = modelMetadata.ModelBindingMessageProvider;
|
var messageProvider = modelMetadata.ModelBindingMessageProvider;
|
||||||
var name = modelMetadata.DisplayName ?? modelMetadata.PropertyName;
|
var name = modelMetadata.DisplayName ?? modelMetadata.Name;
|
||||||
if (name == null)
|
if (name == null)
|
||||||
{
|
{
|
||||||
return messageProvider.NonPropertyValueMustBeANumberAccessor();
|
return messageProvider.NonPropertyValueMustBeANumberAccessor();
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
return Enumerable.Empty<ModelValidationResult>();
|
return Enumerable.Empty<ModelValidationResult>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var validatable = model as IValidatableObject;
|
if (!(model is IValidatableObject validatable))
|
||||||
if (validatable == null)
|
|
||||||
{
|
{
|
||||||
var message = Resources.FormatValidatableObjectAdapter_IncompatibleType(
|
var message = Resources.FormatValidatableObjectAdapter_IncompatibleType(
|
||||||
typeof(IValidatableObject).Name,
|
typeof(IValidatableObject).Name,
|
||||||
|
|
@ -39,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
items: null)
|
items: null)
|
||||||
{
|
{
|
||||||
DisplayName = context.ModelMetadata.GetDisplayName(),
|
DisplayName = context.ModelMetadata.GetDisplayName(),
|
||||||
MemberName = context.ModelMetadata.PropertyName,
|
MemberName = context.ModelMetadata.Name,
|
||||||
};
|
};
|
||||||
|
|
||||||
return ConvertResults(validatable.Validate(validationContext));
|
return ConvertResults(validatable.Validate(validationContext));
|
||||||
|
|
@ -66,4 +65,4 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -24,10 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[InlineData(typeof(int))]
|
[InlineData(typeof(int))]
|
||||||
public void IsComplexType_ReturnsFalseForSimpleTypes(Type type)
|
public void IsComplexType_ReturnsFalseForSimpleTypes(Type type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange & Act
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var modelMetadata = new TestModelMetadata(type);
|
var modelMetadata = new TestModelMetadata(type);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -41,10 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[InlineData(typeof(Nullable<IsComplexTypeModel>))]
|
[InlineData(typeof(Nullable<IsComplexTypeModel>))]
|
||||||
public void IsComplexType_ReturnsTrueForComplexTypes(Type type)
|
public void IsComplexType_ReturnsTrueForComplexTypes(Type type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange & Act
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var modelMetadata = new TestModelMetadata(type);
|
var modelMetadata = new TestModelMetadata(type);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -106,10 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[InlineData(typeof(JustEnumerable))]
|
[InlineData(typeof(JustEnumerable))]
|
||||||
public void IsCollectionType_ReturnsFalseForNonCollectionTypes(Type type)
|
public void IsCollectionType_ReturnsFalseForNonCollectionTypes(Type type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange & Act
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var modelMetadata = new TestModelMetadata(type);
|
var modelMetadata = new TestModelMetadata(type);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -120,10 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[MemberData(nameof(CollectionAndEnumerableData))]
|
[MemberData(nameof(CollectionAndEnumerableData))]
|
||||||
public void IsCollectionType_ReturnsTrueForCollectionTypes(Type type)
|
public void IsCollectionType_ReturnsTrueForCollectionTypes(Type type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange & Act
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var modelMetadata = new TestModelMetadata(type);
|
var modelMetadata = new TestModelMetadata(type);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -134,10 +123,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[MemberData(nameof(NonCollectionNonEnumerableData))]
|
[MemberData(nameof(NonCollectionNonEnumerableData))]
|
||||||
public void IsEnumerableType_ReturnsFalseForNonEnumerableTypes(Type type)
|
public void IsEnumerableType_ReturnsFalseForNonEnumerableTypes(Type type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange & Act
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var modelMetadata = new TestModelMetadata(type);
|
var modelMetadata = new TestModelMetadata(type);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -151,10 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[InlineData(typeof(JustEnumerable))]
|
[InlineData(typeof(JustEnumerable))]
|
||||||
public void IsEnumerableType_ReturnsTrueForEnumerableTypes(Type type)
|
public void IsEnumerableType_ReturnsTrueForEnumerableTypes(Type type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange & Act
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var modelMetadata = new TestModelMetadata(type);
|
var modelMetadata = new TestModelMetadata(type);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -173,10 +156,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[InlineData(typeof(Nullable<IsComplexTypeModel>), true)]
|
[InlineData(typeof(Nullable<IsComplexTypeModel>), true)]
|
||||||
public void IsNullableValueType_ReturnsExpectedValue(Type modelType, bool expected)
|
public void IsNullableValueType_ReturnsExpectedValue(Type modelType, bool expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange & Act
|
||||||
var modelMetadata = new TestModelMetadata(modelType);
|
var modelMetadata = new TestModelMetadata(modelType);
|
||||||
|
|
||||||
// Act & Assert
|
// Assert
|
||||||
Assert.Equal(expected, modelMetadata.IsNullableValueType);
|
Assert.Equal(expected, modelMetadata.IsNullableValueType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,10 +175,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[InlineData(typeof(Nullable<IsComplexTypeModel>), true)]
|
[InlineData(typeof(Nullable<IsComplexTypeModel>), true)]
|
||||||
public void IsReferenceOrNullableType_ReturnsExpectedValue(Type modelType, bool expected)
|
public void IsReferenceOrNullableType_ReturnsExpectedValue(Type modelType, bool expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange & Act
|
||||||
var modelMetadata = new TestModelMetadata(modelType);
|
var modelMetadata = new TestModelMetadata(modelType);
|
||||||
|
|
||||||
// Act & Assert
|
// Assert
|
||||||
Assert.Equal(expected, modelMetadata.IsReferenceOrNullableType);
|
Assert.Equal(expected, modelMetadata.IsReferenceOrNullableType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,13 +194,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
[InlineData(typeof(Nullable<IsComplexTypeModel>), typeof(IsComplexTypeModel))]
|
[InlineData(typeof(Nullable<IsComplexTypeModel>), typeof(IsComplexTypeModel))]
|
||||||
public void UnderlyingOrModelType_ReturnsExpectedValue(Type modelType, Type expected)
|
public void UnderlyingOrModelType_ReturnsExpectedValue(Type modelType, Type expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange & Act
|
||||||
var modelMetadata = new TestModelMetadata(modelType);
|
var modelMetadata = new TestModelMetadata(modelType);
|
||||||
|
|
||||||
// Act & Assert
|
// Assert
|
||||||
Assert.Equal(expected, modelMetadata.UnderlyingOrModelType);
|
Assert.Equal(expected, modelMetadata.UnderlyingOrModelType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ElementType
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(typeof(object))]
|
[InlineData(typeof(object))]
|
||||||
[InlineData(typeof(int))]
|
[InlineData(typeof(int))]
|
||||||
|
|
@ -257,13 +242,86 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.Equal(expected, elementType);
|
Assert.Equal(expected, elementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerType
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContainerType_IsNull_ForType()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var metadata = new TestModelMetadata(typeof(int));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(metadata.ContainerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContainerType_IsNull_ForParameter()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var method = typeof(CollectionImplementation).GetMethod(nameof(CollectionImplementation.Add));
|
||||||
|
var parameter = method.GetParameters()[0]; // Add(string item)
|
||||||
|
var metadata = new TestModelMetadata(parameter);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(metadata.ContainerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContainerType_ReturnExpectedMetadata_ForProperty()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var metadata = new TestModelMetadata(typeof(int), nameof(string.Length), typeof(string));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(typeof(string), metadata.ContainerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name / ParameterName / PropertyName
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Names_ReturnExpectedMetadata_ForType()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var metadata = new TestModelMetadata(typeof(int));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(metadata.Name);
|
||||||
|
Assert.Null(metadata.ParameterName);
|
||||||
|
Assert.Null(metadata.PropertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Names_ReturnExpectedMetadata_ForParameter()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var method = typeof(CollectionImplementation).GetMethod(nameof(CollectionImplementation.Add));
|
||||||
|
var parameter = method.GetParameters()[0]; // Add(string item)
|
||||||
|
var metadata = new TestModelMetadata(parameter);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("item", metadata.Name);
|
||||||
|
Assert.Equal("item", metadata.ParameterName);
|
||||||
|
Assert.Null(metadata.PropertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Names_ReturnExpectedMetadata_ForProperty()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var metadata = new TestModelMetadata(typeof(int), nameof(string.Length), typeof(string));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(nameof(string.Length), metadata.Name);
|
||||||
|
Assert.Null(metadata.ParameterName);
|
||||||
|
Assert.Equal(nameof(string.Length), metadata.PropertyName);
|
||||||
|
}
|
||||||
|
|
||||||
// GetDisplayName()
|
// GetDisplayName()
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetDisplayName_ReturnsDisplayName_IfSet()
|
public void GetDisplayName_ReturnsDisplayName_IfSet()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
var metadata = new TestModelMetadata(typeof(int), "Length", typeof(string));
|
var metadata = new TestModelMetadata(typeof(int), "Length", typeof(string));
|
||||||
metadata.SetDisplayName("displayName");
|
metadata.SetDisplayName("displayName");
|
||||||
|
|
||||||
|
|
@ -274,11 +332,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.Equal("displayName", result);
|
Assert.Equal("displayName", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDisplayName_ReturnsParameterName_WhenSetAndDisplayNameIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = typeof(CollectionImplementation).GetMethod(nameof(CollectionImplementation.Add));
|
||||||
|
var parameter = method.GetParameters()[0]; // Add(string item)
|
||||||
|
var metadata = new TestModelMetadata(parameter);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = metadata.GetDisplayName();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("item", result);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetDisplayName_ReturnsPropertyName_WhenSetAndDisplayNameIsNull()
|
public void GetDisplayName_ReturnsPropertyName_WhenSetAndDisplayNameIsNull()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
var metadata = new TestModelMetadata(typeof(int), "Length", typeof(string));
|
var metadata = new TestModelMetadata(typeof(int), "Length", typeof(string));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -292,7 +364,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public void GetDisplayName_ReturnsTypeName_WhenPropertyNameAndDisplayNameAreNull()
|
public void GetDisplayName_ReturnsTypeName_WhenPropertyNameAndDisplayNameAreNull()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var provider = new EmptyModelMetadataProvider();
|
|
||||||
var metadata = new TestModelMetadata(typeof(string));
|
var metadata = new TestModelMetadata(typeof(string));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -302,6 +373,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.Equal("String", result);
|
Assert.Equal("String", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Virtual methods and properties that throw NotImplementedException in the abstract class.
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetContainerMetadata_ThrowsNotImplementedException_ByDefault()
|
public void GetContainerMetadata_ThrowsNotImplementedException_ByDefault()
|
||||||
{
|
{
|
||||||
|
|
@ -341,6 +414,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TestModelMetadata(ParameterInfo parameter)
|
||||||
|
: base(ModelMetadataIdentity.ForParameter(parameter))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public TestModelMetadata(Type modelType, string propertyName, Type containerType)
|
public TestModelMetadata(Type modelType, string propertyName, Type containerType)
|
||||||
: base(ModelMetadataIdentity.ForProperty(modelType, propertyName, containerType))
|
: base(ModelMetadataIdentity.ForProperty(modelType, propertyName, containerType))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -971,7 +971,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ModelStateDictionary_AddsCustomErrorMessage_WhenModelStateNotSet_WithNonProperty()
|
public void ModelStateDictionary_AddsCustomErrorMessage_WhenModelStateNotSet_WithParameter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = "Hmm, the supplied value is not valid.";
|
var expected = "Hmm, the supplied value is not valid.";
|
||||||
|
|
@ -981,7 +981,35 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
||||||
var optionsAccessor = new OptionsAccessor();
|
var optionsAccessor = new OptionsAccessor();
|
||||||
optionsAccessor.Value.ModelBindingMessageProvider.SetNonPropertyUnknownValueIsInvalidAccessor(
|
optionsAccessor.Value.ModelBindingMessageProvider.SetNonPropertyUnknownValueIsInvalidAccessor(
|
||||||
() => $"Hmm, the supplied value is not valid.");
|
() => "Hmm, the supplied value is not valid.");
|
||||||
|
|
||||||
|
var method = typeof(string).GetMethod(nameof(string.Copy));
|
||||||
|
var parameter = method.GetParameters()[0]; // Copy(string str)
|
||||||
|
var provider = new DefaultModelMetadataProvider(compositeProvider, optionsAccessor);
|
||||||
|
var metadata = provider.GetMetadataForParameter(parameter);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.TryAddModelError("key", new FormatException(), metadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var entry = Assert.Single(dictionary);
|
||||||
|
Assert.Equal("key", entry.Key);
|
||||||
|
var error = Assert.Single(entry.Value.Errors);
|
||||||
|
Assert.Equal(expected, error.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ModelStateDictionary_AddsCustomErrorMessage_WhenModelStateNotSet_WithType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Hmm, the supplied value is not valid.";
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
|
||||||
|
var bindingMetadataProvider = new DefaultBindingMetadataProvider();
|
||||||
|
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
||||||
|
var optionsAccessor = new OptionsAccessor();
|
||||||
|
optionsAccessor.Value.ModelBindingMessageProvider.SetNonPropertyUnknownValueIsInvalidAccessor(
|
||||||
|
() => "Hmm, the supplied value is not valid.");
|
||||||
|
|
||||||
var provider = new DefaultModelMetadataProvider(compositeProvider, optionsAccessor);
|
var provider = new DefaultModelMetadataProvider(compositeProvider, optionsAccessor);
|
||||||
var metadata = provider.GetMetadataForType(typeof(int));
|
var metadata = provider.GetMetadataForType(typeof(int));
|
||||||
|
|
@ -1058,7 +1086,36 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ModelStateDictionary_AddsCustomErrorMessage_WhenModelStateSet_WithNonProperty()
|
public void ModelStateDictionary_AddsCustomErrorMessage_WhenModelStateSet_WithParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Hmm, the value 'some value' is not valid.";
|
||||||
|
var dictionary = new ModelStateDictionary();
|
||||||
|
dictionary.SetModelValue("key", new string[] { "some value" }, "some value");
|
||||||
|
|
||||||
|
var bindingMetadataProvider = new DefaultBindingMetadataProvider();
|
||||||
|
var compositeProvider = new DefaultCompositeMetadataDetailsProvider(new[] { bindingMetadataProvider });
|
||||||
|
var optionsAccessor = new OptionsAccessor();
|
||||||
|
optionsAccessor.Value.ModelBindingMessageProvider.SetNonPropertyAttemptedValueIsInvalidAccessor(
|
||||||
|
value => $"Hmm, the value '{ value }' is not valid.");
|
||||||
|
|
||||||
|
var method = typeof(string).GetMethod(nameof(string.Copy));
|
||||||
|
var parameter = method.GetParameters()[0]; // Copy(string str)
|
||||||
|
var provider = new DefaultModelMetadataProvider(compositeProvider, optionsAccessor);
|
||||||
|
var metadata = provider.GetMetadataForParameter(parameter);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dictionary.TryAddModelError("key", new FormatException(), metadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var entry = Assert.Single(dictionary);
|
||||||
|
Assert.Equal("key", entry.Key);
|
||||||
|
var error = Assert.Single(entry.Value.Errors);
|
||||||
|
Assert.Equal(expected, error.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ModelStateDictionary_AddsCustomErrorMessage_WhenModelStateSet_WithType()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = "Hmm, the value 'some value' is not valid.";
|
var expected = "Hmm, the value 'some value' is not valid.";
|
||||||
|
|
@ -1477,4 +1534,4 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
public override string Message { get; }
|
public override string Message { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
@ -371,6 +370,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
exception.Message);
|
exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateModel_ForClassWithNoParameterlessConstructor_AsElement_ThrowsException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedMessage = "Could not create an instance of type " +
|
||||||
|
$"'{typeof(ClassWithNoParameterlessConstructor)}'. Model bound complex types must not be abstract " +
|
||||||
|
"or value types and must have a parameterless constructor.";
|
||||||
|
var metadata = GetMetadataForType(typeof(ClassWithNoParameterlessConstructor));
|
||||||
|
var bindingContext = new DefaultModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = metadata,
|
||||||
|
};
|
||||||
|
var binder = CreateBinder(metadata);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => binder.CreateModelPublic(bindingContext));
|
||||||
|
Assert.Equal(expectedMessage, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateModel_ForStructModelType_AsProperty_ThrowsException()
|
public void CreateModel_ForStructModelType_AsProperty_ThrowsException()
|
||||||
{
|
{
|
||||||
|
|
@ -1096,6 +1114,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
public double Y { get; }
|
public double Y { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ClassWithNoParameterlessConstructor
|
||||||
|
{
|
||||||
|
public ClassWithNoParameterlessConstructor(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private class BindingOptionalProperty
|
private class BindingOptionalProperty
|
||||||
{
|
{
|
||||||
[BindingBehavior(BindingBehavior.Optional)]
|
[BindingBehavior(BindingBehavior.Optional)]
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
|
|
@ -157,12 +158,34 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
Assert.Equal(message, error.ErrorMessage);
|
Assert.Equal(message, error.ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
public static TheoryData<ModelMetadata> IntegerModelMetadataDataSet
|
||||||
public async Task BindModel_EmptyValueProviderResult_ReturnsFailed()
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
|
var method = typeof(MetadataClass).GetMethod(nameof(MetadataClass.IsLovely));
|
||||||
|
var parameter = method.GetParameters()[0]; // IsLovely(int parameter)
|
||||||
|
|
||||||
|
return new TheoryData<ModelMetadata>
|
||||||
|
{
|
||||||
|
metadataProvider.GetMetadataForParameter(parameter),
|
||||||
|
metadataProvider.GetMetadataForProperty(typeof(MetadataClass), nameof(MetadataClass.Property)),
|
||||||
|
metadataProvider.GetMetadataForType(typeof(int)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(IntegerModelMetadataDataSet))]
|
||||||
|
public async Task BindModel_EmptyValueProviderResult_ReturnsFailedAndLogsSuccessfully(ModelMetadata metadata)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var bindingContext = GetBindingContext(typeof(int));
|
var bindingContext = GetBindingContext(typeof(int));
|
||||||
var binder = new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance);
|
bindingContext.ModelMetadata = metadata;
|
||||||
|
|
||||||
|
var sink = new TestSink();
|
||||||
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||||
|
var binder = new SimpleTypeModelBinder(typeof(int), loggerFactory);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await binder.BindModelAsync(bindingContext);
|
await binder.BindModelAsync(bindingContext);
|
||||||
|
|
@ -170,6 +193,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(ModelBindingResult.Failed(), bindingContext.Result);
|
Assert.Equal(ModelBindingResult.Failed(), bindingContext.Result);
|
||||||
Assert.Empty(bindingContext.ModelState);
|
Assert.Empty(bindingContext.ModelState);
|
||||||
|
Assert.Equal(2, sink.Writes.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -236,17 +260,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public async Task BindModel_ValidValueProviderResult_ReturnsModel()
|
[MemberData(nameof(IntegerModelMetadataDataSet))]
|
||||||
|
public async Task BindModel_ValidValueProviderResult_ReturnsModelAndLogsSuccessfully(ModelMetadata metadata)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var bindingContext = GetBindingContext(typeof(int));
|
var bindingContext = GetBindingContext(typeof(int));
|
||||||
|
bindingContext.ModelMetadata = metadata;
|
||||||
bindingContext.ValueProvider = new SimpleValueProvider
|
bindingContext.ValueProvider = new SimpleValueProvider
|
||||||
{
|
{
|
||||||
{ "theModelName", "42" }
|
{ "theModelName", "42" }
|
||||||
};
|
};
|
||||||
|
|
||||||
var binder = new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance);
|
var sink = new TestSink();
|
||||||
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||||
|
var binder = new SimpleTypeModelBinder(typeof(int), loggerFactory);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await binder.BindModelAsync(bindingContext);
|
await binder.BindModelAsync(bindingContext);
|
||||||
|
|
@ -255,6 +283,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
Assert.True(bindingContext.Result.IsModelSet);
|
Assert.True(bindingContext.Result.IsModelSet);
|
||||||
Assert.Equal(42, bindingContext.Result.Model);
|
Assert.Equal(42, bindingContext.Result.Model);
|
||||||
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
Assert.True(bindingContext.ModelState.ContainsKey("theModelName"));
|
||||||
|
Assert.Equal(2, sink.Writes.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TheoryData<Type> BiggerNumericTypes
|
public static TheoryData<Type> BiggerNumericTypes
|
||||||
|
|
@ -488,5 +517,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||||
Value2 = 2,
|
Value2 = 2,
|
||||||
MaxValue = int.MaxValue
|
MaxValue = int.MaxValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class MetadataClass
|
||||||
|
{
|
||||||
|
public int Property { get; set; }
|
||||||
|
|
||||||
|
public bool IsLovely(int parameter)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
@ -15,6 +17,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -309,6 +312,107 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.Empty(actionContext.ModelState);
|
Assert.Empty(actionContext.ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TheoryData<RequiredAttribute, ParameterDescriptor, ModelMetadata> EnforcesTopLevelRequiredDataSet
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var attribute = new RequiredAttribute();
|
||||||
|
var bindingInfo = new BindingInfo
|
||||||
|
{
|
||||||
|
BinderModelName = string.Empty,
|
||||||
|
};
|
||||||
|
var parameterDescriptor = new ParameterDescriptor
|
||||||
|
{
|
||||||
|
Name = string.Empty,
|
||||||
|
BindingInfo = bindingInfo,
|
||||||
|
ParameterType = typeof(Person),
|
||||||
|
};
|
||||||
|
|
||||||
|
var method = typeof(Person).GetMethod(nameof(Person.Equals), new[] { typeof(Person) });
|
||||||
|
var parameter = method.GetParameters()[0]; // Equals(Person other)
|
||||||
|
var controllerParameterDescriptor = new ControllerParameterDescriptor
|
||||||
|
{
|
||||||
|
Name = string.Empty,
|
||||||
|
BindingInfo = bindingInfo,
|
||||||
|
ParameterInfo = parameter,
|
||||||
|
ParameterType = typeof(Person),
|
||||||
|
};
|
||||||
|
|
||||||
|
var provider1 = new TestModelMetadataProvider();
|
||||||
|
provider1
|
||||||
|
.ForParameter(parameter)
|
||||||
|
.ValidationDetails(d =>
|
||||||
|
{
|
||||||
|
d.IsRequired = true;
|
||||||
|
d.ValidatorMetadata.Add(attribute);
|
||||||
|
});
|
||||||
|
provider1
|
||||||
|
.ForProperty(typeof(Family), nameof(Family.Mom))
|
||||||
|
.ValidationDetails(d =>
|
||||||
|
{
|
||||||
|
d.IsRequired = true;
|
||||||
|
d.ValidatorMetadata.Add(attribute);
|
||||||
|
});
|
||||||
|
|
||||||
|
var provider2 = new TestModelMetadataProvider();
|
||||||
|
provider2
|
||||||
|
.ForType(typeof(Person))
|
||||||
|
.ValidationDetails(d =>
|
||||||
|
{
|
||||||
|
d.IsRequired = true;
|
||||||
|
d.ValidatorMetadata.Add(attribute);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new TheoryData<RequiredAttribute, ParameterDescriptor, ModelMetadata>
|
||||||
|
{
|
||||||
|
{ attribute, parameterDescriptor, provider1.GetMetadataForParameter(parameter) },
|
||||||
|
{ attribute, parameterDescriptor, provider1.GetMetadataForProperty(typeof(Family), nameof(Family.Mom)) },
|
||||||
|
{ attribute, parameterDescriptor, provider2.GetMetadataForType(typeof(Person)) },
|
||||||
|
{ attribute, controllerParameterDescriptor, provider2.GetMetadataForType(typeof(Person)) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(EnforcesTopLevelRequiredDataSet))]
|
||||||
|
public async Task BindModelAsync_EnforcesTopLevelRequiredAndLogsSuccessfully_WithEmptyPrefix(
|
||||||
|
RequiredAttribute attribute,
|
||||||
|
ParameterDescriptor parameterDescriptor,
|
||||||
|
ModelMetadata metadata)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedKey = metadata.Name ?? string.Empty;
|
||||||
|
var expectedFieldName = metadata.Name ?? nameof(Person);
|
||||||
|
|
||||||
|
var actionContext = GetControllerContext();
|
||||||
|
var validator = new DataAnnotationsModelValidator(
|
||||||
|
new ValidationAttributeAdapterProvider(),
|
||||||
|
attribute,
|
||||||
|
stringLocalizer: null);
|
||||||
|
|
||||||
|
var sink = new TestSink();
|
||||||
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||||
|
var parameterBinder = CreateParameterBinder(metadata, validator, loggerFactory: loggerFactory);
|
||||||
|
var modelBindingResult = ModelBindingResult.Success(null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await parameterBinder.BindModelAsync(
|
||||||
|
actionContext,
|
||||||
|
CreateMockModelBinder(modelBindingResult),
|
||||||
|
CreateMockValueProvider(),
|
||||||
|
parameterDescriptor,
|
||||||
|
metadata,
|
||||||
|
"ignoredvalue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(actionContext.ModelState.IsValid);
|
||||||
|
var modelState = Assert.Single(actionContext.ModelState);
|
||||||
|
Assert.Equal(expectedKey, modelState.Key);
|
||||||
|
var error = Assert.Single(modelState.Value.Errors);
|
||||||
|
Assert.Equal(attribute.FormatErrorMessage(expectedFieldName), error.ErrorMessage);
|
||||||
|
Assert.Equal(4, sink.Writes.Count);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModelAsync_EnforcesTopLevelDataAnnotationsAttribute()
|
public async Task BindModelAsync_EnforcesTopLevelDataAnnotationsAttribute()
|
||||||
{
|
{
|
||||||
|
|
@ -426,7 +530,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
private static ParameterBinder CreateParameterBinder(
|
private static ParameterBinder CreateParameterBinder(
|
||||||
ModelMetadata modelMetadata,
|
ModelMetadata modelMetadata,
|
||||||
IModelValidator validator = null,
|
IModelValidator validator = null,
|
||||||
IOptions<MvcOptions> optionsAccessor = null)
|
IOptions<MvcOptions> optionsAccessor = null,
|
||||||
|
ILoggerFactory loggerFactory = null)
|
||||||
{
|
{
|
||||||
var mockModelMetadataProvider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
|
var mockModelMetadataProvider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
|
||||||
mockModelMetadataProvider
|
mockModelMetadataProvider
|
||||||
|
|
@ -442,7 +547,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
mockModelMetadataProvider.Object,
|
mockModelMetadataProvider.Object,
|
||||||
new[] { GetModelValidatorProvider(validator) }),
|
new[] { GetModelValidatorProvider(validator) }),
|
||||||
optionsAccessor,
|
optionsAccessor,
|
||||||
NullLoggerFactory.Instance);
|
loggerFactory ?? NullLoggerFactory.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IModelValidatorProvider GetModelValidatorProvider(IModelValidator validator = null)
|
private static IModelValidatorProvider GetModelValidatorProvider(IModelValidator validator = null)
|
||||||
|
|
@ -527,6 +632,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Family
|
||||||
|
{
|
||||||
|
public Person Dad { get; set; }
|
||||||
|
|
||||||
|
public Person Mom { get; set; }
|
||||||
|
|
||||||
|
public IList<Person> Kids { get; } = new List<Person>();
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class FakeModelMetadata : ModelMetadata
|
public abstract class FakeModelMetadata : ModelMetadata
|
||||||
{
|
{
|
||||||
public FakeModelMetadata()
|
public FakeModelMetadata()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -9,7 +8,6 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -18,7 +16,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
{
|
{
|
||||||
public class DataAnnotationsModelValidatorTest
|
public class DataAnnotationsModelValidatorTest
|
||||||
{
|
{
|
||||||
private static IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
private static readonly ModelMetadataProvider _metadataProvider
|
||||||
|
= TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Constructor_SetsAttribute()
|
public void Constructor_SetsAttribute()
|
||||||
|
|
@ -36,11 +35,15 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
Assert.Same(attribute, validator.Attribute);
|
Assert.Same(attribute, validator.Attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TheoryData Validate_SetsMemberName_AsExpectedData
|
public static TheoryData<ModelMetadata, object, object, string> Validate_SetsMemberName_AsExpectedData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var array = new[] { new SampleModel { Name = "one" }, new SampleModel { Name = "two" } };
|
var array = new[] { new SampleModel { Name = "one" }, new SampleModel { Name = "two" } };
|
||||||
|
var method = typeof(ModelValidationResultComparer).GetMethod(
|
||||||
|
nameof(ModelValidationResultComparer.GetHashCode),
|
||||||
|
new[] { typeof(ModelValidationResult) });
|
||||||
|
var parameter = method.GetParameters()[0]; // GetHashCode(ModelValidationResult obj)
|
||||||
|
|
||||||
// metadata, container, model, expected MemberName
|
// metadata, container, model, expected MemberName
|
||||||
return new TheoryData<ModelMetadata, object, object, string>
|
return new TheoryData<ModelMetadata, object, object, string>
|
||||||
|
|
@ -52,7 +55,21 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
nameof(string.Length)
|
nameof(string.Length)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Validating a top-level model
|
// Validating a top-level property.
|
||||||
|
_metadataProvider.GetMetadataForProperty(typeof(SampleModel), nameof(SampleModel.Name)),
|
||||||
|
null,
|
||||||
|
"Fred",
|
||||||
|
nameof(SampleModel.Name)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Validating a parameter.
|
||||||
|
_metadataProvider.GetMetadataForParameter(parameter),
|
||||||
|
null,
|
||||||
|
new ModelValidationResult(memberName: string.Empty, message: string.Empty),
|
||||||
|
"obj"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Validating a top-level parameter as if using old-fashioned metadata provider.
|
||||||
_metadataProvider.GetMetadataForType(typeof(SampleModel)),
|
_metadataProvider.GetMetadataForType(typeof(SampleModel)),
|
||||||
null,
|
null,
|
||||||
15,
|
15,
|
||||||
|
|
@ -542,39 +559,5 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
{
|
{
|
||||||
void DoSomething();
|
void DoSomething();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ModelValidationResultComparer : IEqualityComparer<ModelValidationResult>
|
|
||||||
{
|
|
||||||
public static readonly ModelValidationResultComparer Instance = new ModelValidationResultComparer();
|
|
||||||
|
|
||||||
private ModelValidationResultComparer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(ModelValidationResult x, ModelValidationResult y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null)
|
|
||||||
{
|
|
||||||
return x == null && y == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(x.MemberName, y.MemberName, StringComparison.Ordinal) &&
|
|
||||||
string.Equals(x.Message, y.Message, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(ModelValidationResult obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
|
||||||
hashCodeCombiner.Add(obj.MemberName, StringComparer.Ordinal);
|
|
||||||
hashCodeCombiner.Add(obj.Message, StringComparer.Ordinal);
|
|
||||||
|
|
||||||
return hashCodeCombiner.CombinedHash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
|
{
|
||||||
|
public class ModelValidationResultComparer : IEqualityComparer<ModelValidationResult>
|
||||||
|
{
|
||||||
|
public static readonly ModelValidationResultComparer Instance = new ModelValidationResultComparer();
|
||||||
|
|
||||||
|
private ModelValidationResultComparer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ModelValidationResult x, ModelValidationResult y)
|
||||||
|
{
|
||||||
|
if (x == null || y == null)
|
||||||
|
{
|
||||||
|
return x == null && y == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(x.MemberName, y.MemberName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(x.Message, y.Message, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(ModelValidationResult obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||||
|
hashCodeCombiner.Add(obj.MemberName, StringComparer.Ordinal);
|
||||||
|
hashCodeCombiner.Add(obj.Message, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
return hashCodeCombiner.CombinedHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -70,7 +70,39 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AddValidation_CorrectValidationTypeAndOverriddenErrorMessage_WithNonProperty()
|
public void AddValidation_CorrectValidationTypeAndOverriddenErrorMessage_WithParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedMessage = "Error message about 'number' from override.";
|
||||||
|
|
||||||
|
var method = typeof(TypeWithNumericProperty).GetMethod(nameof(TypeWithNumericProperty.IsLovely));
|
||||||
|
var parameter = method.GetParameters()[0]; // IsLovely(double number)
|
||||||
|
var provider = new TestModelMetadataProvider();
|
||||||
|
provider
|
||||||
|
.ForParameter(parameter)
|
||||||
|
.BindingDetails(d =>
|
||||||
|
{
|
||||||
|
d.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(
|
||||||
|
name => $"Error message about '{ name }' from override.");
|
||||||
|
});
|
||||||
|
var metadata = provider.GetMetadataForParameter(parameter);
|
||||||
|
|
||||||
|
var adapter = new NumericClientModelValidator();
|
||||||
|
var actionContext = new ActionContext();
|
||||||
|
var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
adapter.AddValidation(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
context.Attributes,
|
||||||
|
kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); },
|
||||||
|
kvp => { Assert.Equal("data-val-number", kvp.Key); Assert.Equal(expectedMessage, kvp.Value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddValidation_CorrectValidationTypeAndOverriddenErrorMessage_WithType()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedMessage = "Error message from override.";
|
var expectedMessage = "Error message from override.";
|
||||||
|
|
@ -125,6 +157,11 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
{
|
{
|
||||||
[Display(Name = "DisplayId")]
|
[Display(Name = "DisplayId")]
|
||||||
public float Id { get; set; }
|
public float Id { get; set; }
|
||||||
|
|
||||||
|
public bool IsLovely(double number)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
// 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.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
|
{
|
||||||
|
public class ValidatableObjectAdapterTest
|
||||||
|
{
|
||||||
|
private static readonly ModelMetadataProvider _metadataProvider
|
||||||
|
= TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
|
||||||
|
// Inspired by DataAnnotationsModelValidatorTest.Validate_SetsMemberName_AsExpectedData but using a type that
|
||||||
|
// implements IValidatableObject. Values are metadata, expected DisplayName, and expected MemberName.
|
||||||
|
public static TheoryData<ModelMetadata, string, string> Validate_PassesExpectedNamesData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var method = typeof(SampleModel).GetMethod(nameof(SampleModel.IsLovely));
|
||||||
|
var parameter = method.GetParameters()[0]; // IsLovely(SampleModel other)
|
||||||
|
return new TheoryData<ModelMetadata, string, string>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// Validating a property.
|
||||||
|
_metadataProvider.GetMetadataForProperty(
|
||||||
|
typeof(SampleModelContainer),
|
||||||
|
nameof(SampleModelContainer.SampleModel)),
|
||||||
|
nameof(SampleModelContainer.SampleModel),
|
||||||
|
nameof(SampleModelContainer.SampleModel)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Validating a property with [Display(Name = "...")].
|
||||||
|
_metadataProvider.GetMetadataForProperty(
|
||||||
|
typeof(SampleModelContainer),
|
||||||
|
nameof(SampleModelContainer.SampleModelWithDisplay)),
|
||||||
|
"sample model",
|
||||||
|
nameof(SampleModelContainer.SampleModelWithDisplay)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Validating a parameter.
|
||||||
|
_metadataProvider.GetMetadataForParameter(parameter),
|
||||||
|
"other",
|
||||||
|
"other"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Validating a top-level parameter when using old-fashioned metadata provider.
|
||||||
|
// Or, validating an element of a collection.
|
||||||
|
_metadataProvider.GetMetadataForType(typeof(SampleModel)),
|
||||||
|
nameof(SampleModel),
|
||||||
|
null
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<ValidationResult[], ModelValidationResult[]> Validate_ReturnsExpectedResultsData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<ValidationResult[], ModelValidationResult[]>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
new[] { new ValidationResult("Error message") },
|
||||||
|
new[] { new ModelValidationResult(memberName: null, message: "Error message") }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new[] { new ValidationResult("Error message", new[] { nameof(SampleModel.FirstName) }) },
|
||||||
|
new[] { new ModelValidationResult(nameof(SampleModel.FirstName), "Error message") }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ValidationResult("Error message1"),
|
||||||
|
new ValidationResult("Error message2", new[] { nameof(SampleModel.FirstName) }),
|
||||||
|
new ValidationResult("Error message3", new[] { nameof(SampleModel.LastName) }),
|
||||||
|
new ValidationResult("Error message4", new[] { nameof(SampleModel) }),
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ModelValidationResult(memberName: null, message: "Error message1"),
|
||||||
|
new ModelValidationResult(nameof(SampleModel.FirstName), "Error message2"),
|
||||||
|
new ModelValidationResult(nameof(SampleModel.LastName), "Error message3"),
|
||||||
|
// No special case for ValidationContext.MemberName==ValidationResult.MemberName
|
||||||
|
new ModelValidationResult(nameof(SampleModel), "Error message4"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ValidationResult("Error message1", new[]
|
||||||
|
{
|
||||||
|
nameof(SampleModel.FirstName),
|
||||||
|
nameof(SampleModel.LastName),
|
||||||
|
}),
|
||||||
|
new ValidationResult("Error message2"),
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ModelValidationResult(nameof(SampleModel.FirstName), "Error message1"),
|
||||||
|
new ModelValidationResult(nameof(SampleModel.LastName), "Error message1"),
|
||||||
|
new ModelValidationResult(memberName: null, message: "Error message2"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Validate_PassesExpectedNamesData))]
|
||||||
|
public void Validate_PassesExpectedNames(
|
||||||
|
ModelMetadata metadata,
|
||||||
|
string expectedDisplayName,
|
||||||
|
string expectedMemberName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var adapter = new ValidatableObjectAdapter();
|
||||||
|
var model = new SampleModel();
|
||||||
|
var validationContext = new ModelValidationContext(
|
||||||
|
new ActionContext(),
|
||||||
|
metadata,
|
||||||
|
_metadataProvider,
|
||||||
|
container: new SampleModelContainer(),
|
||||||
|
model: model);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var results = adapter.Validate(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(results);
|
||||||
|
Assert.Empty(results);
|
||||||
|
|
||||||
|
Assert.Equal(expectedDisplayName, model.DisplayName);
|
||||||
|
Assert.Equal(expectedMemberName, model.MemberName);
|
||||||
|
Assert.Equal(model, model.ObjectInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Validate_ReturnsExpectedResultsData))]
|
||||||
|
public void Validate_ReturnsExpectedResults(
|
||||||
|
ValidationResult[] innerResults,
|
||||||
|
ModelValidationResult[] expectedResults)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var adapter = new ValidatableObjectAdapter();
|
||||||
|
var model = new SampleModel();
|
||||||
|
foreach (var result in innerResults)
|
||||||
|
{
|
||||||
|
model.ValidationResults.Add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata = _metadataProvider.GetMetadataForProperty(
|
||||||
|
typeof(SampleModelContainer),
|
||||||
|
nameof(SampleModelContainer.SampleModel));
|
||||||
|
var validationContext = new ModelValidationContext(
|
||||||
|
new ActionContext(),
|
||||||
|
metadata,
|
||||||
|
_metadataProvider,
|
||||||
|
container: null,
|
||||||
|
model: model);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var results = adapter.Validate(validationContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(results);
|
||||||
|
Assert.Equal(expectedResults, results, ModelValidationResultComparer.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SampleModel : IValidatableObject
|
||||||
|
{
|
||||||
|
// "Real" properties.
|
||||||
|
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
|
||||||
|
public string LastName { get; set; }
|
||||||
|
|
||||||
|
// ValidationContext members passed to Validate(...)
|
||||||
|
|
||||||
|
public string DisplayName { get; private set; }
|
||||||
|
|
||||||
|
public string MemberName { get; private set; }
|
||||||
|
|
||||||
|
public object ObjectInstance { get; private set; }
|
||||||
|
|
||||||
|
// What Validate(...) should return.
|
||||||
|
|
||||||
|
public IList<ValidationResult> ValidationResults { get; } = new List<ValidationResult>();
|
||||||
|
|
||||||
|
// Test method.
|
||||||
|
|
||||||
|
public bool IsLovely(SampleModel other)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IValidatableObject for realz.
|
||||||
|
|
||||||
|
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||||
|
{
|
||||||
|
DisplayName = validationContext.DisplayName;
|
||||||
|
MemberName = validationContext.MemberName;
|
||||||
|
ObjectInstance = validationContext.ObjectInstance;
|
||||||
|
|
||||||
|
return ValidationResults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SampleModelContainer
|
||||||
|
{
|
||||||
|
[Display(Name = "sample model")]
|
||||||
|
public SampleModel SampleModelWithDisplay { get; set; }
|
||||||
|
|
||||||
|
public SampleModel SampleModel { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -112,6 +112,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IMetadataBuilder ForParameter(ParameterInfo parameter)
|
||||||
|
{
|
||||||
|
var key = ModelMetadataIdentity.ForParameter(parameter);
|
||||||
|
var builder = new MetadataBuilder(key);
|
||||||
|
_detailsProvider.Builders.Add(builder);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
public IMetadataBuilder ForProperty<TContainer>(string propertyName)
|
public IMetadataBuilder ForProperty<TContainer>(string propertyName)
|
||||||
{
|
{
|
||||||
return ForProperty(typeof(TContainer), propertyName);
|
return ForProperty(typeof(TContainer), propertyName);
|
||||||
|
|
@ -223,4 +232,4 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue