Merge pull request #8024 from aspnet/merge/release/2.2
[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
commit
23cf9643a2
|
|
@ -66,17 +66,37 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="ModelMetadataIdentity"/> for the provided parameter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">The <see cref="ParameterInfo" />.</param>
|
||||||
|
/// <returns>A <see cref="ModelMetadataIdentity"/>.</returns>
|
||||||
public static ModelMetadataIdentity ForParameter(ParameterInfo parameter)
|
public static ModelMetadataIdentity ForParameter(ParameterInfo parameter)
|
||||||
|
=> ForParameter(parameter, parameter?.ParameterType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="ModelMetadataIdentity"/> for the provided parameter with the specified
|
||||||
|
/// model type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">The <see cref="ParameterInfo" />.</param>
|
||||||
|
/// <param name="modelType">The model type.</param>
|
||||||
|
/// <returns>A <see cref="ModelMetadataIdentity"/>.</returns>
|
||||||
|
public static ModelMetadataIdentity ForParameter(ParameterInfo parameter, Type modelType)
|
||||||
{
|
{
|
||||||
if (parameter == null)
|
if (parameter == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(parameter));
|
throw new ArgumentNullException(nameof(parameter));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (modelType == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(modelType));
|
||||||
|
}
|
||||||
|
|
||||||
return new ModelMetadataIdentity()
|
return new ModelMetadataIdentity()
|
||||||
{
|
{
|
||||||
Name = parameter.Name,
|
Name = parameter.Name,
|
||||||
ModelType = parameter.ParameterType,
|
ModelType = modelType,
|
||||||
ParameterInfo = parameter,
|
ParameterInfo = parameter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,5 +32,27 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// <param name="parameter">The <see cref="ParameterInfo"/>.</param>
|
/// <param name="parameter">The <see cref="ParameterInfo"/>.</param>
|
||||||
/// <returns>A <see cref="ModelMetadata"/> instance describing the <paramref name="parameter"/>.</returns>
|
/// <returns>A <see cref="ModelMetadata"/> instance describing the <paramref name="parameter"/>.</returns>
|
||||||
public abstract ModelMetadata GetMetadataForParameter(ParameterInfo parameter);
|
public abstract ModelMetadata GetMetadataForParameter(ParameterInfo parameter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Supplies metadata describing a parameter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">The <see cref="ParameterInfo"/></param>
|
||||||
|
/// <param name="modelType">The actual model type.</param>
|
||||||
|
/// <returns>A <see cref="ModelMetadata"/> instance describing the <paramref name="parameter"/>.</returns>
|
||||||
|
public virtual ModelMetadata GetMetadataForParameter(ParameterInfo parameter, Type modelType)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Supplies metadata describing a property.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyInfo">The <see cref="PropertyInfo"/>.</param>
|
||||||
|
/// <param name="modelType">The actual model type.</param>
|
||||||
|
/// <returns>A <see cref="ModelMetadata"/> instance describing the <paramref name="propertyInfo"/>.</returns>
|
||||||
|
public virtual ModelMetadata GetMetadataForProperty(PropertyInfo propertyInfo, Type modelType)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1704,6 +1704,14 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
public virtual UnauthorizedResult Unauthorized()
|
public virtual UnauthorizedResult Unauthorized()
|
||||||
=> new UnauthorizedResult();
|
=> new UnauthorizedResult();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an <see cref="UnauthorizedObjectResult"/> that produces a <see cref="StatusCodes.Status401Unauthorized"/> response.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The created <see cref="UnauthorizedObjectResult"/> for the response.</returns>
|
||||||
|
[NonAction]
|
||||||
|
public virtual UnauthorizedObjectResult Unauthorized(object value)
|
||||||
|
=> new UnauthorizedObjectResult(value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an <see cref="NotFoundResult"/> that produces a <see cref="StatusCodes.Status404NotFound"/> response.
|
/// Creates an <see cref="NotFoundResult"/> that produces a <see cref="StatusCodes.Status404NotFound"/> response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@
|
||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Controllers
|
namespace Microsoft.AspNetCore.Mvc.Controllers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A descriptor for model bound properties of a controller.
|
/// A descriptor for model bound properties of a controller.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ControllerBoundPropertyDescriptor : ParameterDescriptor
|
public class ControllerBoundPropertyDescriptor : ParameterDescriptor, IPropertyInfoParameterDescriptor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="PropertyInfo"/> for this property.
|
/// Gets or sets the <see cref="System.Reflection.PropertyInfo"/> for this property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PropertyInfo PropertyInfo { get; set; }
|
public PropertyInfo PropertyInfo { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@
|
||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Controllers
|
namespace Microsoft.AspNetCore.Mvc.Controllers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A descriptor for method parameters of an action method.
|
/// A descriptor for method parameters of an action method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ControllerParameterDescriptor : ParameterDescriptor
|
public class ControllerParameterDescriptor : ParameterDescriptor, IParameterInfoParameterDescriptor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="ParameterInfo"/>.
|
/// Gets or sets the <see cref="System.Reflection.ParameterInfo"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ParameterInfo ParameterInfo { get; set; }
|
public ParameterInfo ParameterInfo { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// 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.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="ParameterDescriptor"/> for action parameters.
|
||||||
|
/// </summary>
|
||||||
|
public interface IParameterInfoParameterDescriptor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="System.Reflection.ParameterInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
ParameterInfo ParameterInfo { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// 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.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="ParameterDescriptor"/> for bound properties.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPropertyInfoParameterDescriptor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="System.Reflection.PropertyInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
PropertyInfo PropertyInfo { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -97,14 +98,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
return cacheEntry.Details.Properties;
|
return cacheEntry.Details.Properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public override ModelMetadata GetMetadataForParameter(ParameterInfo parameter)
|
public override ModelMetadata GetMetadataForParameter(ParameterInfo parameter)
|
||||||
|
=> GetMetadataForParameter(parameter, parameter?.ParameterType);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ModelMetadata GetMetadataForParameter(ParameterInfo parameter, Type modelType)
|
||||||
{
|
{
|
||||||
if (parameter == null)
|
if (parameter == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(parameter));
|
throw new ArgumentNullException(nameof(parameter));
|
||||||
}
|
}
|
||||||
|
|
||||||
var cacheEntry = GetCacheEntry(parameter);
|
if (modelType == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(modelType));
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheEntry = GetCacheEntry(parameter, modelType);
|
||||||
|
|
||||||
return cacheEntry.Metadata;
|
return cacheEntry.Metadata;
|
||||||
}
|
}
|
||||||
|
|
@ -122,6 +133,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
return cacheEntry.Metadata;
|
return cacheEntry.Metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ModelMetadata GetMetadataForProperty(PropertyInfo propertyInfo, Type modelType)
|
||||||
|
{
|
||||||
|
if (propertyInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(propertyInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelType == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(modelType));
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheEntry = GetCacheEntry(propertyInfo, modelType);
|
||||||
|
|
||||||
|
return cacheEntry.Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
private static DefaultModelBindingMessageProvider GetMessageProvider(IOptions<MvcOptions> optionsAccessor)
|
private static DefaultModelBindingMessageProvider GetMessageProvider(IOptions<MvcOptions> optionsAccessor)
|
||||||
{
|
{
|
||||||
if (optionsAccessor == null)
|
if (optionsAccessor == null)
|
||||||
|
|
@ -151,10 +180,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
return cacheEntry;
|
return cacheEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModelMetadataCacheEntry GetCacheEntry(ParameterInfo parameter)
|
private ModelMetadataCacheEntry GetCacheEntry(ParameterInfo parameter, Type modelType)
|
||||||
{
|
{
|
||||||
return _typeCache.GetOrAdd(
|
return _typeCache.GetOrAdd(
|
||||||
ModelMetadataIdentity.ForParameter(parameter),
|
ModelMetadataIdentity.ForParameter(parameter, modelType),
|
||||||
|
_cacheEntryFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModelMetadataCacheEntry GetCacheEntry(PropertyInfo property, Type modelType)
|
||||||
|
{
|
||||||
|
return _typeCache.GetOrAdd(
|
||||||
|
ModelMetadataIdentity.ForProperty(modelType, property.Name, property.DeclaringType),
|
||||||
_cacheEntryFactory);
|
_cacheEntryFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +201,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
details = CreateParameterDetails(key);
|
details = CreateParameterDetails(key);
|
||||||
}
|
}
|
||||||
|
else if (key.MetadataKind == ModelMetadataKind.Property)
|
||||||
|
{
|
||||||
|
details = CreateSinglePropertyDetails(key);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
details = CreateTypeDetails(key);
|
details = CreateTypeDetails(key);
|
||||||
|
|
@ -174,6 +214,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
return new ModelMetadataCacheEntry(metadata, details);
|
return new ModelMetadataCacheEntry(metadata, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DefaultMetadataDetails CreateSinglePropertyDetails(ModelMetadataIdentity propertyKey)
|
||||||
|
{
|
||||||
|
var propertyHelpers = PropertyHelper.GetVisibleProperties(propertyKey.ContainerType);
|
||||||
|
for (var i = 0; i < propertyHelpers.Length; i++)
|
||||||
|
{
|
||||||
|
var propertyHelper = propertyHelpers[i];
|
||||||
|
if (propertyHelper.Name == propertyKey.Name)
|
||||||
|
{
|
||||||
|
return CreateSinglePropertyDetails(propertyKey, propertyHelper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Fail($"Unable to find property '{propertyKey.Name}' on type '{propertyKey.ContainerType}.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private ModelMetadataCacheEntry GetMetadataCacheEntryForObjectType()
|
private ModelMetadataCacheEntry GetMetadataCacheEntryForObjectType()
|
||||||
{
|
{
|
||||||
var key = ModelMetadataIdentity.ForType(typeof(object));
|
var key = ModelMetadataIdentity.ForType(typeof(object));
|
||||||
|
|
@ -217,35 +273,48 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
for (var i = 0; i < propertyHelpers.Length; i++)
|
for (var i = 0; i < propertyHelpers.Length; i++)
|
||||||
{
|
{
|
||||||
var propertyHelper = propertyHelpers[i];
|
var propertyHelper = propertyHelpers[i];
|
||||||
|
|
||||||
var propertyKey = ModelMetadataIdentity.ForProperty(
|
var propertyKey = ModelMetadataIdentity.ForProperty(
|
||||||
propertyHelper.Property.PropertyType,
|
propertyHelper.Property.PropertyType,
|
||||||
propertyHelper.Name,
|
propertyHelper.Name,
|
||||||
key.ModelType);
|
key.ModelType);
|
||||||
|
|
||||||
var attributes = ModelAttributes.GetAttributesForProperty(
|
var propertyEntry = CreateSinglePropertyDetails(propertyKey, propertyHelper);
|
||||||
key.ModelType,
|
|
||||||
propertyHelper.Property);
|
|
||||||
|
|
||||||
var propertyEntry = new DefaultMetadataDetails(propertyKey, attributes);
|
|
||||||
if (propertyHelper.Property.CanRead && propertyHelper.Property.GetMethod?.IsPublic == true)
|
|
||||||
{
|
|
||||||
var getter = PropertyHelper.MakeNullSafeFastPropertyGetter(propertyHelper.Property);
|
|
||||||
propertyEntry.PropertyGetter = getter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propertyHelper.Property.CanWrite &&
|
|
||||||
propertyHelper.Property.SetMethod?.IsPublic == true &&
|
|
||||||
!key.ModelType.GetTypeInfo().IsValueType)
|
|
||||||
{
|
|
||||||
propertyEntry.PropertySetter = propertyHelper.ValueSetter;
|
|
||||||
}
|
|
||||||
|
|
||||||
propertyEntries.Add(propertyEntry);
|
propertyEntries.Add(propertyEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return propertyEntries.ToArray();
|
return propertyEntries.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DefaultMetadataDetails CreateSinglePropertyDetails(
|
||||||
|
ModelMetadataIdentity propertyKey,
|
||||||
|
PropertyHelper propertyHelper)
|
||||||
|
{
|
||||||
|
Debug.Assert(propertyKey.MetadataKind == ModelMetadataKind.Property);
|
||||||
|
var containerType = propertyKey.ContainerType;
|
||||||
|
|
||||||
|
var attributes = ModelAttributes.GetAttributesForProperty(
|
||||||
|
containerType,
|
||||||
|
propertyHelper.Property,
|
||||||
|
propertyKey.ModelType);
|
||||||
|
|
||||||
|
var propertyEntry = new DefaultMetadataDetails(propertyKey, attributes);
|
||||||
|
if (propertyHelper.Property.CanRead && propertyHelper.Property.GetMethod?.IsPublic == true)
|
||||||
|
{
|
||||||
|
var getter = PropertyHelper.MakeNullSafeFastPropertyGetter(propertyHelper.Property);
|
||||||
|
propertyEntry.PropertyGetter = getter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyHelper.Property.CanWrite &&
|
||||||
|
propertyHelper.Property.SetMethod?.IsPublic == true &&
|
||||||
|
!containerType.IsValueType)
|
||||||
|
{
|
||||||
|
propertyEntry.PropertySetter = propertyHelper.ValueSetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyEntry;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the <see cref="DefaultMetadataDetails"/> entry for a model <see cref="Type"/>.
|
/// Creates the <see cref="DefaultMetadataDetails"/> entry for a model <see cref="Type"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -269,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
return new DefaultMetadataDetails(
|
return new DefaultMetadataDetails(
|
||||||
key,
|
key,
|
||||||
ModelAttributes.GetAttributesForParameter(key.ParameterInfo));
|
ModelAttributes.GetAttributesForParameter(key.ParameterInfo, key.ModelType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TypeCache : ConcurrentDictionary<ModelMetadataIdentity, ModelMetadataCacheEntry>
|
private class TypeCache : ConcurrentDictionary<ModelMetadataIdentity, ModelMetadataCacheEntry>
|
||||||
|
|
|
||||||
|
|
@ -135,9 +135,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static ModelAttributes GetAttributesForProperty(Type type, PropertyInfo property)
|
public static ModelAttributes GetAttributesForProperty(Type type, PropertyInfo property)
|
||||||
{
|
{
|
||||||
if (type == null)
|
return GetAttributesForProperty(type, property, property.PropertyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the attributes for the given <paramref name="property"/> with the specified <paramref name="modelType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="containerType">The <see cref="Type"/> in which caller found <paramref name="property"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="property">A <see cref="PropertyInfo"/> for which attributes need to be resolved.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="modelType">The model type</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="ModelAttributes"/> instance with the attributes of the property and its <see cref="Type"/>.
|
||||||
|
/// </returns>
|
||||||
|
public static ModelAttributes GetAttributesForProperty(Type containerType, PropertyInfo property, Type modelType)
|
||||||
|
{
|
||||||
|
if (containerType == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(type));
|
throw new ArgumentNullException(nameof(containerType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property == null)
|
if (property == null)
|
||||||
|
|
@ -146,9 +162,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
var propertyAttributes = property.GetCustomAttributes();
|
var propertyAttributes = property.GetCustomAttributes();
|
||||||
var typeAttributes = property.PropertyType.GetTypeInfo().GetCustomAttributes();
|
var typeAttributes = modelType.GetCustomAttributes();
|
||||||
|
|
||||||
var metadataType = GetMetadataType(type);
|
var metadataType = GetMetadataType(containerType);
|
||||||
if (metadataType != null)
|
if (metadataType != null)
|
||||||
{
|
{
|
||||||
var metadataProperty = metadataType.GetRuntimeProperty(property.Name);
|
var metadataProperty = metadataType.GetRuntimeProperty(property.Name);
|
||||||
|
|
@ -174,12 +190,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
throw new ArgumentNullException(nameof(type));
|
throw new ArgumentNullException(nameof(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
var attributes = type.GetTypeInfo().GetCustomAttributes();
|
var attributes = type.GetCustomAttributes();
|
||||||
|
|
||||||
var metadataType = GetMetadataType(type);
|
var metadataType = GetMetadataType(type);
|
||||||
if (metadataType != null)
|
if (metadataType != null)
|
||||||
{
|
{
|
||||||
attributes = attributes.Concat(metadataType.GetTypeInfo().GetCustomAttributes());
|
attributes = attributes.Concat(metadataType.GetCustomAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ModelAttributes(attributes, propertyAttributes: null, parameterAttributes: null);
|
return new ModelAttributes(attributes, propertyAttributes: null, parameterAttributes: null);
|
||||||
|
|
@ -205,9 +221,40 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
return new ModelAttributes(typeAttributes, propertyAttributes: null, parameterAttributes);
|
return new ModelAttributes(typeAttributes, propertyAttributes: null, parameterAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the attributes for the given <paramref name="parameterInfo"/> with the specified <paramref name="modelType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameterInfo">
|
||||||
|
/// The <see cref="ParameterInfo"/> for which attributes need to be resolved.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="modelType">The model type.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="ModelAttributes"/> instance with the attributes of the parameter and its <see cref="Type"/>.
|
||||||
|
/// </returns>
|
||||||
|
public static ModelAttributes GetAttributesForParameter(ParameterInfo parameterInfo, Type modelType)
|
||||||
|
{
|
||||||
|
if (parameterInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(parameterInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelType == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(modelType));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prior versions called IModelMetadataProvider.GetMetadataForType(...) and therefore
|
||||||
|
// GetAttributesForType(...) for parameters. Maintain that set of attributes (including those from an
|
||||||
|
// ModelMetadataTypeAttribute reference) for back-compatibility.
|
||||||
|
var typeAttributes = GetAttributesForType(modelType).TypeAttributes;
|
||||||
|
var parameterAttributes = parameterInfo.GetCustomAttributes();
|
||||||
|
|
||||||
|
return new ModelAttributes(typeAttributes, propertyAttributes: null, parameterAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
private static Type GetMetadataType(Type type)
|
private static Type GetMetadataType(Type type)
|
||||||
{
|
{
|
||||||
return type.GetTypeInfo().GetCustomAttribute<ModelMetadataTypeAttribute>()?.MetadataType;
|
return type.GetCustomAttribute<ModelMetadataTypeAttribute>()?.MetadataType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -283,6 +284,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
ModelBindingContext modelBindingContext,
|
ModelBindingContext modelBindingContext,
|
||||||
ModelBindingResult modelBindingResult)
|
ModelBindingResult modelBindingResult)
|
||||||
{
|
{
|
||||||
|
RecalculateModelMetadata(parameter, modelBindingResult, ref metadata);
|
||||||
|
|
||||||
if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired)
|
if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired)
|
||||||
{
|
{
|
||||||
// Enforce BindingBehavior.Required (e.g., [BindRequired])
|
// Enforce BindingBehavior.Required (e.g., [BindRequired])
|
||||||
|
|
@ -330,5 +333,40 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
metadata);
|
metadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RecalculateModelMetadata(
|
||||||
|
ParameterDescriptor parameter,
|
||||||
|
ModelBindingResult modelBindingResult,
|
||||||
|
ref ModelMetadata metadata)
|
||||||
|
{
|
||||||
|
// Attempt to recalculate ModelMetadata for top level parameters and properties using the actual
|
||||||
|
// model type. This ensures validation uses a combination of top-level validation metadata
|
||||||
|
// as well as metadata on the actual, rather than declared, model type.
|
||||||
|
|
||||||
|
if (!modelBindingResult.IsModelSet ||
|
||||||
|
modelBindingResult.Model == null ||
|
||||||
|
!(_modelMetadataProvider is ModelMetadataProvider modelMetadataProvider))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var modelType = modelBindingResult.Model.GetType();
|
||||||
|
if (parameter is IParameterInfoParameterDescriptor parameterInfoParameter)
|
||||||
|
{
|
||||||
|
var parameterInfo = parameterInfoParameter.ParameterInfo;
|
||||||
|
if (modelType != parameterInfo.ParameterType)
|
||||||
|
{
|
||||||
|
metadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo, modelType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (parameter is IPropertyInfoParameterDescriptor propertyInfoParameter)
|
||||||
|
{
|
||||||
|
var propertyInfo = propertyInfoParameter.PropertyInfo;
|
||||||
|
if (modelType != propertyInfo.PropertyType)
|
||||||
|
{
|
||||||
|
metadata = modelMetadataProvider.GetMetadataForProperty(propertyInfo, modelType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ObjectResult"/> that when executed will produce a Unauthorized (401) response.
|
||||||
|
/// </summary>
|
||||||
|
[DefaultStatusCode(DefaultStatusCode)]
|
||||||
|
public class UnauthorizedObjectResult : ObjectResult
|
||||||
|
{
|
||||||
|
private const int DefaultStatusCode = StatusCodes.Status401Unauthorized;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="UnauthorizedObjectResult"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public UnauthorizedObjectResult(object value) : base(value)
|
||||||
|
{
|
||||||
|
StatusCode = DefaultStatusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,15 @@
|
||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
public class HandlerParameterDescriptor : ParameterDescriptor
|
public class HandlerParameterDescriptor : ParameterDescriptor, IParameterInfoParameterDescriptor
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="System.Reflection.ParameterInfo"/>.
|
||||||
|
/// </summary>
|
||||||
public ParameterInfo ParameterInfo { get; set; }
|
public ParameterInfo ParameterInfo { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,17 @@
|
||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
public class PageBoundPropertyDescriptor : ParameterDescriptor
|
public class PageBoundPropertyDescriptor : ParameterDescriptor, IPropertyInfoParameterDescriptor
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="System.Reflection.PropertyInfo"/> for this property.
|
||||||
|
/// </summary>
|
||||||
public PropertyInfo Property { get; set; }
|
public PropertyInfo Property { get; set; }
|
||||||
|
|
||||||
|
PropertyInfo IPropertyInfoParameterDescriptor.PropertyInfo => Property;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
{
|
{
|
||||||
private const string ForAttributeName = "for";
|
private const string ForAttributeName = "for";
|
||||||
private const string ModelAttributeName = "model";
|
private const string ModelAttributeName = "model";
|
||||||
|
private const string OptionalAttributeName = "optional";
|
||||||
private object _model;
|
private object _model;
|
||||||
private bool _hasModel;
|
private bool _hasModel;
|
||||||
private bool _hasFor;
|
private bool _hasFor;
|
||||||
|
|
@ -74,6 +75,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When optional, executing the tag helper will no-op if the view cannot be located.
|
||||||
|
/// Otherwise will throw stating the view could not be found.
|
||||||
|
/// </summary>
|
||||||
|
[HtmlAttributeName(OptionalAttributeName)]
|
||||||
|
public bool Optional { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="ViewDataDictionary"/> to pass into the partial view.
|
/// A <see cref="ViewDataDictionary"/> to pass into the partial view.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -96,16 +104,21 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
var model = ResolveModel();
|
var viewEngineResult = FindView();
|
||||||
var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize);
|
|
||||||
using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8))
|
|
||||||
{
|
|
||||||
await RenderPartialViewAsync(writer, model);
|
|
||||||
|
|
||||||
// Reset the TagName. We don't want `partial` to render.
|
if (viewEngineResult.Success)
|
||||||
output.TagName = null;
|
{
|
||||||
output.Content.SetHtmlContent(viewBuffer);
|
var model = ResolveModel();
|
||||||
|
var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize);
|
||||||
|
using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8))
|
||||||
|
{
|
||||||
|
await RenderPartialViewAsync(writer, model, viewEngineResult.View);
|
||||||
|
output.Content.SetHtmlContent(viewBuffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the TagName. We don't want `partial` to render.
|
||||||
|
output.TagName = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
|
|
@ -139,7 +152,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
return ViewContext.ViewData.Model;
|
return ViewContext.ViewData.Model;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RenderPartialViewAsync(TextWriter writer, object model)
|
private ViewEngineResult FindView()
|
||||||
{
|
{
|
||||||
var viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, Name, isMainPage: false);
|
var viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, Name, isMainPage: false);
|
||||||
var getViewLocations = viewEngineResult.SearchedLocations;
|
var getViewLocations = viewEngineResult.SearchedLocations;
|
||||||
|
|
@ -148,7 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
viewEngineResult = _viewEngine.FindView(ViewContext, Name, isMainPage: false);
|
viewEngineResult = _viewEngine.FindView(ViewContext, Name, isMainPage: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!viewEngineResult.Success)
|
if (!viewEngineResult.Success && !Optional)
|
||||||
{
|
{
|
||||||
var searchedLocations = Enumerable.Concat(getViewLocations, viewEngineResult.SearchedLocations);
|
var searchedLocations = Enumerable.Concat(getViewLocations, viewEngineResult.SearchedLocations);
|
||||||
var locations = string.Empty;
|
var locations = string.Empty;
|
||||||
|
|
@ -161,7 +174,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
Resources.FormatViewEngine_PartialViewNotFound(Name, locations));
|
Resources.FormatViewEngine_PartialViewNotFound(Name, locations));
|
||||||
}
|
}
|
||||||
|
|
||||||
var view = viewEngineResult.View;
|
return viewEngineResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RenderPartialViewAsync(TextWriter writer, object model, IView view)
|
||||||
|
{
|
||||||
// Determine which ViewData we should use to construct a new ViewData
|
// Determine which ViewData we should use to construct a new ViewData
|
||||||
var baseViewData = ViewData ?? ViewContext.ViewData;
|
var baseViewData = ViewData ?? ViewContext.ViewData;
|
||||||
var newViewData = new ViewDataDictionary<object>(baseViewData, model);
|
var newViewData = new ViewDataDictionary<object>(baseViewData, model);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -263,6 +264,173 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
Assert.Same(metadata1, metadata2);
|
Assert.Same(metadata1, metadata2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForParameter_WithModelType_ReturnsCombinedModelMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameter = GetType()
|
||||||
|
.GetMethod(nameof(GetMetadataForParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Instance)
|
||||||
|
.GetParameters()[0];
|
||||||
|
var provider = CreateProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata = provider.GetMetadataForParameter(parameter, typeof(DerivedModelType));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(ModelMetadataKind.Parameter, metadata.MetadataKind);
|
||||||
|
Assert.Equal(typeof(DerivedModelType), metadata.ModelType);
|
||||||
|
|
||||||
|
var defaultModelMetadata = Assert.IsType<DefaultModelMetadata>(metadata);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
defaultModelMetadata.Attributes.Attributes,
|
||||||
|
a => Assert.Equal("OnParameter", Assert.IsType<ModelAttribute>(a).Value),
|
||||||
|
a => Assert.Equal("OnDerivedType", Assert.IsType<ModelAttribute>(a).Value),
|
||||||
|
a => Assert.Equal("OnType", Assert.IsType<ModelAttribute>(a).Value));
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
metadata.Properties.OrderBy(p => p.Name),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(DerivedModelType.DerivedProperty), p.Name);
|
||||||
|
|
||||||
|
var defaultPropertyMetadata = Assert.IsType<DefaultModelMetadata>(p);
|
||||||
|
Assert.Collection(
|
||||||
|
defaultPropertyMetadata.Attributes.Attributes.OfType<ModelAttribute>(),
|
||||||
|
a => Assert.Equal("OnDerivedProperty", Assert.IsType<ModelAttribute>(a).Value));
|
||||||
|
},
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(DerivedModelType.Property1), p.Name);
|
||||||
|
|
||||||
|
var defaultPropertyMetadata = Assert.IsType<DefaultModelMetadata>(p);
|
||||||
|
Assert.Collection(
|
||||||
|
defaultPropertyMetadata.Attributes.Attributes.OfType<ModelAttribute>(),
|
||||||
|
a => Assert.Equal("OnProperty", Assert.IsType<ModelAttribute>(a).Value),
|
||||||
|
a => Assert.Equal("OnPropertyType", Assert.IsType<ModelAttribute>(a).Value));
|
||||||
|
},
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(DerivedModelType.Property2), p.Name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForParameter_WithModelType_CachesResults()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameter = GetType()
|
||||||
|
.GetMethod(nameof(GetMetadataForParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Instance)
|
||||||
|
.GetParameters()[0];
|
||||||
|
var provider = CreateProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata1 = provider.GetMetadataForParameter(parameter, typeof(DerivedModelType));
|
||||||
|
var metadata2 = provider.GetMetadataForParameter(parameter, typeof(DerivedModelType));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(metadata1, metadata2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForParameter_WithModelType_VariesByModelType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameter = GetType()
|
||||||
|
.GetMethod(nameof(GetMetadataForParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Instance)
|
||||||
|
.GetParameters()[0];
|
||||||
|
var provider = CreateProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata1 = provider.GetMetadataForParameter(parameter, typeof(DerivedModelType));
|
||||||
|
var metadata2 = provider.GetMetadataForParameter(parameter, typeof(object));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotSame(metadata1, metadata2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForProperty_WithModelType_ReturnsCombinedModelMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var property = typeof(TestContainer)
|
||||||
|
.GetProperty(nameof(TestContainer.ModelProperty));
|
||||||
|
var provider = CreateProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata = provider.GetMetadataForProperty(property, typeof(DerivedModelType));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(ModelMetadataKind.Property, metadata.MetadataKind);
|
||||||
|
Assert.Equal(typeof(DerivedModelType), metadata.ModelType);
|
||||||
|
|
||||||
|
var defaultModelMetadata = Assert.IsType<DefaultModelMetadata>(metadata);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
defaultModelMetadata.Attributes.Attributes,
|
||||||
|
a => Assert.Equal("OnProperty", Assert.IsType<ModelAttribute>(a).Value),
|
||||||
|
a => Assert.Equal("OnDerivedType", Assert.IsType<ModelAttribute>(a).Value),
|
||||||
|
a => Assert.Equal("OnType", Assert.IsType<ModelAttribute>(a).Value));
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
metadata.Properties.OrderBy(p => p.Name),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(DerivedModelType.DerivedProperty), p.Name);
|
||||||
|
|
||||||
|
var defaultPropertyMetadata = Assert.IsType<DefaultModelMetadata>(p);
|
||||||
|
Assert.Collection(
|
||||||
|
defaultPropertyMetadata.Attributes.Attributes.OfType<ModelAttribute>(),
|
||||||
|
a => Assert.Equal("OnDerivedProperty", Assert.IsType<ModelAttribute>(a).Value));
|
||||||
|
},
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(DerivedModelType.Property1), p.Name);
|
||||||
|
|
||||||
|
var defaultPropertyMetadata = Assert.IsType<DefaultModelMetadata>(p);
|
||||||
|
Assert.Collection(
|
||||||
|
defaultPropertyMetadata.Attributes.Attributes.OfType<ModelAttribute>(),
|
||||||
|
a => Assert.Equal("OnProperty", Assert.IsType<ModelAttribute>(a).Value),
|
||||||
|
a => Assert.Equal("OnPropertyType", Assert.IsType<ModelAttribute>(a).Value));
|
||||||
|
},
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(DerivedModelType.Property2), p.Name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForProperty_WithModelType_CachesResults()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var property = typeof(TestContainer)
|
||||||
|
.GetProperty(nameof(TestContainer.ModelProperty));
|
||||||
|
var provider = CreateProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata1 = provider.GetMetadataForProperty(property, typeof(DerivedModelType));
|
||||||
|
var metadata2 = provider.GetMetadataForProperty(property, typeof(DerivedModelType));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(metadata1, metadata2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForProperty_WithModelType_VariesByModelType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var property = typeof(TestContainer)
|
||||||
|
.GetProperty(nameof(TestContainer.ModelProperty));
|
||||||
|
var provider = CreateProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata1 = provider.GetMetadataForProperty(property, typeof(DerivedModelType));
|
||||||
|
var metadata2 = provider.GetMetadataForProperty(property, typeof(object));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotSame(metadata1, metadata2);
|
||||||
|
}
|
||||||
|
|
||||||
private static DefaultModelMetadataProvider CreateProvider()
|
private static DefaultModelMetadataProvider CreateProvider()
|
||||||
{
|
{
|
||||||
return new DefaultModelMetadataProvider(
|
return new DefaultModelMetadataProvider(
|
||||||
|
|
@ -321,5 +489,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
public new string Property { get; set; }
|
public new string Property { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Model("OnDerivedType")]
|
||||||
|
private class DerivedModelType : ModelType
|
||||||
|
{
|
||||||
|
[Model("OnDerivedProperty")]
|
||||||
|
public string DerivedProperty { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestContainer
|
||||||
|
{
|
||||||
|
[Model("OnProperty")]
|
||||||
|
public ModelType ModelProperty { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
@ -242,6 +241,55 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Assert.IsType<ClassValidator>(Assert.Single(attributes.TypeAttributes));
|
Assert.IsType<ClassValidator>(Assert.Single(attributes.TypeAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAttributesForParameter_WithModelType_IncludesTypeAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameters = typeof(MethodWithParamAttributesType)
|
||||||
|
.GetMethod(nameof(MethodWithParamAttributesType.Method))
|
||||||
|
.GetParameters();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attributes = ModelAttributes.GetAttributesForParameter(parameters[2], typeof(DerivedModelWithAttributes));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
attributes.Attributes,
|
||||||
|
attribute => Assert.IsType<BindRequiredAttribute>(attribute),
|
||||||
|
attribute => Assert.IsType<ModelBinderAttribute>(attribute),
|
||||||
|
attribute => Assert.IsType<ClassValidator>(attribute));
|
||||||
|
Assert.IsType<BindRequiredAttribute>(Assert.Single(attributes.ParameterAttributes));
|
||||||
|
Assert.Null(attributes.PropertyAttributes);
|
||||||
|
Assert.Collection(
|
||||||
|
attributes.TypeAttributes,
|
||||||
|
attribute => Assert.IsType<ModelBinderAttribute>(attribute),
|
||||||
|
attribute => Assert.IsType<ClassValidator>(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAttributesForProperty_WithModelType_IncludesTypeAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var property = typeof(MergedAttributes)
|
||||||
|
.GetProperty(nameof(MergedAttributes.BaseModel));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attributes = ModelAttributes.GetAttributesForProperty(typeof(MergedAttributes), property, typeof(DerivedModelWithAttributes));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
attributes.Attributes,
|
||||||
|
attribute => Assert.IsType<BindRequiredAttribute>(attribute),
|
||||||
|
attribute => Assert.IsType<ModelBinderAttribute>(attribute),
|
||||||
|
attribute => Assert.IsType<ClassValidator>(attribute));
|
||||||
|
Assert.IsType<BindRequiredAttribute>(Assert.Single(attributes.PropertyAttributes));
|
||||||
|
Assert.Null(attributes.ParameterAttributes);
|
||||||
|
Assert.Collection(
|
||||||
|
attributes.TypeAttributes,
|
||||||
|
attribute => Assert.IsType<ModelBinderAttribute>(attribute),
|
||||||
|
attribute => Assert.IsType<ClassValidator>(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
[ClassValidator]
|
[ClassValidator]
|
||||||
private class BaseModel
|
private class BaseModel
|
||||||
{
|
{
|
||||||
|
|
@ -272,6 +320,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ModelBinder(Name = "Custom")]
|
||||||
|
private class DerivedModelWithAttributes : BaseModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[ModelMetadataType(typeof(BaseModel))]
|
[ModelMetadataType(typeof(BaseModel))]
|
||||||
private class BaseViewModel
|
private class BaseViewModel
|
||||||
{
|
{
|
||||||
|
|
@ -313,6 +366,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public PropertyType Property { get; set; }
|
public PropertyType Property { get; set; }
|
||||||
|
|
||||||
|
[BindRequired]
|
||||||
|
public BaseModel BaseModel { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MergedAttributesMetadata
|
private class MergedAttributesMetadata
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
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;
|
||||||
|
|
@ -491,6 +492,220 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage);
|
actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindModelAsync_ForParameter_UsesValidationFromActualModel_WhenDerivedModelIsSet()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = GetType().GetMethod(nameof(TestMethodWithoutAttributes), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
var parameter = method.GetParameters()[0];
|
||||||
|
var parameterDescriptor = new ControllerParameterDescriptor
|
||||||
|
{
|
||||||
|
ParameterInfo = parameter,
|
||||||
|
Name = parameter.Name,
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionContext = GetControllerContext();
|
||||||
|
var modelMetadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
|
var model = new DerivedPerson();
|
||||||
|
var modelBindingResult = ModelBindingResult.Success(model);
|
||||||
|
|
||||||
|
var parameterBinder = new ParameterBinder(
|
||||||
|
modelMetadataProvider,
|
||||||
|
Mock.Of<IModelBinderFactory>(),
|
||||||
|
new DefaultObjectValidator(
|
||||||
|
modelMetadataProvider,
|
||||||
|
new[] { TestModelValidatorProvider.CreateDefaultProvider() }),
|
||||||
|
_optionsAccessor,
|
||||||
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameter);
|
||||||
|
var modelBinder = CreateMockModelBinder(modelBindingResult);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await parameterBinder.BindModelAsync(
|
||||||
|
actionContext,
|
||||||
|
modelBinder,
|
||||||
|
CreateMockValueProvider(),
|
||||||
|
parameterDescriptor,
|
||||||
|
modelMetadata,
|
||||||
|
value: null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
|
Assert.Same(model, result.Model);
|
||||||
|
|
||||||
|
Assert.False(actionContext.ModelState.IsValid);
|
||||||
|
Assert.Collection(
|
||||||
|
actionContext.ModelState,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal($"{parameter.Name}.{nameof(DerivedPerson.DerivedProperty)}", kvp.Key);
|
||||||
|
var error = Assert.Single(kvp.Value.Errors);
|
||||||
|
Assert.Equal("The DerivedProperty field is required.", error.ErrorMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindModelAsync_ForParameter_UsesValidationFromParameter_WhenDerivedModelIsSet()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = GetType().GetMethod(nameof(TestMethodWithAttributes), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
var parameter = method.GetParameters()[0];
|
||||||
|
var parameterDescriptor = new ControllerParameterDescriptor
|
||||||
|
{
|
||||||
|
ParameterInfo = parameter,
|
||||||
|
Name = parameter.Name,
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionContext = GetControllerContext();
|
||||||
|
var modelMetadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
|
var model = new DerivedPerson { DerivedProperty = "SomeValue" };
|
||||||
|
var modelBindingResult = ModelBindingResult.Success(model);
|
||||||
|
|
||||||
|
var parameterBinder = new ParameterBinder(
|
||||||
|
modelMetadataProvider,
|
||||||
|
Mock.Of<IModelBinderFactory>(),
|
||||||
|
new DefaultObjectValidator(
|
||||||
|
modelMetadataProvider,
|
||||||
|
new[] { TestModelValidatorProvider.CreateDefaultProvider() }),
|
||||||
|
_optionsAccessor,
|
||||||
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameter);
|
||||||
|
var modelBinder = CreateMockModelBinder(modelBindingResult);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await parameterBinder.BindModelAsync(
|
||||||
|
actionContext,
|
||||||
|
modelBinder,
|
||||||
|
CreateMockValueProvider(),
|
||||||
|
parameterDescriptor,
|
||||||
|
modelMetadata,
|
||||||
|
value: null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
|
Assert.Same(model, result.Model);
|
||||||
|
|
||||||
|
Assert.False(actionContext.ModelState.IsValid);
|
||||||
|
Assert.Collection(
|
||||||
|
actionContext.ModelState,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal(parameter.Name, kvp.Key);
|
||||||
|
var error = Assert.Single(kvp.Value.Errors);
|
||||||
|
Assert.Equal("Always Invalid", error.ErrorMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindModelAsync_ForProperty_UsesValidationFromActualModel_WhenDerivedModelIsSet()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var property = typeof(TestController).GetProperty(nameof(TestController.Model));
|
||||||
|
var parameterDescriptor = new ControllerBoundPropertyDescriptor
|
||||||
|
{
|
||||||
|
PropertyInfo = property,
|
||||||
|
Name = property.Name,
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionContext = GetControllerContext();
|
||||||
|
var modelMetadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
|
var model = new DerivedModel();
|
||||||
|
var modelBindingResult = ModelBindingResult.Success(model);
|
||||||
|
|
||||||
|
var parameterBinder = new ParameterBinder(
|
||||||
|
modelMetadataProvider,
|
||||||
|
Mock.Of<IModelBinderFactory>(),
|
||||||
|
new DefaultObjectValidator(
|
||||||
|
modelMetadataProvider,
|
||||||
|
new[] { TestModelValidatorProvider.CreateDefaultProvider() }),
|
||||||
|
_optionsAccessor,
|
||||||
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForProperty(property.DeclaringType, property.Name);
|
||||||
|
var modelBinder = CreateMockModelBinder(modelBindingResult);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await parameterBinder.BindModelAsync(
|
||||||
|
actionContext,
|
||||||
|
modelBinder,
|
||||||
|
CreateMockValueProvider(),
|
||||||
|
parameterDescriptor,
|
||||||
|
modelMetadata,
|
||||||
|
value: null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
|
Assert.Same(model, result.Model);
|
||||||
|
|
||||||
|
Assert.False(actionContext.ModelState.IsValid);
|
||||||
|
Assert.Collection(
|
||||||
|
actionContext.ModelState,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal($"{property.Name}.{nameof(DerivedPerson.DerivedProperty)}", kvp.Key);
|
||||||
|
var error = Assert.Single(kvp.Value.Errors);
|
||||||
|
Assert.Equal("The DerivedProperty field is required.", error.ErrorMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindModelAsync_ForProperty_UsesValidationOnProperty_WhenDerivedModelIsSet()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var property = typeof(TestControllerWithValidatedProperties).GetProperty(nameof(TestControllerWithValidatedProperties.Model));
|
||||||
|
var parameterDescriptor = new ControllerBoundPropertyDescriptor
|
||||||
|
{
|
||||||
|
PropertyInfo = property,
|
||||||
|
Name = property.Name,
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionContext = GetControllerContext();
|
||||||
|
var modelMetadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
|
var model = new DerivedModel { DerivedProperty = "some value" };
|
||||||
|
var modelBindingResult = ModelBindingResult.Success(model);
|
||||||
|
|
||||||
|
var parameterBinder = new ParameterBinder(
|
||||||
|
modelMetadataProvider,
|
||||||
|
Mock.Of<IModelBinderFactory>(),
|
||||||
|
new DefaultObjectValidator(
|
||||||
|
modelMetadataProvider,
|
||||||
|
new[] { TestModelValidatorProvider.CreateDefaultProvider() }),
|
||||||
|
_optionsAccessor,
|
||||||
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForProperty(property.DeclaringType, property.Name);
|
||||||
|
var modelBinder = CreateMockModelBinder(modelBindingResult);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await parameterBinder.BindModelAsync(
|
||||||
|
actionContext,
|
||||||
|
modelBinder,
|
||||||
|
CreateMockValueProvider(),
|
||||||
|
parameterDescriptor,
|
||||||
|
modelMetadata,
|
||||||
|
value: null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
|
Assert.Same(model, result.Model);
|
||||||
|
|
||||||
|
Assert.False(actionContext.ModelState.IsValid);
|
||||||
|
Assert.Collection(
|
||||||
|
actionContext.ModelState,
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal($"{property.Name}", kvp.Key);
|
||||||
|
var error = Assert.Single(kvp.Value.Errors);
|
||||||
|
Assert.Equal("Always Invalid", error.ErrorMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static ControllerContext GetControllerContext()
|
private static ControllerContext GetControllerContext()
|
||||||
{
|
{
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
|
|
@ -641,6 +856,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
public IList<Person> Kids { get; } = new List<Person>();
|
public IList<Person> Kids { get; } = new List<Person>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DerivedPerson : Person
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string DerivedProperty { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
private Person PersonProperty { get; set; }
|
||||||
|
|
||||||
public abstract class FakeModelMetadata : ModelMetadata
|
public abstract class FakeModelMetadata : ModelMetadata
|
||||||
{
|
{
|
||||||
public FakeModelMetadata()
|
public FakeModelMetadata()
|
||||||
|
|
@ -648,5 +872,44 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TestMethodWithoutAttributes(Person person) { }
|
||||||
|
|
||||||
|
private void TestMethodWithAttributes([Required][AlwaysInvalid] Person person) { }
|
||||||
|
|
||||||
|
private class TestController
|
||||||
|
{
|
||||||
|
public BaseModel Model { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestControllerWithValidatedProperties
|
||||||
|
{
|
||||||
|
[AlwaysInvalid]
|
||||||
|
[Required]
|
||||||
|
public BaseModel Model { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BaseModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DerivedModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string DerivedProperty { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AlwaysInvalidAttribute : ValidationAttribute
|
||||||
|
{
|
||||||
|
public AlwaysInvalidAttribute()
|
||||||
|
{
|
||||||
|
ErrorMessage = "Always Invalid";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsValid(object value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BasicWebSite.Models;
|
using BasicWebSite.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AngleSharp.Dom.Html;
|
using AngleSharp.Dom.Html;
|
||||||
using AngleSharp.Parser.Html;
|
using AngleSharp.Parser.Html;
|
||||||
using Xunit;
|
|
||||||
using Xunit.Sdk;
|
using Xunit.Sdk;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
@ -32,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
||||||
public static async Task<HttpResponseMessage> AssertStatusCodeAsync(this HttpResponseMessage response, HttpStatusCode expectedStatusCode)
|
public static async Task<HttpResponseMessage> AssertStatusCodeAsync(this HttpResponseMessage response, HttpStatusCode expectedStatusCode)
|
||||||
{
|
{
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
if (response.StatusCode == expectedStatusCode)
|
||||||
{
|
{
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using FormatterWebSite.Models;
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
@ -217,5 +218,100 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode);
|
Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindingWorksForPolymorphicTypes()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("PolymorphicBinding/ModelBound?DerivedProperty=Test");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
var result = JsonConvert.DeserializeObject<DerivedModel>(await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Equal("Test", result.DerivedProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidationUsesModelMetadataFromActualModelType_ForModelBoundParameters()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("PolymorphicBinding/ModelBound");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||||
|
var result = JObject.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Collection(
|
||||||
|
result.Properties(),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("DerivedProperty", p.Name);
|
||||||
|
var value = Assert.IsType<JArray>(p.Value);
|
||||||
|
Assert.Equal("The DerivedProperty field is required.", value.First);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InputFormatterWorksForPolymorphicTypes()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var input = "Test";
|
||||||
|
var response = await Client.PostAsJsonAsync("PolymorphicBinding/InputFormatted", input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
var result = JsonConvert.DeserializeObject<DerivedModel>(await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Equal(input, result.DerivedProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidationUsesModelMetadataFromActualModelType_ForInputFormattedParameters()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.PostAsJsonAsync("PolymorphicBinding/InputFormatted", string.Empty);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||||
|
var result = JObject.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Collection(
|
||||||
|
result.Properties(),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("DerivedProperty", p.Name);
|
||||||
|
var value = Assert.IsType<JArray>(p.Value);
|
||||||
|
Assert.Equal("The DerivedProperty field is required.", value.First);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InputFormatterWorksForPolymorphicProperties()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var input = "Test";
|
||||||
|
var response = await Client.PostAsJsonAsync("PolymorhpicPropertyBinding/Action", input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
var result = JsonConvert.DeserializeObject<DerivedModel>(await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Equal(input, result.DerivedProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidationUsesModelMetadataFromActualModelType_ForInputFormattedProperties()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.PostAsJsonAsync("PolymorhpicPropertyBinding/Action", string.Empty);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||||
|
var result = JObject.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Collection(
|
||||||
|
result.Properties(),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("DerivedProperty", p.Name);
|
||||||
|
var value = Assert.IsType<JArray>(p.Value);
|
||||||
|
Assert.Equal("The DerivedProperty field is required.", value.First);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
@ -738,6 +739,65 @@ Hello from /Pages/WithViewStart/Index.cshtml!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PolymorphicPropertiesOnPageModelsAreBound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var name = "TestName";
|
||||||
|
var age = 23;
|
||||||
|
var expected = $"Name = {name}, Age = {age}";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, "Pages/PropertyBinding/PolymorphicBinding")
|
||||||
|
{
|
||||||
|
Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "Name", name },
|
||||||
|
{ "Age", age.ToString() },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
await AddAntiforgeryHeaders(request);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expected, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PolymorphicPropertiesOnPageModelsAreValidated()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var name = "TestName";
|
||||||
|
var age = 123;
|
||||||
|
var expected = $"Name = {name}, Age = {age}";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, "Pages/PropertyBinding/PolymorphicBinding")
|
||||||
|
{
|
||||||
|
Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "Name", name },
|
||||||
|
{ "Age", age.ToString() },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
await AddAntiforgeryHeaders(request);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||||
|
var result = JObject.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.Collection(
|
||||||
|
result.Properties(),
|
||||||
|
p =>
|
||||||
|
{
|
||||||
|
Assert.Equal("Age", p.Name);
|
||||||
|
var value = Assert.IsType<JArray>(p.Value);
|
||||||
|
Assert.Equal("The field Age must be between 0 and 99.", value.First.ToString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HandlerMethodArgumentsAndPropertiesAreModelBound()
|
public async Task HandlerMethodArgumentsAndPropertiesAreModelBound()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -569,7 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ProcessAsync_Throws_IfGetViewAndFindReturnNotFoundResults()
|
public async Task ProcessAsync_Throws_If_NotOptional_And_GetViewAndFindReturnNotFoundResults()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var bufferScope = new TestViewBufferScope();
|
var bufferScope = new TestViewBufferScope();
|
||||||
|
|
@ -596,6 +596,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
Name = partialName,
|
Name = partialName,
|
||||||
ViewContext = viewContext,
|
ViewContext = viewContext,
|
||||||
ViewData = viewData,
|
ViewData = viewData,
|
||||||
|
Optional = false
|
||||||
};
|
};
|
||||||
var tagHelperContext = GetTagHelperContext();
|
var tagHelperContext = GetTagHelperContext();
|
||||||
var output = GetTagHelperOutput();
|
var output = GetTagHelperOutput();
|
||||||
|
|
@ -605,6 +606,47 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
() => tagHelper.ProcessAsync(tagHelperContext, output));
|
() => tagHelper.ProcessAsync(tagHelperContext, output));
|
||||||
Assert.Equal(expected, exception.Message);
|
Assert.Equal(expected, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ProcessAsync_IfOptional_And_ViewIsNotFound_WillNotRenderAnything()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = string.Empty;
|
||||||
|
var bufferScope = new TestViewBufferScope();
|
||||||
|
var partialName = "_ThisViewDoesNotExists";
|
||||||
|
var model = new object();
|
||||||
|
var viewContext = GetViewContext();
|
||||||
|
|
||||||
|
var view = new Mock<IView>();
|
||||||
|
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||||
|
.Callback((ViewContext v) =>
|
||||||
|
{
|
||||||
|
v.Writer.Write(expected);
|
||||||
|
})
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
|
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||||
|
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), partialName, false))
|
||||||
|
.Returns(ViewEngineResult.NotFound(partialName, searchedLocations: Array.Empty<string>()));
|
||||||
|
viewEngine.Setup(v => v.FindView(viewContext, partialName, false))
|
||||||
|
.Returns(ViewEngineResult.NotFound(partialName, searchedLocations: Array.Empty<string>()));
|
||||||
|
|
||||||
|
var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope)
|
||||||
|
{
|
||||||
|
Name = partialName,
|
||||||
|
ViewContext = viewContext,
|
||||||
|
Optional = true
|
||||||
|
};
|
||||||
|
var tagHelperContext = GetTagHelperContext();
|
||||||
|
var output = GetTagHelperOutput();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await tagHelper.ProcessAsync(tagHelperContext, output);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var content = HtmlContentUtilities.HtmlContentToString(output.Content, new HtmlTestEncoder());
|
||||||
|
Assert.Empty(content);
|
||||||
|
}
|
||||||
|
|
||||||
private static ViewContext GetViewContext()
|
private static ViewContext GetViewContext()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// 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.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
@ -36,7 +35,7 @@ namespace BasicWebSite
|
||||||
var previous = options.InvalidModelStateResponseFactory;
|
var previous = options.InvalidModelStateResponseFactory;
|
||||||
options.InvalidModelStateResponseFactory = context =>
|
options.InvalidModelStateResponseFactory = context =>
|
||||||
{
|
{
|
||||||
var result = (BadRequestObjectResult) previous(context);
|
var result = (BadRequestObjectResult)previous(context);
|
||||||
if (context.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is VndErrorAttribute))
|
if (context.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is VndErrorAttribute))
|
||||||
{
|
{
|
||||||
result.ContentTypes.Clear();
|
result.ContentTypes.Clear();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// 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 FormatterWebSite.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class PolymorhpicPropertyBindingController : ControllerBase
|
||||||
|
{
|
||||||
|
[FromBody]
|
||||||
|
public IModel Person { get; set; }
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Action()
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// 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 FormatterWebSite.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class PolymorphicBindingController : ControllerBase
|
||||||
|
{
|
||||||
|
public IActionResult ModelBound([ModelBinder(typeof(PolymorphicBinder))] BaseModel person)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(person);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult InputFormatted([FromBody] IModel person)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// 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 FormatterWebSite.Models;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace FormatterWebSite
|
||||||
|
{
|
||||||
|
public class IModelConverter : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(IModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
return new DerivedModel
|
||||||
|
{
|
||||||
|
DerivedProperty = reader.Value.ToString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Models
|
||||||
|
{
|
||||||
|
public class BaseModel
|
||||||
|
{
|
||||||
|
public string BaseProperty { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// 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.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Models
|
||||||
|
{
|
||||||
|
public class DerivedModel : BaseModel, IModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[StringLength(10)]
|
||||||
|
public string DerivedProperty { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Models
|
||||||
|
{
|
||||||
|
public interface IModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using FormatterWebSite.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class PolymorphicBinder : IModelBinder
|
||||||
|
{
|
||||||
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
var model = new DerivedModel
|
||||||
|
{
|
||||||
|
DerivedProperty = bindingContext.ValueProvider.GetValue(nameof(DerivedModel.DerivedProperty)).FirstValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
bindingContext.Result = ModelBindingResult.Success(model);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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 Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
|
@ -19,6 +20,8 @@ namespace FormatterWebSite
|
||||||
options.InputFormatters.Add(new StringInputFormatter());
|
options.InputFormatters.Add(new StringInputFormatter());
|
||||||
})
|
})
|
||||||
.AddXmlDataContractSerializerFormatters();
|
.AddXmlDataContractSerializerFormatters();
|
||||||
|
|
||||||
|
services.Configure<MvcJsonOptions>(options => { options.SerializerSettings.Converters.Insert(0, new IModelConverter()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace RazorPagesWebSite
|
||||||
|
{
|
||||||
|
public interface IUserModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,12 +5,17 @@ using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace RazorPagesWebSite
|
namespace RazorPagesWebSite
|
||||||
{
|
{
|
||||||
public class UserModel
|
public class UserModel : IUserModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
[Range(0, 99)]
|
[Range(0, 99)]
|
||||||
public int Age { get; set; }
|
public int Age { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Name = {Name}, Age = {Age}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace RazorPagesWebSite
|
||||||
|
{
|
||||||
|
public class PolymorphicBinding : PageModel
|
||||||
|
{
|
||||||
|
[ModelBinder(typeof(PolymorphicModelBinder))]
|
||||||
|
public IUserModel UserModel { get; set; }
|
||||||
|
|
||||||
|
public IActionResult OnPost()
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ContentResult { Content = UserModel.ToString() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
@page
|
||||||
|
@model PolymorphicBinding
|
||||||
|
|
||||||
|
<form action="">
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
</form>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace RazorPagesWebSite
|
||||||
|
{
|
||||||
|
public class PolymorphicModelBinder : IModelBinder
|
||||||
|
{
|
||||||
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
var ageValue = bindingContext.ValueProvider.GetValue(nameof(UserModel.Age));
|
||||||
|
var age = 0;
|
||||||
|
if (ageValue.Length != 0)
|
||||||
|
{
|
||||||
|
age = int.Parse(ageValue.FirstValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var model = new UserModel
|
||||||
|
{
|
||||||
|
Name = bindingContext.ValueProvider.GetValue(nameof(UserModel.Name)).FirstValue,
|
||||||
|
Age = age,
|
||||||
|
};
|
||||||
|
|
||||||
|
bindingContext.Result = ModelBindingResult.Success(model);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue