diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs index 249f59a606..552aa9da6e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding; @@ -43,18 +44,17 @@ namespace Microsoft.AspNet.Mvc nameof(actionContext)); } + + var methodParameters = actionDescriptor.MethodInfo.GetParameters(); var parameterMetadata = new List(); foreach (var parameter in actionDescriptor.Parameters) { + var parameterInfo = methodParameters.Where(p => p.Name == parameter.Name).Single(); var metadata = _modelMetadataProvider.GetMetadataForParameter( - methodInfo: actionDescriptor.MethodInfo, - parameterName: parameter.Name); + parameterInfo, + attributes: new object[] { parameter.BinderMetadata }); - if (metadata != null) - { - UpdateParameterMetadata(metadata, parameter.BinderMetadata); - parameterMetadata.Add(metadata); - } + parameterMetadata.Add(metadata); } var actionArguments = new Dictionary(StringComparer.Ordinal); @@ -62,20 +62,6 @@ namespace Microsoft.AspNet.Mvc return actionArguments; } - private void UpdateParameterMetadata(ModelMetadata metadata, IBinderMetadata binderMetadata) - { - if (binderMetadata != null) - { - metadata.BinderMetadata = binderMetadata; - } - - var nameProvider = binderMetadata as IModelNameProvider; - if (nameProvider != null && nameProvider.Name != null) - { - metadata.BinderModelName = nameProvider.Name; - } - } - private async Task PopulateArgumentAsync( ActionContext actionContext, ActionBindingContext bindingContext, diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs index 5af756c4ea..9d0c6fbbd4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs @@ -433,21 +433,14 @@ namespace Microsoft.AspNet.Mvc.Description public void WalkParameter() { + var parameterInfo = + Context.ActionDescriptor.MethodInfo.GetParameters() + .Where(p => p.Name == Parameter.Name) + .Single(); + var modelMetadata = Context.MetadataProvider.GetMetadataForParameter( - methodInfo: Context.ActionDescriptor.MethodInfo, - parameterName: Parameter.Name); - - var binderMetadata = Parameter.BinderMetadata; - if (binderMetadata != null) - { - modelMetadata.BinderMetadata = binderMetadata; - } - - var nameProvider = binderMetadata as IModelNameProvider; - if (nameProvider != null && nameProvider.Name != null) - { - modelMetadata.BinderModelName = nameProvider.Name; - } + parameterInfo, + attributes: new object[] { Parameter.BinderMetadata }); // Attempt to find a binding source for the parameter // @@ -482,7 +475,7 @@ namespace Microsoft.AspNet.Mvc.Description /// private bool Visit(ModelMetadata modelMetadata, BindingSource ambientSource, string containerName) { - var source = BindingSource.GetBindingSource(modelMetadata.BinderMetadata); + var source = modelMetadata.BindingSource; if (source != null && source.IsGreedy) { // We have a definite answer for this model. This is a greedy source like diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs index 7b267d17a4..c35ba08379 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs @@ -7,7 +7,7 @@ using Microsoft.AspNet.Mvc.ApplicationModels; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.OptionDescriptors; using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.Net.Http.Headers; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; namespace Microsoft.AspNet.Mvc { @@ -30,6 +30,7 @@ namespace Microsoft.AspNet.Mvc Filters = new List(); FormatterMappings = new FormatterMappings(); ValidationExcludeFilters = new List(); + ModelMetadataDetailsProviders = new List(); ModelValidatorProviders = new List(); CacheProfiles = new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -146,5 +147,20 @@ namespace Microsoft.AspNet.Mvc /// . /// public IDictionary CacheProfiles { get; } + + /// + /// Gets a list of instances that will be used to + /// create instances. + /// + /// + /// A provider should implement one or more of the following interfaces, depending on what + /// kind of details are provided: + ///
    + ///
  • + ///
  • + ///
  • + ///
+ ///
+ public IList ModelMetadataDetailsProviders { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index 292432e9ff..9dfdac729a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -1707,7 +1707,15 @@ namespace Microsoft.AspNet.Mvc.Core } /// - /// The model type '{0}' does not match the '{1}' type parameter. + /// The model's runtime type '{0}' is not assignable to the type '{1}'. + /// + internal static string ModelType_WrongType + { + get { return GetString("ModelType_WrongType"); } + } + + /// + /// The model's runtime type '{0}' is not assignable to the type '{1}'. /// internal static string FormatModelType_WrongType(object p0, object p1) { diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/BindingSource.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/BindingSource.cs index 73d5f263b7..0060d45d74 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/BindingSource.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/BindingSource.cs @@ -204,11 +204,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { return !(s1 == s2); } - - // THIS IS TEMP CODE, this will be moved to model metadata - public static BindingSource GetBindingSource(IBinderMetadata metadata) - { - return (metadata as IBindingSourceMetadata)?.BindingSource; - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs index 1098e5ba41..cdcfcbd90f 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs @@ -64,9 +64,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public async Task BindModelAsync(ModelBindingContext context) { - var bindingSourceMetadata = context.ModelMetadata.BinderMetadata as IBindingSourceMetadata; - var allowedBindingSource = bindingSourceMetadata?.BindingSource; - + var allowedBindingSource = context.ModelMetadata.BindingSource; if (allowedBindingSource == null || !allowedBindingSource.CanAcceptDataFrom(BindingSource)) { // Binding Sources are opt-in. This model either didn't specify one or specified something diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs index 93b0640632..32c3872dd8 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs @@ -139,7 +139,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // public IActionResult UpdatePerson([FromForm] Person person) { } // // In this example, [FromQuery] overrides the ambient data source (form). - var bindingSource = BindingSource.GetBindingSource(oldBindingContext.ModelMetadata.BinderMetadata); + var bindingSource = oldBindingContext.ModelMetadata.BindingSource; if (bindingSource != null && !bindingSource.IsGreedy) { var valueProvider = @@ -155,7 +155,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private static BodyBindingState GetBodyBindingState(ModelBindingContext oldBindingContext) { - var bindingSource = BindingSource.GetBindingSource(oldBindingContext.ModelMetadata.BinderMetadata); + var bindingSource = oldBindingContext.ModelMetadata.BindingSource; var willReadBodyWithFormatter = bindingSource == BindingSource.Body; var willReadBodyAsFormData = bindingSource == BindingSource.Form; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs index 16a571de30..08377a802c 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs @@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // // We skip this check if it is a top level object because we want to always evaluate // the creation of top level object (this is also required for ModelBinderAttribute to work.) - var bindingSource = BindingSource.GetBindingSource(bindingContext.ModelMetadata.BinderMetadata); + var bindingSource = bindingContext.ModelMetadata.BindingSource; if (!isTopLevelObject && bindingSource != null && bindingSource.IsGreedy) @@ -139,7 +139,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding foreach (var propertyMetadata in context.PropertyMetadata) { // This check will skip properties which are marked explicitly using a non value binder. - var bindingSource = BindingSource.GetBindingSource(propertyMetadata.BinderMetadata); + var bindingSource = propertyMetadata.BindingSource; if (bindingSource == null || !bindingSource.IsGreedy) { isAnyPropertyEnabledForValueProviderBasedBinding = true; @@ -168,7 +168,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { var valueProvider = bindingContext.ValueProvider; - var bindingSource = BindingSource.GetBindingSource(metadata.BinderMetadata); + var bindingSource = metadata.BindingSource; if (bindingSource != null && !bindingSource.IsGreedy) { var rootValueProvider = bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/EmptyModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/EmptyModelMetadataProvider.cs new file mode 100644 index 0000000000..6861b9b34f --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/EmptyModelMetadataProvider.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class EmptyModelMetadataProvider : DefaultModelMetadataProvider + { + public EmptyModelMetadataProvider() + : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[0])) + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/IModelMetadataProvider.cs similarity index 68% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs rename to src/Microsoft.AspNet.Mvc.ModelBinding/IModelMetadataProvider.cs index 0730ec2a07..1fb1db663c 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/IModelMetadataProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -10,10 +10,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public interface IModelMetadataProvider { - IEnumerable GetMetadataForProperties([NotNull] Type containerType); - ModelMetadata GetMetadataForType([NotNull] Type modelType); - ModelMetadata GetMetadataForParameter([NotNull] MethodInfo methodInfo, [NotNull] string parameterName); + IEnumerable GetMetadataForProperties([NotNull] Type modelType); + + ModelMetadata GetMetadataForParameter([NotNull] ParameterInfo parameter, IEnumerable attributes); } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs deleted file mode 100644 index f434d07232..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.Framework.Internal; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - public abstract class AssociatedMetadataProvider : IModelMetadataProvider - where TModelMetadata : ModelMetadata - { - private readonly ConcurrentDictionary _typeInfoCache = - new ConcurrentDictionary(); - - private readonly ConcurrentDictionary> _typePropertyInfoCache = - new ConcurrentDictionary>(); - - public IEnumerable GetMetadataForProperties([NotNull] Type containerType) - { - return GetMetadataForPropertiesCore(containerType); - } - - public ModelMetadata GetMetadataForProperty([NotNull] Type containerType, - [NotNull] string propertyName) - { - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(propertyName)); - } - - var typePropertyInfo = GetTypePropertyInformation(containerType); - - PropertyInformation propertyInfo; - if (!typePropertyInfo.TryGetValue(propertyName, out propertyInfo)) - { - var message = Resources.FormatCommon_PropertyNotFound(containerType, propertyName); - throw new ArgumentException(message, nameof(propertyName)); - } - - return CreatePropertyMetadata(propertyInfo); - } - - public ModelMetadata GetMetadataForType([NotNull] Type modelType) - { - var prototype = GetTypeInformation(modelType); - return CreateMetadataFromPrototype(prototype); - } - - public ModelMetadata GetMetadataForParameter( - [NotNull] MethodInfo methodInfo, - [NotNull] string parameterName) - { - if (string.IsNullOrEmpty(parameterName)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(parameterName)); - } - - var parameter = methodInfo.GetParameters().FirstOrDefault( - param => StringComparer.Ordinal.Equals(param.Name, parameterName)); - if (parameter == null) - { - var message = Resources.FormatCommon_ParameterNotFound(parameterName); - throw new ArgumentException(message, nameof(parameterName)); - } - - return GetMetadataForParameterCore(parameterName, parameter); - } - - // Override for creating the prototype metadata (without the model accessor). - /// - /// Creates a new instance. - /// - /// The set of attributes relevant for the new instance. - /// - /// containing this property. null unless this - /// describes a property. - /// - /// this describes. - /// - /// Name of the property (in ) or parameter this - /// describes. null or empty if this - /// describes a . - /// - /// A new instance. - protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable attributes, - Type containerType, - Type modelType, - string propertyName); - - // Override for applying the prototype + model accessor to yield the final metadata. - /// - /// Creates a new instance based on a . - /// - /// - /// that provides the basis for new instance. - /// - /// Accessor for model value of new instance. - /// - /// A new instance based on . - /// - protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype); - - private ModelMetadata GetMetadataForParameterCore( - string parameterName, - ParameterInfo parameter) - { - var parameterInfo = - CreateParameterInfo(parameter.ParameterType, - ModelAttributes.GetAttributesForParameter(parameter), - parameterName); - - var metadata = CreateMetadataFromPrototype(parameterInfo.Prototype); - return metadata; - } - - private IEnumerable GetMetadataForPropertiesCore(Type containerType) - { - var typePropertyInfo = GetTypePropertyInformation(containerType); - - foreach (var kvp in typePropertyInfo) - { - var propertyInfo = kvp.Value; - var propertyMetadata = CreatePropertyMetadata(propertyInfo); - yield return propertyMetadata; - } - } - - private TModelMetadata CreatePropertyMetadata(PropertyInformation propertyInfo) - { - var metadata = CreateMetadataFromPrototype(propertyInfo.Prototype); - if (propertyInfo.IsReadOnly) - { - metadata.IsReadOnly = true; - } - - return metadata; - } - - private TModelMetadata GetTypeInformation(Type type, IEnumerable associatedAttributes = null) - { - // This retrieval is implemented as a TryGetValue/TryAdd instead of a GetOrAdd - // to avoid the performance cost of creating instance delegates - TModelMetadata typeInfo; - if (!_typeInfoCache.TryGetValue(type, out typeInfo)) - { - typeInfo = CreateTypeInformation(type, associatedAttributes); - _typeInfoCache.TryAdd(type, typeInfo); - } - - return typeInfo; - } - - private Dictionary GetTypePropertyInformation(Type type) - { - // This retrieval is implemented as a TryGetValue/TryAdd instead of a GetOrAdd - // to avoid the performance cost of creating instance delegates - Dictionary typePropertyInfo; - if (!_typePropertyInfoCache.TryGetValue(type, out typePropertyInfo)) - { - typePropertyInfo = GetPropertiesLookup(type); - _typePropertyInfoCache.TryAdd(type, typePropertyInfo); - } - - return typePropertyInfo; - } - - private TModelMetadata CreateTypeInformation(Type type, IEnumerable associatedAttributes) - { - var attributes = ModelAttributes.GetAttributesForType(type); - if (associatedAttributes != null) - { - attributes = attributes.Concat(associatedAttributes); - } - - return CreateMetadataPrototype(attributes, containerType: null, modelType: type, propertyName: null); - } - - private PropertyInformation CreatePropertyInformation(Type containerType, PropertyHelper helper) - { - var property = helper.Property; - var attributes = ModelAttributes.GetAttributesForProperty(containerType, property); - - return new PropertyInformation - { - PropertyHelper = helper, - Prototype = CreateMetadataPrototype(attributes, - containerType, - property.PropertyType, - property.Name), - IsReadOnly = !property.CanWrite || property.SetMethod.IsPrivate - }; - } - - private Dictionary GetPropertiesLookup(Type containerType) - { - var properties = new Dictionary(StringComparer.Ordinal); - foreach (var propertyHelper in PropertyHelper.GetProperties(containerType)) - { - // Avoid re-generating a property descriptor if one has already been generated for the property name - if (!properties.ContainsKey(propertyHelper.Name)) - { - properties.Add(propertyHelper.Name, CreatePropertyInformation(containerType, propertyHelper)); - } - } - - return properties; - } - - private ParameterInformation CreateParameterInfo( - Type parameterType, - IEnumerable attributes, - string parameterName) - { - var metadataProtoType = CreateMetadataPrototype(attributes: attributes, - containerType: null, - modelType: parameterType, - propertyName: parameterName); - - return new ParameterInformation - { - Prototype = metadataProtoType - }; - } - - private sealed class ParameterInformation - { - public TModelMetadata Prototype { get; set; } - } - - private sealed class PropertyInformation - { - public PropertyHelper PropertyHelper { get; set; } - public TModelMetadata Prototype { get; set; } - public bool IsReadOnly { get; set; } - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadata.cs new file mode 100644 index 0000000000..7a82f22c94 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadata.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Binding metadata details for a . + /// + public class BindingMetadata + { + /// + /// Gets or sets the . + /// See . + /// + public BindingSource BindingSource { get; set; } + + /// + /// Gets or sets the binder model name. If null the property or parameter name will be used. + /// See . + /// + public string BinderModelName { get; set; } + + /// + /// Gets or sets the of the model binder used to bind the model. + /// See . + /// + public Type BinderType { get; set; } + + /// + /// Gets or sets a value indicating whether or not the model is read-only. Will be ignored + /// if the model metadata being created is not a property. If null then + /// will be computed based on the accessibility + /// of the property accessor and model . See . + /// + public bool? IsReadOnly { get; set; } + + /// + /// Gets or sets a value indicating whether or not the model is a required value. Will be ignored + /// if the model metadata being created is not a property. If null then + /// will be computed based on the model . + /// See . + /// + public bool? IsRequired { get; set; } + + /// + /// Gets or sets the . + /// See . + /// + public IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadataProviderContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadataProviderContext.cs new file mode 100644 index 0000000000..400bf90b85 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadataProviderContext.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A context for an . + /// + public class BindingMetadataProviderContext + { + /// + /// Creates a new . + /// + /// The for the . + /// The attributes for the . + public BindingMetadataProviderContext( + [NotNull] ModelMetadataIdentity key, + [NotNull] IReadOnlyList attributes) + { + Key = key; + Attributes = attributes; + BindingMetadata = new BindingMetadata(); + } + + /// + /// Gets the attributes. + /// + public IReadOnlyList Attributes { get; } + + /// + /// Gets the . + /// + public ModelMetadataIdentity Key { get; } + + /// + /// Gets the . + /// + public BindingMetadata BindingMetadata { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs deleted file mode 100644 index d463ef502f..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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 System.Linq; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - public class CachedDataAnnotationsMetadataAttributes - { - public CachedDataAnnotationsMetadataAttributes(IEnumerable attributes) - { - DataType = attributes.OfType().FirstOrDefault(); - Display = attributes.OfType().FirstOrDefault(); - DisplayColumn = attributes.OfType().FirstOrDefault(); - DisplayFormat = attributes.OfType().FirstOrDefault(); - Editable = attributes.OfType().FirstOrDefault(); - HiddenInput = attributes.OfType().FirstOrDefault(); - Required = attributes.OfType().FirstOrDefault(); - ScaffoldColumn = attributes.OfType().FirstOrDefault(); - UIHint = attributes.OfType().FirstOrDefault(); - - BinderMetadata = attributes.OfType().FirstOrDefault(); - PropertyBindingPredicateProviders = attributes.OfType(); - BinderModelNameProvider = attributes.OfType().FirstOrDefault(); - BinderTypeProviders = attributes.OfType(); - - // Special case the [DisplayFormat] attribute hanging off an applied [DataType] attribute. This property is - // non-null for DataType.Currency, DataType.Date, DataType.Time, and potentially custom [DataType] - // subclasses. The DataType.Currency, DataType.Date, and DataType.Time [DisplayFormat] attributes have a - // non-null DataFormatString and the DataType.Date and DataType.Time [DisplayFormat] attributes have - // ApplyFormatInEditMode==true. - if (DisplayFormat == null && DataType != null) - { - DisplayFormat = DataType.DisplayFormat; - } - } - - /// - /// Gets (or sets in subclasses) found in collection - /// passed to the constructor, - /// if any. - /// - public IEnumerable BinderTypeProviders { get; set; } - - /// - /// Gets (or sets in subclasses) found in collection passed to the - /// constructor, if any. - /// - public IBinderMetadata BinderMetadata { get; protected set; } - - /// - /// Gets (or sets in subclasses) found in collection passed to the - /// constructor, if any. - /// - public IModelNameProvider BinderModelNameProvider { get; protected set; } - - /// - /// Gets (or sets in subclasses) found in collection passed to the - /// constructor, if any. - /// - public DataTypeAttribute DataType { get; protected set; } - - public DisplayAttribute Display { get; protected set; } - - public DisplayColumnAttribute DisplayColumn { get; protected set; } - - /// - /// Gets (or sets in subclasses) found in collection passed to the - /// constructor, if any. - /// If no such attribute was found but a was, gets the - /// value. - /// - public DisplayFormatAttribute DisplayFormat { get; protected set; } - - public EditableAttribute Editable { get; protected set; } - - /// - /// Gets (or sets in subclasses) found in collection passed to the - /// constructor, if any. - /// - public HiddenInputAttribute HiddenInput { get; protected set; } - - /// - /// Gets (or sets in subclasses) found in - /// collection passed to the - /// constructor, if any. - /// - public IEnumerable PropertyBindingPredicateProviders { get; protected set; } - - public RequiredAttribute Required { get; protected set; } - - public ScaffoldColumnAttribute ScaffoldColumn { get; protected set; } - - /// - /// Gets (or sets in subclasses) found in collection passed to the - /// constructor, if any. - /// - public UIHintAttribute UIHint { get; protected set; } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs deleted file mode 100644 index 10bda645de..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Reflection; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - // Class does not override ComputeIsCollectionType() or ComputeIsComplexType() because values calculated in - // ModelMetadata's base implementation are correct. No data annotations override those calculations. - public class CachedDataAnnotationsModelMetadata : CachedModelMetadata - { - private static readonly string HtmlName = DataType.Html.ToString(); - private bool _isEditFormatStringFromCache; - - public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadata prototype) - : base(prototype) - { - } - - public CachedDataAnnotationsModelMetadata(DataAnnotationsModelMetadataProvider provider, - Type containerType, - Type modelType, - string propertyName, - IEnumerable attributes) - : base(provider, - containerType, - modelType, - propertyName, - new CachedDataAnnotationsMetadataAttributes(attributes)) - { - } - - protected override Type ComputeBinderType() - { - if (PrototypeCache.BinderTypeProviders != null) - { - // We want to respect the value set by the parameter (if any), and use the value specifed - // on the type as a fallback. - // - // We generalize this process, in case someone adds ordered providers (with count > 2) through - // extensibility. - foreach (var provider in PrototypeCache.BinderTypeProviders) - { - if (provider.BinderType != null) - { - return provider.BinderType; - } - } - } - - return base.ComputeBinderType(); - } - - protected override IBinderMetadata ComputeBinderMetadata() - { - return PrototypeCache.BinderMetadata != null - ? PrototypeCache.BinderMetadata - : base.ComputeBinderMetadata(); - } - - protected override string ComputeBinderModelNamePrefix() - { - return PrototypeCache.BinderModelNameProvider != null - ? PrototypeCache.BinderModelNameProvider.Name - : base.ComputeBinderModelNamePrefix(); - } - - protected override IPropertyBindingPredicateProvider ComputePropertyBindingPredicateProvider() - { - return PrototypeCache.PropertyBindingPredicateProviders.Any() - ? new CompositePredicateProvider(PrototypeCache.PropertyBindingPredicateProviders.ToArray()) - : null; - } - - protected override bool ComputeConvertEmptyStringToNull() - { - return PrototypeCache.DisplayFormat != null - ? PrototypeCache.DisplayFormat.ConvertEmptyStringToNull - : base.ComputeConvertEmptyStringToNull(); - } - - /// - /// Calculate based on presence of a - /// and its method. - /// - /// - /// Calculated value. - /// value if a exists. - /// "Html" if a exists with its - /// value false. null otherwise. - /// - protected override string ComputeDataTypeName() - { - if (PrototypeCache.DataType != null) - { - return PrototypeCache.DataType.GetDataTypeName(); - } - - if (PrototypeCache.DisplayFormat != null && !PrototypeCache.DisplayFormat.HtmlEncode) - { - return HtmlName; - } - - return base.ComputeDataTypeName(); - } - - protected override string ComputeDescription() - { - return PrototypeCache.Display != null - ? PrototypeCache.Display.GetDescription() - : base.ComputeDescription(); - } - - /// - /// Calculate based on presence of a - /// and its value. - /// - /// - /// Calculated value. - /// if a exists. - /// null otherwise. - /// - protected override string ComputeDisplayFormatString() - { - return PrototypeCache.DisplayFormat != null - ? PrototypeCache.DisplayFormat.DataFormatString - : base.ComputeDisplayFormatString(); - } - - protected override string ComputeDisplayName() - { - // DisplayName may be provided by DisplayAttribute. - // If that does not supply a name, then we fall back to the property name (in base.GetDisplayName()). - if (PrototypeCache.Display != null) - { - // DisplayAttribute doesn't require you to set a name, so this could be null. - var name = PrototypeCache.Display.GetName(); - if (name != null) - { - return name; - } - } - - return base.ComputeDisplayName(); - } - - /// - /// Calculate based on presence of a - /// and its and - /// values. - /// - /// - /// Calculated value. - /// if a exists and - /// its is true; null otherwise. - /// - /// - /// Subclasses overriding this method should also override to - /// ensure the two calculations remain consistent. - /// - protected override string ComputeEditFormatString() - { - if (PrototypeCache.DisplayFormat != null && PrototypeCache.DisplayFormat.ApplyFormatInEditMode) - { - _isEditFormatStringFromCache = true; - return PrototypeCache.DisplayFormat.DataFormatString; - } - - return base.ComputeEditFormatString(); - } - - /// - /// Calculate based on - /// and presence of and - /// . - /// - /// - /// Calculated value. true if - /// is non-null, non-empty, and came from the cache (was - /// not set directly). In addition the applied must not have come from an - /// applied . false otherwise. - /// - protected override bool ComputeHasNonDefaultEditFormat() - { - // Following calculation ignores possibility something (an IModelMetadataProvider) set EditFormatString - // directly. - if (!string.IsNullOrEmpty(EditFormatString) && _isEditFormatStringFromCache) - { - // Have a non-empty EditFormatString based on [DisplayFormat] from our cache. - if (PrototypeCache.DataType == null) - { - // Attributes include no [DataType]; [DisplayFormat] was applied directly. - return true; - } - - if (PrototypeCache.DataType.DisplayFormat != PrototypeCache.DisplayFormat) - { - // Attributes include separate [DataType] and [DisplayFormat]; [DisplayFormat] provided override. - return true; - } - - if (PrototypeCache.DataType.GetType() != typeof(DataTypeAttribute)) - { - // Attributes include [DisplayFormat] copied from [DataType] and [DataType] was of a subclass. - // Assume the [DataType] constructor used the protected DisplayFormat setter to override its - // default. That is derived [DataType] provided override. - return true; - } - } - - return base.ComputeHasNonDefaultEditFormat(); - } - - /// - /// Calculate based on presence of an - /// and its value. - /// - /// Calculated value. true if an - /// exists and its value is - /// false; false otherwise. - protected override bool ComputeHideSurroundingHtml() - { - if (PrototypeCache.HiddenInput != null) - { - return !PrototypeCache.HiddenInput.DisplayValue; - } - - return base.ComputeHideSurroundingHtml(); - } - - /// - /// Calculate based on presence of a - /// and its value. - /// - /// - /// Calculated value. false if a - /// exists and its value - /// is false. true otherwise. - /// - protected override bool ComputeHtmlEncode() - { - if (PrototypeCache.DisplayFormat != null) - { - return PrototypeCache.DisplayFormat.HtmlEncode; - } - - return base.ComputeHtmlEncode(); - } - - protected override bool ComputeIsReadOnly() - { - if (PrototypeCache.Editable != null) - { - return !PrototypeCache.Editable.AllowEdit; - } - - return base.ComputeIsReadOnly(); - } - - protected override bool ComputeIsRequired() - { - return (PrototypeCache.Required != null) || base.ComputeIsRequired(); - } - - /// - /// Calculate the value based on the presence of a - /// and its value. - /// - /// - /// Calculated value. - /// if a exists; - /// null otherwise. - /// - protected override string ComputeNullDisplayText() - { - return PrototypeCache.DisplayFormat != null - ? PrototypeCache.DisplayFormat.NullDisplayText - : base.ComputeNullDisplayText(); - } - - /// - /// Calculate the value based on presence of a - /// and its value. - /// - /// - /// Calculated value. if a - /// exists and its has been set; - /// 10000 otherwise. - /// - protected override int ComputeOrder() - { - var result = PrototypeCache.Display?.GetOrder(); - - return result ?? base.ComputeOrder(); - } - - /// - protected override string ComputeSimpleDisplayProperty() - { - if (!string.IsNullOrEmpty(PrototypeCache.DisplayColumn?.DisplayColumn)) - { - var displayColumnProperty = ModelType.GetTypeInfo().GetDeclaredProperty( - PrototypeCache.DisplayColumn.DisplayColumn); - ValidateDisplayColumnAttribute(PrototypeCache.DisplayColumn, displayColumnProperty, ModelType); - - return displayColumnProperty.Name; - } - - return base.ComputeSimpleDisplayProperty(); - } - - protected override bool ComputeShowForDisplay() - { - return PrototypeCache.ScaffoldColumn != null - ? PrototypeCache.ScaffoldColumn.Scaffold - : base.ComputeShowForDisplay(); - } - - protected override bool ComputeShowForEdit() - { - return PrototypeCache.ScaffoldColumn != null - ? PrototypeCache.ScaffoldColumn.Scaffold - : base.ComputeShowForEdit(); - } - - /// - /// Calculate the value based on presence of a - /// and its value or presence of a - /// when no exists. - /// - /// - /// Calculated value. if a - /// exists. "HiddenInput" if a exists - /// and no exists. null otherwise. - /// - protected override string ComputeTemplateHint() - { - if (PrototypeCache.UIHint != null) - { - return PrototypeCache.UIHint.UIHint; - } - - if (PrototypeCache.HiddenInput != null) - { - return "HiddenInput"; - } - - return base.ComputeTemplateHint(); - } - - private static void ValidateDisplayColumnAttribute(DisplayColumnAttribute displayColumnAttribute, - PropertyInfo displayColumnProperty, Type modelType) - { - if (displayColumnProperty == null) - { - throw new InvalidOperationException( - Resources.FormatDataAnnotationsModelMetadataProvider_UnknownProperty( - modelType.FullName, displayColumnAttribute.DisplayColumn)); - } - - if (displayColumnProperty.GetGetMethod() == null) - { - throw new InvalidOperationException( - Resources.FormatDataAnnotationsModelMetadataProvider_UnreadableProperty( - modelType.FullName, displayColumnAttribute.DisplayColumn)); - } - } - - private class CompositePredicateProvider : IPropertyBindingPredicateProvider - { - private readonly IPropertyBindingPredicateProvider[] _providers; - - public CompositePredicateProvider(IPropertyBindingPredicateProvider[] providers) - { - _providers = providers; - } - - public Func PropertyFilter - { - get - { - return CreatePredicate(); - } - } - - private Func CreatePredicate() - { - var predicates = _providers - .Select(p => p.PropertyFilter) - .Where(p => p != null) - .ToArray(); - - return (context, propertyName) => - { - foreach (var predicate in predicates) - { - if (!predicate(context, propertyName)) - { - return false; - } - } - - return true; - }; - } - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs deleted file mode 100644 index ece05c4887..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs +++ /dev/null @@ -1,727 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - // This class assumes that model metadata is expensive to create, and allows the user to - // stash a cache object that can be copied around as a prototype to make creation and - // computation quicker. It delegates the retrieval of values to getter methods, the results - // of which are cached on a per-metadata-instance basis. - // - // This allows flexible caching strategies: either caching the source of information across - // instances or caching of the actual information itself, depending on what the developer - // decides to put into the prototype cache. - public abstract class CachedModelMetadata : ModelMetadata - { - private bool _convertEmptyStringToNull; - private string _dataTypeName; - private string _description; - private string _displayFormatString; - private string _displayName; - private string _editFormatString; - private bool _hasNonDefaultEditFormat; - private bool _hideSurroundingHtml; - private bool _htmlEncode; - private bool _isCollectionType; - private bool _isComplexType; - private bool _isReadOnly; - private bool _isRequired; - private string _nullDisplayText; - private int _order; - private bool _showForDisplay; - private bool _showForEdit; - private string _simpleDisplayProperty; - private string _templateHint; - private IBinderMetadata _binderMetadata; - private string _binderModelName; - private IPropertyBindingPredicateProvider _propertyBindingPredicateProvider; - private Type _binderType; - - private bool _convertEmptyStringToNullComputed; - private bool _dataTypeNameComputed; - private bool _descriptionComputed; - private bool _displayFormatStringComputed; - private bool _displayNameComputed; - private bool _editFormatStringComputed; - private bool _hasNonDefaultEditFormatComputed; - private bool _hideSurroundingHtmlComputed; - private bool _htmlEncodeComputed; - private bool _isCollectionTypeComputed; - private bool _isComplexTypeComputed; - private bool _isReadOnlyComputed; - private bool _isRequiredComputed; - private bool _nullDisplayTextComputed; - private bool _orderComputed; - private bool _showForDisplayComputed; - private bool _showForEditComputed; - private bool _simpleDisplayPropertyComputed; - private bool _templateHintComputed; - private bool _isBinderMetadataComputed; - private bool _isBinderModelNameComputed; - private bool _isBinderTypeComputed; - private bool _propertyBindingPredicateProviderComputed; - - // Constructor for creating real instances of the metadata class based on a prototype - protected CachedModelMetadata(CachedModelMetadata prototype) - : base(prototype.Provider, - prototype.ContainerType, - prototype.ModelType, - prototype.PropertyName) - { - CacheKey = prototype.CacheKey; - PrototypeCache = prototype.PrototypeCache; - _isComplexType = prototype.IsComplexType; - _isComplexTypeComputed = true; - } - - // Constructor for creating the prototype instances of the metadata class - protected CachedModelMetadata(DataAnnotationsModelMetadataProvider provider, - Type containerType, - Type modelType, - string propertyName, - TPrototypeCache prototypeCache) - : base(provider, containerType, modelType: modelType, propertyName: propertyName) - { - PrototypeCache = prototypeCache; - } - - // Sealing for consistency with other properties. - // ModelMetadata already caches the collection and we have no use case for a ComputeAdditionalValues() method. - /// - public sealed override IDictionary AdditionalValues - { - get - { - return base.AdditionalValues; - } - } - - /// - public sealed override IBinderMetadata BinderMetadata - { - get - { - if (!_isBinderMetadataComputed) - { - _binderMetadata = ComputeBinderMetadata(); - _isBinderMetadataComputed = true; - } - - return _binderMetadata; - } - - set - { - _binderMetadata = value; - _isBinderMetadataComputed = true; - } - } - - /// - public sealed override string BinderModelName - { - get - { - if (!_isBinderModelNameComputed) - { - _binderModelName = ComputeBinderModelNamePrefix(); - _isBinderModelNameComputed = true; - } - - return _binderModelName; - } - - set - { - _binderModelName = value; - _isBinderModelNameComputed = true; - } - } - - /// - public sealed override bool ConvertEmptyStringToNull - { - get - { - if (!_convertEmptyStringToNullComputed) - { - _convertEmptyStringToNull = ComputeConvertEmptyStringToNull(); - _convertEmptyStringToNullComputed = true; - } - return _convertEmptyStringToNull; - } - set - { - _convertEmptyStringToNull = value; - _convertEmptyStringToNullComputed = true; - } - } - - /// - public sealed override string DataTypeName - { - get - { - if (!_dataTypeNameComputed) - { - _dataTypeName = ComputeDataTypeName(); - _dataTypeNameComputed = true; - } - - return _dataTypeName; - } - - set - { - _dataTypeName = value; - _dataTypeNameComputed = true; - } - } - - /// - public sealed override string Description - { - get - { - if (!_descriptionComputed) - { - _description = ComputeDescription(); - _descriptionComputed = true; - } - return _description; - } - set - { - _description = value; - _descriptionComputed = true; - } - } - - /// - public sealed override string DisplayFormatString - { - get - { - if (!_displayFormatStringComputed) - { - _displayFormatString = ComputeDisplayFormatString(); - _displayFormatStringComputed = true; - } - - return _displayFormatString; - } - - set - { - _displayFormatString = value; - _displayFormatStringComputed = true; - } - } - - /// - public sealed override string DisplayName - { - get - { - if (!_displayNameComputed) - { - _displayName = ComputeDisplayName(); - _displayNameComputed = true; - } - - return _displayName; - } - set - { - _displayName = value; - _displayNameComputed = true; - } - } - - /// - public sealed override string EditFormatString - { - get - { - if (!_editFormatStringComputed) - { - _editFormatString = ComputeEditFormatString(); - _editFormatStringComputed = true; - } - - return _editFormatString; - } - - set - { - _editFormatString = value; - _editFormatStringComputed = true; - } - } - - /// - public sealed override bool HasNonDefaultEditFormat - { - get - { - if (!_hasNonDefaultEditFormatComputed) - { - _hasNonDefaultEditFormat = ComputeHasNonDefaultEditFormat(); - _hasNonDefaultEditFormatComputed = true; - } - - return _hasNonDefaultEditFormat; - } - - set - { - _hasNonDefaultEditFormat = value; - _hasNonDefaultEditFormatComputed = true; - } - } - - /// - public sealed override bool HideSurroundingHtml - { - get - { - if (!_hideSurroundingHtmlComputed) - { - _hideSurroundingHtml = ComputeHideSurroundingHtml(); - _hideSurroundingHtmlComputed = true; - } - - return _hideSurroundingHtml; - } - - set - { - _hideSurroundingHtml = value; - _hideSurroundingHtmlComputed = true; - } - } - - /// - public sealed override bool HtmlEncode - { - get - { - if (!_htmlEncodeComputed) - { - _htmlEncode = ComputeHtmlEncode(); - _htmlEncodeComputed = true; - } - - return _htmlEncode; - } - - set - { - _htmlEncode = value; - _htmlEncodeComputed = true; - } - } - - /// - public sealed override bool IsCollectionType - { - get - { - if (!_isCollectionTypeComputed) - { - _isCollectionType = ComputeIsCollectionType(); - _isCollectionTypeComputed = true; - } - - return _isCollectionType; - } - } - - /// - public sealed override bool IsComplexType - { - get - { - if (!_isComplexTypeComputed) - { - _isComplexType = ComputeIsComplexType(); - _isComplexTypeComputed = true; - } - return _isComplexType; - } - } - - /// - public sealed override bool IsReadOnly - { - get - { - if (!_isReadOnlyComputed) - { - _isReadOnly = ComputeIsReadOnly(); - _isReadOnlyComputed = true; - } - return _isReadOnly; - } - set - { - _isReadOnly = value; - _isReadOnlyComputed = true; - } - } - - /// - public sealed override bool IsRequired - { - get - { - if (!_isRequiredComputed) - { - _isRequired = ComputeIsRequired(); - _isRequiredComputed = true; - } - return _isRequired; - } - set - { - _isRequired = value; - _isRequiredComputed = true; - } - } - - /// - public sealed override string NullDisplayText - { - get - { - if (!_nullDisplayTextComputed) - { - _nullDisplayText = ComputeNullDisplayText(); - _nullDisplayTextComputed = true; - } - return _nullDisplayText; - } - set - { - _nullDisplayText = value; - _nullDisplayTextComputed = true; - } - } - - /// - public sealed override int Order - { - get - { - if (!_orderComputed) - { - _order = ComputeOrder(); - _orderComputed = true; - } - - return _order; - } - - set - { - _order = value; - _orderComputed = true; - } - } - - public sealed override IPropertyBindingPredicateProvider PropertyBindingPredicateProvider - { - get - { - if (!_propertyBindingPredicateProviderComputed) - { - _propertyBindingPredicateProvider = ComputePropertyBindingPredicateProvider(); - _propertyBindingPredicateProviderComputed = true; - } - return _propertyBindingPredicateProvider; - } - - set - { - _propertyBindingPredicateProvider = value; - _propertyBindingPredicateProviderComputed = true; - } - } - - // Sealing for consistency with other properties. - // ModelMetadata already caches the collection and we have no use case for a ComputeProperties() method. - /// - public sealed override ModelPropertyCollection Properties - { - get - { - return base.Properties; - } - } - - /// - public sealed override bool ShowForDisplay - { - get - { - if (!_showForDisplayComputed) - { - _showForDisplay = ComputeShowForDisplay(); - _showForDisplayComputed = true; - } - return _showForDisplay; - } - set - { - _showForDisplay = value; - _showForDisplayComputed = true; - } - } - - /// - public sealed override bool ShowForEdit - { - get - { - if (!_showForEditComputed) - { - _showForEdit = ComputeShowForEdit(); - _showForEditComputed = true; - } - return _showForEdit; - } - set - { - _showForEdit = value; - _showForEditComputed = true; - } - } - - /// - public sealed override string SimpleDisplayProperty - { - get - { - if (!_simpleDisplayPropertyComputed) - { - _simpleDisplayProperty = ComputeSimpleDisplayProperty(); - _simpleDisplayPropertyComputed = true; - } - return _simpleDisplayProperty; - } - set - { - _simpleDisplayProperty = value; - _simpleDisplayPropertyComputed = true; - } - } - - /// - public sealed override string TemplateHint - { - get - { - if (!_templateHintComputed) - { - _templateHint = ComputeTemplateHint(); - _templateHintComputed = true; - } - - return _templateHint; - } - - set - { - _templateHint = value; - _templateHintComputed = true; - } - } - - /// - public sealed override Type BinderType - { - get - { - if (!_isBinderTypeComputed) - { - _binderType = ComputeBinderType(); - _isBinderTypeComputed = true; - } - return _binderType; - } - set - { - _binderType = value; - _isBinderTypeComputed = true; - } - } - - protected TPrototypeCache PrototypeCache { get; set; } - - protected virtual Type ComputeBinderType() - { - return base.BinderType; - } - - protected virtual IBinderMetadata ComputeBinderMetadata() - { - return base.BinderMetadata; - } - - protected virtual IPropertyBindingPredicateProvider ComputePropertyBindingPredicateProvider() - { - return base.PropertyBindingPredicateProvider; - } - - protected virtual string ComputeBinderModelNamePrefix() - { - return base.BinderModelName; - } - - protected virtual bool ComputeConvertEmptyStringToNull() - { - return base.ConvertEmptyStringToNull; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual string ComputeDataTypeName() - { - return base.DataTypeName; - } - - protected virtual string ComputeDescription() - { - return base.Description; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual string ComputeDisplayFormatString() - { - return base.DisplayFormatString; - } - - protected virtual string ComputeDisplayName() - { - return base.DisplayName; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual string ComputeEditFormatString() - { - return base.EditFormatString; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual bool ComputeHasNonDefaultEditFormat() - { - return base.HasNonDefaultEditFormat; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual bool ComputeHideSurroundingHtml() - { - return base.HideSurroundingHtml; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual bool ComputeHtmlEncode() - { - return base.HtmlEncode; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual bool ComputeIsCollectionType() - { - return base.IsCollectionType; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual bool ComputeIsComplexType() - { - return base.IsComplexType; - } - - protected virtual bool ComputeIsReadOnly() - { - return base.IsReadOnly; - } - - protected virtual bool ComputeIsRequired() - { - return base.IsRequired; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual string ComputeNullDisplayText() - { - return base.NullDisplayText; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual int ComputeOrder() - { - return base.Order; - } - - protected virtual bool ComputeShowForDisplay() - { - return base.ShowForDisplay; - } - - protected virtual bool ComputeShowForEdit() - { - return base.ShowForEdit; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual string ComputeSimpleDisplayProperty() - { - return base.SimpleDisplayProperty; - } - - /// - /// Calculate the value. - /// - /// Calculated value. - protected virtual string ComputeTemplateHint() - { - return base.TemplateHint; - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsMetadataDetailsProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsMetadataDetailsProvider.cs new file mode 100644 index 0000000000..45e10e522b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsMetadataDetailsProvider.cs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 System.Linq; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// An implementation of and for + /// the System.ComponentModel.DataAnnotations attribute classes. + /// + public class DataAnnotationsMetadataDetailsProvider : + IBindingMetadataProvider, + IDisplayMetadataProvider + { + /// + public void GetBindingMetadata([NotNull] BindingMetadataProviderContext context) + { + context.BindingMetadata.IsRequired = context.Attributes.OfType().Any(); + + var editableAttribute = context.Attributes.OfType().FirstOrDefault(); + if (editableAttribute != null) + { + context.BindingMetadata.IsReadOnly = !editableAttribute.AllowEdit; + } + } + + /// + public void GetDisplayMetadata([NotNull] DisplayMetadataProviderContext context) + { + var attributes = context.Attributes; + var dataTypeAttribute = attributes.OfType().FirstOrDefault(); + var displayAttribute = attributes.OfType().FirstOrDefault(); + var displayColumnAttribute = attributes.OfType().FirstOrDefault(); + var displayFormatAttribute = attributes.OfType().FirstOrDefault(); + var hiddenInputAttribute = attributes.OfType().FirstOrDefault(); + var scaffoldColumnAttribute = attributes.OfType().FirstOrDefault(); + var uiHintAttribute = attributes.OfType().FirstOrDefault(); + + // Special case the [DisplayFormat] attribute hanging off an applied [DataType] attribute. This property is + // non-null for DataType.Currency, DataType.Date, DataType.Time, and potentially custom [DataType] + // subclasses. The DataType.Currency, DataType.Date, and DataType.Time [DisplayFormat] attributes have a + // non-null DataFormatString and the DataType.Date and DataType.Time [DisplayFormat] attributes have + // ApplyFormatInEditMode==true. + if (displayFormatAttribute == null && dataTypeAttribute != null) + { + displayFormatAttribute = dataTypeAttribute.DisplayFormat; + } + + var displayMetadata = context.DisplayMetadata; + + // ConvertEmptyStringToNull + if (displayFormatAttribute != null) + { + displayMetadata.ConvertEmptyStringToNull = displayFormatAttribute.ConvertEmptyStringToNull; + } + + // DataTypeName + if (dataTypeAttribute != null) + { + displayMetadata.DataTypeName = dataTypeAttribute.GetDataTypeName(); + } + else if (displayFormatAttribute != null && !displayFormatAttribute.HtmlEncode) + { + displayMetadata.DataTypeName = DataType.Html.ToString(); + } + + // Description + if (displayAttribute != null) + { + displayMetadata.Description = displayAttribute.GetDescription(); + } + + // DisplayFormat + if (displayFormatAttribute != null) + { + displayMetadata.DisplayFormatString = displayFormatAttribute.DataFormatString; + } + + // DisplayName + if (displayAttribute != null) + { + displayMetadata.DisplayName = displayAttribute.GetName(); + } + + if (displayFormatAttribute != null && displayFormatAttribute.ApplyFormatInEditMode) + { + displayMetadata.EditFormatString = displayFormatAttribute.DataFormatString; + } + + // HasNonDefaultEditFormat + if (!string.IsNullOrEmpty(displayFormatAttribute?.DataFormatString) && + displayFormatAttribute?.ApplyFormatInEditMode == true) + { + // Have a non-empty EditFormatString based on [DisplayFormat] from our cache. + if (dataTypeAttribute == null) + { + // Attributes include no [DataType]; [DisplayFormat] was applied directly. + displayMetadata.HasNonDefaultEditFormat = true; + } + else if (dataTypeAttribute.DisplayFormat != displayFormatAttribute) + { + // Attributes include separate [DataType] and [DisplayFormat]; [DisplayFormat] provided override. + displayMetadata.HasNonDefaultEditFormat = true; + } + else if (dataTypeAttribute.GetType() != typeof(DataTypeAttribute)) + { + // Attributes include [DisplayFormat] copied from [DataType] and [DataType] was of a subclass. + // Assume the [DataType] constructor used the protected DisplayFormat setter to override its + // default. That is derived [DataType] provided override. + displayMetadata.HasNonDefaultEditFormat = true; + } + } + + // HideSurroundingHtml + if (hiddenInputAttribute != null) + { + displayMetadata.HideSurroundingHtml = !hiddenInputAttribute.DisplayValue; + } + + // HtmlEncode + if (displayFormatAttribute != null) + { + displayMetadata.HtmlEncode = displayFormatAttribute.HtmlEncode; + } + + // NullDisplayText + if (displayFormatAttribute != null) + { + displayMetadata.NullDisplayText = displayFormatAttribute.NullDisplayText; + } + + // Order + if (displayAttribute?.GetOrder() != null) + { + displayMetadata.Order = displayAttribute.GetOrder().Value; + } + + // ShowForDisplay + if (scaffoldColumnAttribute != null) + { + displayMetadata.ShowForDisplay = scaffoldColumnAttribute.Scaffold; + } + + // ShowForEdit + if (scaffoldColumnAttribute != null) + { + displayMetadata.ShowForEdit = scaffoldColumnAttribute.Scaffold; + } + + // SimpleDisplayProperty + if (displayColumnAttribute != null) + { + displayMetadata.SimpleDisplayProperty = displayColumnAttribute.DisplayColumn; + } + + // TemplateHinte + if (uiHintAttribute != null) + { + displayMetadata.TemplateHint = uiHintAttribute.UIHint; + } + else if (hiddenInputAttribute != null) + { + displayMetadata.TemplateHint = "HiddenInput"; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsModelMetadataProvider.cs deleted file mode 100644 index af1c4dc781..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsModelMetadataProvider.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - /// - /// An implementation that provides - /// instances. Those instances primarily calculate property values - /// using attributes from the namespace. - /// - public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider - { - /// - protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype( - IEnumerable attributes, - Type containerType, - Type modelType, - string propertyName) - { - return new CachedDataAnnotationsModelMetadata(this, containerType, modelType, propertyName, attributes); - } - - /// - /// - /// Copies only a few values from the . Unlikely the rest have been computed. - /// - protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype( - CachedDataAnnotationsModelMetadata prototype) - { - var metadata = new CachedDataAnnotationsModelMetadata(prototype); - foreach (var keyValuePair in prototype.AdditionalValues) - { - metadata.AdditionalValues.Add(keyValuePair); - } - - return metadata; - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs new file mode 100644 index 0000000000..b9dcbe416f --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A default implementation of . + /// + public class DefaultBindingMetadataProvider : IBindingMetadataProvider + { + /// + public void GetBindingMetadata([NotNull] BindingMetadataProviderContext context) + { + // For Model Name - we only use the first attribute we find. An attribute on the parameter + // is considered an override of an attribute on the type. This is for compatibility with [Bind] + // from MVC 5. + // + // BinderType and BindingSource fall back to the first attribute to provide a value. + + // BinderModelName + var binderModelNameAttribute = context.Attributes.OfType().FirstOrDefault(); + if (binderModelNameAttribute?.Name != null) + { + context.BindingMetadata.BinderModelName = binderModelNameAttribute.Name; + } + + // BinderType + foreach (var binderTypeAttribute in context.Attributes.OfType()) + { + if (binderTypeAttribute.BinderType != null) + { + context.BindingMetadata.BinderType = binderTypeAttribute.BinderType; + break; + } + } + + // BindingSource + foreach (var bindingSourceAttribute in context.Attributes.OfType()) + { + if (bindingSourceAttribute.BindingSource != null) + { + context.BindingMetadata.BindingSource = bindingSourceAttribute.BindingSource; + break; + } + } + + // PropertyBindingPredicateProvider + var predicateProviders = context.Attributes.OfType().ToArray(); + if (predicateProviders.Length > 0) + { + context.BindingMetadata.PropertyBindingPredicateProvider = new CompositePredicateProvider( + predicateProviders); + } + } + + private class CompositePredicateProvider : IPropertyBindingPredicateProvider + { + private readonly IEnumerable _providers; + + public CompositePredicateProvider(IEnumerable providers) + { + _providers = providers; + } + + public Func PropertyFilter + { + get + { + return CreatePredicate(); + } + } + + private Func CreatePredicate() + { + var predicates = _providers + .Select(p => p.PropertyFilter) + .Where(p => p != null); + + return (context, propertyName) => + { + foreach (var predicate in predicates) + { + if (!predicate(context, propertyName)) + { + return false; + } + } + + return true; + }; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultCompositeMetadataDetailsProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultCompositeMetadataDetailsProvider.cs new file mode 100644 index 0000000000..2cb628277d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultCompositeMetadataDetailsProvider.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A default implementation of . + /// + public class DefaultCompositeMetadataDetailsProvider : ICompositeMetadataDetailsProvider + { + private readonly IEnumerable _providers; + + /// + /// Creates a new . + /// + /// The set of instances. + public DefaultCompositeMetadataDetailsProvider(IEnumerable providers) + { + _providers = providers; + } + + /// + public virtual void GetBindingMetadata([NotNull] BindingMetadataProviderContext context) + { + foreach (var provider in _providers.OfType()) + { + provider.GetBindingMetadata(context); + } + } + + /// + public virtual void GetDisplayMetadata([NotNull] DisplayMetadataProviderContext context) + { + foreach (var provider in _providers.OfType()) + { + provider.GetDisplayMetadata(context); + } + } + + /// + public virtual void GetValidationMetadata([NotNull] ValidationMetadataProviderContext context) + { + foreach (var provider in _providers.OfType()) + { + provider.GetValidationMetadata(context); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultMetadataDetailsCache.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultMetadataDetailsCache.cs new file mode 100644 index 0000000000..1fff319426 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultMetadataDetailsCache.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A cache of metadata objects for a . + /// + /// + /// These instances are shared by all instances representing + /// the same , property, or parameter. Any modifications to the data must be + /// thread-safe for multiple readers and writers. + /// + public class DefaultMetadataDetailsCache + { + /// + /// Creates a new . + /// + /// The . + /// The set of model attributes. + public DefaultMetadataDetailsCache(ModelMetadataIdentity key, IReadOnlyList attributes) + { + Key = key; + Attributes = attributes; + } + + /// + /// Gets or sets the set of model attributes. + /// + public IReadOnlyList Attributes { get; } + + /// + /// Gets or sets the + /// + public BindingMetadata BindingMetadata { get; set; } + + /// + /// Gets or sets the + /// + public DisplayMetadata DisplayMetadata { get; set; } + + /// + /// Gets or sets the + /// + public ModelMetadataIdentity Key { get; } + + /// + /// Gets or sets a property accessor delegate to get the property value from a model object. + /// + public Func PropertyAccessor { get; set; } + + /// + /// Gets or sets a property setter delegate to set the property value on a model object. + /// + public Action PropertySetter { get; set; } + + /// + /// Gets or sets the + /// + public ValidationMetadata ValidationMetadata { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs new file mode 100644 index 0000000000..c83a27c330 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs @@ -0,0 +1,360 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A default implementation. + /// + public class DefaultModelMetadata : ModelMetadata + { + private readonly IModelMetadataProvider _provider; + private readonly ICompositeMetadataDetailsProvider _detailsProvider; + private readonly DefaultMetadataDetailsCache _cache; + + private ReadOnlyDictionary _additionalValues; + private bool? _isReadOnly; + private bool? _isRequired; + private ModelPropertyCollection _properties; + + /// + /// Creates a new . + /// + /// The . + /// The . + /// The . + public DefaultModelMetadata( + [NotNull] IModelMetadataProvider provider, + [NotNull] ICompositeMetadataDetailsProvider detailsProvider, + [NotNull] DefaultMetadataDetailsCache cache) + : base(cache.Key) + { + _provider = provider; + _detailsProvider = detailsProvider; + _cache = cache; + } + + /// + /// Gets the set of attributes for the current instance. + /// + public IReadOnlyList Attributes + { + get + { + return _cache.Attributes; + } + } + + /// + /// Gets the for the current instance. + /// + /// + /// Accessing this property will populate the if necessary. + /// + public BindingMetadata BindingMetadata + { + get + { + if (_cache.BindingMetadata == null) + { + var context = new BindingMetadataProviderContext(Identity, _cache.Attributes); + _detailsProvider.GetBindingMetadata(context); + _cache.BindingMetadata = context.BindingMetadata; + } + + return _cache.BindingMetadata; + } + } + + /// + /// Gets the for the current instance. + /// + /// + /// Accessing this property will populate the if necessary. + /// + public DisplayMetadata DisplayMetadata + { + get + { + if (_cache.DisplayMetadata == null) + { + var context = new DisplayMetadataProviderContext(Identity, _cache.Attributes); + _detailsProvider.GetDisplayMetadata(context); + _cache.DisplayMetadata = context.DisplayMetadata; + } + + return _cache.DisplayMetadata; + } + } + + /// + /// Gets the for the current instance. + /// + /// + /// Accessing this property will populate the if necessary. + /// + public ValidationMetadata ValidationMetadata + { + get + { + if (_cache.ValidationMetadata == null) + { + var context = new ValidationMetadataProviderContext(Identity, _cache.Attributes); + _detailsProvider.GetValidationMetadata(context); + _cache.ValidationMetadata = context.ValidationMetadata; + } + + return _cache.ValidationMetadata; + } + } + + /// + public override IReadOnlyDictionary AdditionalValues + { + get + { + if (_additionalValues == null) + { + _additionalValues = new ReadOnlyDictionary(DisplayMetadata.AdditionalValues); + } + + return _additionalValues; + } + } + + /// + public override BindingSource BindingSource + { + get + { + return BindingMetadata.BindingSource; + } + } + + /// + public override string BinderModelName + { + get + { + return BindingMetadata.BinderModelName; + } + } + + /// + public override Type BinderType + { + get + { + return BindingMetadata.BinderType; + } + } + + /// + public override bool ConvertEmptyStringToNull + { + get + { + return DisplayMetadata.ConvertEmptyStringToNull; + } + } + + /// + public override string DataTypeName + { + get + { + return DisplayMetadata.DataTypeName; + } + } + + /// + public override string Description + { + get + { + return DisplayMetadata.Description; + } + } + + /// + public override string DisplayFormatString + { + get + { + return DisplayMetadata.DisplayFormatString; + } + } + + /// + public override string DisplayName + { + get + { + return DisplayMetadata.DisplayName; + } + } + + /// + public override string EditFormatString + { + get + { + return DisplayMetadata.EditFormatString; + } + } + + /// + public override bool HasNonDefaultEditFormat + { + get + { + return DisplayMetadata.HasNonDefaultEditFormat; + } + } + + /// + public override bool HideSurroundingHtml + { + get + { + return DisplayMetadata.HideSurroundingHtml; + } + } + + /// + public override bool HtmlEncode + { + get + { + return DisplayMetadata.HtmlEncode; + } + } + + /// + public override bool IsReadOnly + { + get + { + if (!_isReadOnly.HasValue) + { + if (BindingMetadata.IsReadOnly.HasValue) + { + _isReadOnly = BindingMetadata.IsReadOnly; + } + else + { + _isReadOnly = _cache.PropertySetter != null; + } + } + + return _isReadOnly.Value; + } + } + + /// + public override bool IsRequired + { + get + { + if (!_isRequired.HasValue) + { + if (BindingMetadata.IsRequired.HasValue) + { + _isRequired = BindingMetadata.IsRequired; + } + else + { + _isRequired = !ModelType.AllowsNullValue(); + } + } + + return _isRequired.Value; + } + } + + /// + public override string NullDisplayText + { + get + { + return DisplayMetadata.NullDisplayText; + } + } + + /// + public override int Order + { + get + { + return DisplayMetadata.Order; + } + } + + /// + public override ModelPropertyCollection Properties + { + get + { + if (_properties == null) + { + var properties = _provider.GetMetadataForProperties(ModelType); + properties = properties.OrderBy(p => p.Order); + _properties = new ModelPropertyCollection(properties); + } + + return _properties; + } + } + + /// + public override IPropertyBindingPredicateProvider PropertyBindingPredicateProvider + { + get + { + return BindingMetadata.PropertyBindingPredicateProvider; + } + } + + /// + public override bool ShowForDisplay + { + get + { + return DisplayMetadata.ShowForDisplay; + } + } + + /// + public override bool ShowForEdit + { + get + { + return DisplayMetadata.ShowForEdit; + } + } + + /// + public override string SimpleDisplayProperty + { + get + { + return DisplayMetadata.SimpleDisplayProperty; + } + } + + /// + public override string TemplateHint + { + get + { + return DisplayMetadata.TemplateHint; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadataProvider.cs new file mode 100644 index 0000000000..d1f9255dff --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadataProvider.cs @@ -0,0 +1,198 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Concurrent; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A default implementation of based on reflection. + /// + public class DefaultModelMetadataProvider : IModelMetadataProvider + { + private readonly TypeCache _typeCache = new TypeCache(); + private readonly ParameterCache _parameterCache = new ParameterCache(); + private readonly PropertiesCache _propertiesCache = new PropertiesCache(); + + /// + /// Creates a new . + /// + /// The . + public DefaultModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider) + { + DetailsProvider = detailsProvider; + } + + /// + /// Gets the . + /// + protected ICompositeMetadataDetailsProvider DetailsProvider { get; } + + /// + public virtual ModelMetadata GetMetadataForParameter( + [NotNull] ParameterInfo parameterInfo, + [NotNull] IEnumerable attributes) + { + var key = ModelMetadataIdentity.ForParameter(parameterInfo); + + DefaultMetadataDetailsCache entry; + if (!_parameterCache.TryGetValue(key, out entry)) + { + entry = CreateParameterCacheEntry(key, attributes); + entry = _parameterCache.GetOrAdd(key, entry); + } + + return CreateModelMetadata(entry); + } + + /// + public virtual IEnumerable GetMetadataForProperties([NotNull]Type modelType) + { + var key = ModelMetadataIdentity.ForType(modelType); + + var propertyEntries = _propertiesCache.GetOrAdd(key, CreatePropertyCacheEntries); + + var properties = new ModelMetadata[propertyEntries.Length]; + for (var i = 0; i < properties.Length; i++) + { + properties[i] = CreateModelMetadata(propertyEntries[i]); + } + + return properties; + } + + /// + public virtual ModelMetadata GetMetadataForType([NotNull] Type modelType) + { + var key = ModelMetadataIdentity.ForType(modelType); + + var entry = _typeCache.GetOrAdd(key, CreateTypeCacheEntry); + return CreateModelMetadata(entry); + } + + /// + /// Creates a new from a . + /// + /// The entry with cached data. + /// A new instance. + /// + /// will always create instances of + /// .Override this method to create a + /// of a different concrete type. + /// + protected virtual ModelMetadata CreateModelMetadata(DefaultMetadataDetailsCache entry) + { + return new DefaultModelMetadata(this, DetailsProvider, entry); + } + + /// + /// Creates the entries for the properties of a model + /// . + /// + /// + /// The identifying the model . + /// + /// A cache object for each property of the model . + /// + /// The results of this method will be cached and used to satisfy calls to + /// . Override this method to provide a different + /// set of property data. + /// + protected virtual DefaultMetadataDetailsCache[] CreatePropertyCacheEntries([NotNull] ModelMetadataIdentity key) + { + var propertyHelpers = PropertyHelper.GetProperties(key.ModelType); + + var propertyEntries = new DefaultMetadataDetailsCache[propertyHelpers.Length]; + for (var i = 0; i < propertyHelpers.Length; i++) + { + var propertyHelper = propertyHelpers[i]; + var propertyKey = ModelMetadataIdentity.ForProperty( + propertyHelper.Property.PropertyType, + propertyHelper.Name, + key.ModelType); + + var attributes = new List(ModelAttributes.GetAttributesForProperty( + key.ModelType, + propertyHelper.Property)); + + propertyEntries[i] = new DefaultMetadataDetailsCache(propertyKey, attributes); + if (propertyHelper.Property.CanRead && propertyHelper.Property.GetMethod?.IsPrivate == true) + { + propertyEntries[i].PropertyAccessor = PropertyHelper.MakeFastPropertyGetter(propertyHelper.Property); + } + + if (propertyHelper.Property.CanWrite && propertyHelper.Property.SetMethod?.IsPrivate == true) + { + propertyEntries[i].PropertySetter = PropertyHelper.MakeFastPropertySetter(propertyHelper.Property); + } + } + + return propertyEntries; + } + + /// + /// Creates the entry for a model . + /// + /// + /// The identifying the model . + /// + /// A cache object for the model . + /// + /// The results of this method will be cached and used to satisfy calls to + /// . Override this method to provide a different + /// set of attributes. + /// + protected virtual DefaultMetadataDetailsCache CreateTypeCacheEntry([NotNull] ModelMetadataIdentity key) + { + var attributes = new List(ModelAttributes.GetAttributesForType(key.ModelType)); + return new DefaultMetadataDetailsCache(key, attributes); + } + + /// + /// Creates the entry for a parameter. + /// + /// + /// The identifying the model parameter. + /// + /// + /// The set of model attributes applied to the parameter. + /// + /// A cache object for the model parameter. + /// + /// The results of this method will be cached and used to satisfy calls to + /// . + /// Override this method to provide a different set of attributes. + /// + protected virtual DefaultMetadataDetailsCache CreateParameterCacheEntry( + [NotNull] ModelMetadataIdentity key, + [NotNull] IEnumerable attributes) + { + var allAttributes = new List(); + + if (attributes != null) + { + allAttributes.AddRange(attributes); + } + + allAttributes.AddRange(ModelAttributes.GetAttributesForParameter(key.ParameterInfo)); + + return new DefaultMetadataDetailsCache(key, allAttributes); + } + + private class TypeCache : ConcurrentDictionary + { + } + + private class PropertiesCache : ConcurrentDictionary + { + } + + private class ParameterCache : ConcurrentDictionary + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DisplayMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DisplayMetadata.cs new file mode 100644 index 0000000000..0b8718cd18 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DisplayMetadata.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Display metadata details for a . + /// + public class DisplayMetadata + { + /// + /// Gets a set of additional values. See + /// + public IDictionary AdditionalValues { get; } = new Dictionary(); + + /// + /// Gets or sets a value indicating whether or not empty strings should be treated as null. + /// See + /// + public bool ConvertEmptyStringToNull { get; set; } = true; + + /// + /// Gets or sets the name of the data type. + /// See + /// + public string DataTypeName { get; set; } + + /// + /// Gets or sets a model description. + /// See + /// + public string Description { get; set; } + + /// + /// Gets or sets a display format string for the model. + /// See + /// + public string DisplayFormatString { get; set; } + + /// + /// Gets or sets a display name for the model. + /// See + /// + public string DisplayName { get; set; } + + /// + /// Gets or sets an edit format string for the model. + /// See + /// + /// + /// instances that set this property to a non-null, non-empty, + /// non-default value should also set to true. + /// + public string EditFormatString { get; set; } + + /// + /// Gets or sets a value indicating whether or not the model has a non-default edit format. + /// See + /// + public bool HasNonDefaultEditFormat { get; set; } + + /// + /// Gets or sets a value indicating if the surrounding HTML should be hidden. + /// See + /// + public bool HideSurroundingHtml { get; set; } + + /// + /// Gets or sets a value indicating if the model value should be HTML encoded. + /// See + /// + public bool HtmlEncode { get; set; } = true; + + /// + /// Gets or sets the text to display when the model value is null. + /// See + /// + public string NullDisplayText { get; set; } + + /// + /// Gets or sets the order. + /// See + /// + public int Order { get; set; } = 10000; + + /// + /// Gets or sets a value indicating whether or not to include in the model value in display. + /// See + /// + public bool ShowForDisplay { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not to include in the model value in an editor. + /// See + /// + public bool ShowForEdit { get; set; } = true; + + /// + /// Gets or sets a the property name of a model property to use for display. + /// See + /// + public string SimpleDisplayProperty { get; set; } + + /// + /// Gets or sets a hint for location of a display or editor template. + /// See + /// + public string TemplateHint { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DisplayMetadataProviderContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DisplayMetadataProviderContext.cs new file mode 100644 index 0000000000..2eae19df70 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DisplayMetadataProviderContext.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A context for and . + /// + public class DisplayMetadataProviderContext + { + /// + /// Creates a new . + /// + /// The for the . + /// The attributes for the . + public DisplayMetadataProviderContext( + [NotNull] ModelMetadataIdentity key, + [NotNull] IReadOnlyList attributes) + { + Key = key; + Attributes = attributes; + DisplayMetadata = new DisplayMetadata(); + } + + /// + /// Gets the attributes. + /// + public IReadOnlyList Attributes { get; } + + /// + /// Gets the . + /// + public ModelMetadataIdentity Key { get; } + + /// + /// Gets the . + /// + public DisplayMetadata DisplayMetadata { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/EmptyModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/EmptyModelMetadataProvider.cs deleted file mode 100644 index fe59cb6684..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/EmptyModelMetadataProvider.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Framework.Internal; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - /// - /// An that provides base instances and does not - /// set most properties. For example this provider does not use data annotations. - /// - /// - /// Provided for efficiency in scenarios that require minimal information. - /// - public class EmptyModelMetadataProvider : AssociatedMetadataProvider - { - /// - /// Ignores . - protected override ModelMetadata CreateMetadataPrototype(IEnumerable attributes, - Type containerType, - [NotNull] Type modelType, - string propertyName) - { - return new ModelMetadata( - this, - containerType, - modelType: modelType, - propertyName: propertyName); - } - - /// - /// - /// Copies very few values from the . Likely has not - /// been modified except to add entries. - /// - protected override ModelMetadata CreateMetadataFromPrototype([NotNull] ModelMetadata prototype) - { - var metadata = new ModelMetadata( - this, - prototype.ContainerType, - prototype.ModelType, - prototype.PropertyName); - foreach (var keyValuePair in prototype.AdditionalValues) - { - metadata.AdditionalValues.Add(keyValuePair); - } - - return metadata; - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IBindingMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IBindingMetadataProvider.cs new file mode 100644 index 0000000000..2d1396a8ec --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IBindingMetadataProvider.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Provides for a . + /// + public interface IBindingMetadataProvider : IMetadataDetailsProvider + { + /// + /// Gets the values for properties of . + /// + /// The . + void GetBindingMetadata([NotNull] BindingMetadataProviderContext context); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs new file mode 100644 index 0000000000..b6db126cd1 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A composite . + /// + public interface ICompositeMetadataDetailsProvider : + IBindingMetadataProvider, + IDisplayMetadataProvider, + IValidationMetadataProvider + { + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IDisplayMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IDisplayMetadataProvider.cs new file mode 100644 index 0000000000..dc651c0bf2 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IDisplayMetadataProvider.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Provides for a . + /// + public interface IDisplayMetadataProvider : IMetadataDetailsProvider + { + /// + /// Gets the values for properties of . + /// + /// The . + void GetDisplayMetadata([NotNull] DisplayMetadataProviderContext context); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IMetadataDetailsProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IMetadataDetailsProvider.cs new file mode 100644 index 0000000000..cf6f8831e4 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IMetadataDetailsProvider.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Marker interface for a provider of metadata details about model objects. Implementations should + /// implement one or more of , , + /// and . + /// + public interface IMetadataDetailsProvider + { + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IValidationMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IValidationMetadataProvider.cs new file mode 100644 index 0000000000..1cc2b8e5af --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IValidationMetadataProvider.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Provides for a . + /// + public interface IValidationMetadataProvider : IMetadataDetailsProvider + { + /// + /// Gets the values for properties of . + /// + /// The . + void GetValidationMetadata([NotNull] ValidationMetadataProviderContext context); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs deleted file mode 100644 index 6fd2b77f71..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNet.Mvc.ModelBinding.Internal; -using Microsoft.Framework.Internal; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - public class ModelMetadata - { - public static readonly int DefaultOrder = 10000; - - private readonly Type _containerType; - private readonly Type _modelType; - private readonly string _propertyName; - private EfficientTypePropertyKey _cacheKey; - - // Backing fields for virtual properties with default values. - private bool _convertEmptyStringToNull = true; - private bool _htmlEncode = true; - private bool _showForDisplay = true; - private bool _showForEdit = true; - - private int _order = DefaultOrder; - private bool _isRequired; - private ModelPropertyCollection _properties; - - public ModelMetadata([NotNull] IModelMetadataProvider provider, - Type containerType, - [NotNull] Type modelType, - string propertyName) - { - Provider = provider; - - _containerType = containerType; - _modelType = modelType; - _propertyName = propertyName; - _isRequired = !modelType.AllowsNullValue(); - } - - /// - /// Gets a collection of additional information about the model. - /// - public virtual IDictionary AdditionalValues { get; } = new Dictionary(); - - /// - /// Gets or sets the name of a model if specified explicitly using . - /// - public virtual string BinderModelName { get; set; } - - /// - /// Gets or sets the of an or an - /// of a model if specified explicitly using - /// . - /// - public virtual Type BinderType { get; set; } - - /// - /// Gets or sets a binder metadata for this model. - /// - public virtual IBinderMetadata BinderMetadata { get; set; } - - public Type ContainerType - { - get { return _containerType; } - } - - public virtual bool ConvertEmptyStringToNull - { - get { return _convertEmptyStringToNull; } - set { _convertEmptyStringToNull = value; } - } - - /// - /// Gets or sets the name of the Model's datatype. Overrides in some - /// display scenarios. - /// - /// null unless set manually or through additional metadata e.g. attributes. - public virtual string DataTypeName { get; set; } - - public virtual string Description { get; set; } - - /// - /// Gets or sets the composite format (see - /// http://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to display the Model. - /// - public virtual string DisplayFormatString { get; set; } - - public virtual string DisplayName { get; set; } - - /// - /// Gets or sets the composite format (see - /// http://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to edit the Model. - /// - /// - /// instances that set this property to a non-null, non-empty, - /// non-default value should also set to true. - /// - public virtual string EditFormatString { get; set; } - - /// - /// Gets or sets a value indicating whether has a non-null, non-empty - /// value different from the default for the datatype. - /// - public virtual bool HasNonDefaultEditFormat { get; set; } - - /// - /// Gets or sets a value indicating whether the value should be HTML-encoded. - /// - /// If true, value should be HTML-encoded. Default is true. - public virtual bool HtmlEncode - { - get { return _htmlEncode; } - set { _htmlEncode = value; } - } - - /// - /// Gets or sets a value indicating whether the "HiddenInput" display template should return - /// string.Empty (not the expression value) and whether the "HiddenInput" editor template should not - /// also return the expression value (together with the hidden <input> element). - /// - /// - /// If true, also causes the default display and editor templates to return HTML - /// lacking the usual per-property <div> wrapper around the associated property. Thus the default - /// display template effectively skips the property and the default - /// editor template returns only the hidden <input> element for the property. - /// - public virtual bool HideSurroundingHtml { get; set; } - - /// - /// Gets a value indicating whether the is a collection . - /// - /// - /// true if the is not and is assignable to - /// ; false otherwise. - /// - public virtual bool IsCollectionType - { - get { return TypeHelper.IsCollectionType(ModelType); } - } - - /// - /// Gets a value indicating whether the is a complex type. - /// - /// - /// false if the has a direct conversion to ; true - /// otherwise. - /// - public virtual bool IsComplexType - { - get { return !TypeHelper.HasStringConverter(ModelType); } - } - - public bool IsNullableValueType - { - get { return ModelType.IsNullableValueType(); } - } - - public virtual bool IsReadOnly { get; set; } - - public virtual bool IsRequired - { - get { return _isRequired; } - set { _isRequired = value; } - } - - /// - /// Gets or sets a value indicating where the current metadata should be ordered relative to other properties - /// in its containing type. - /// - /// - /// For example this property is used to order items in . - /// The default order is 10000. - /// - /// The order value of the current metadata. - public virtual int Order - { - get { return _order; } - set { _order = value; } - } - - public Type ModelType - { - get { return _modelType; } - } - - public virtual string NullDisplayText { get; set; } - - /// - /// Gets the collection of instances for the model's properties. - /// - public virtual ModelPropertyCollection Properties - { - get - { - if (_properties == null) - { - var properties = Provider.GetMetadataForProperties(ModelType); - _properties = new ModelPropertyCollection(properties.OrderBy(m => m.Order)); - } - - return _properties; - } - } - - /// - /// Gets or sets the , which can determine which properties - /// should be model bound. - /// - public virtual IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { get; set; } - - public string PropertyName - { - get { return _propertyName; } - } - - protected IModelMetadataProvider Provider { get; } - - /// - /// Gets or sets a value which is the name of the property used to display the model. - /// - public virtual string SimpleDisplayProperty { get; set; } - - /// - /// Gets or sets a value that indicates whether the property should be displayed in read-only views. - /// - public virtual bool ShowForDisplay - { - get { return _showForDisplay; } - set { _showForDisplay = value; } - } - - /// - /// Gets or sets a value that indicates whether the property should be displayed in editable views. - /// - public virtual bool ShowForEdit - { - get { return _showForEdit; } - set { _showForEdit = value; } - } - - /// - /// Gets or sets a hint that suggests what template to use for this model. Overrides - /// in that context but, unlike , this value is not used elsewhere. - /// - /// null unless set manually or through additional metadata e.g. attributes. - public virtual string TemplateHint { get; set; } - - internal EfficientTypePropertyKey CacheKey - { - get - { - if (_cacheKey == null) - { - _cacheKey = CreateCacheKey(ContainerType, ModelType, PropertyName); - } - - return _cacheKey; - } - set - { - _cacheKey = value; - } - } - - public string GetDisplayName() - { - return DisplayName ?? PropertyName ?? ModelType.Name; - } - - - private static EfficientTypePropertyKey CreateCacheKey(Type containerType, - Type modelType, - string propertyName) - { - // If metadata is for a property then containerType != null && propertyName != null - // If metadata is for a type then containerType == null && propertyName == null, - // so we have to use modelType for the cache key. - return new EfficientTypePropertyKey(containerType ?? modelType, propertyName); - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataIdentity.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataIdentity.cs new file mode 100644 index 0000000000..e595e0f2b3 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataIdentity.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Reflection; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A key type which identifies a . + /// + public struct ModelMetadataIdentity + { + /// + /// Creates a for the provided model . + /// + /// The model . + /// A . + public static ModelMetadataIdentity ForType([NotNull] Type modelType) + { + return new ModelMetadataIdentity() + { + ModelType = modelType, + }; + } + + /// + /// Creates a for the provided . + /// + /// The model parameter. + /// A . + public static ModelMetadataIdentity ForParameter([NotNull] ParameterInfo parameterInfo) + { + return new ModelMetadataIdentity() + { + ParameterInfo = parameterInfo, + Name = parameterInfo.Name, + ModelType = parameterInfo.ParameterType, + }; + } + + /// + /// Creates a for the provided property. + /// + /// The model type. + /// The name of the property. + /// The container type of the model property. + /// A . + public static ModelMetadataIdentity ForProperty( + [NotNull] Type modelType, + string name, + [NotNull] Type containerType) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name)); + } + + return new ModelMetadataIdentity() + { + ModelType = modelType, + Name = name, + ContainerType = containerType, + }; + } + + /// + /// Gets the defining the model property respresented by the current + /// instance, or null if the current instance does not represent a property. + /// + public Type ContainerType { get; private set; } + + /// + /// Gets the represented by the current instance, or null + /// if the current instance does not represent a parameter. + /// + public ParameterInfo ParameterInfo { get; private set; } + + /// + /// Gets the represented by the current instance. + /// + public Type ModelType { get; private set; } + + /// + /// Gets a value indicating the kind of metadata represented by the current instance. + /// + public ModelMetadataKind MetadataKind + { + get + { + if (ParameterInfo != null) + { + return ModelMetadataKind.Parameter; + } + else if (ContainerType != null && Name != null) + { + return ModelMetadataKind.Property; + } + else + { + return ModelMetadataKind.Type; + } + } + } + + /// + /// Gets the name of the current instance if it represents a parameter or property, or null if + /// the current instance represents a type. + /// + public string Name { get; private set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataKind.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataKind.cs new file mode 100644 index 0000000000..5cab259568 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataKind.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Enumeration for the kinds of + /// + public enum ModelMetadataKind + { + /// + /// Used for for a . + /// + Type, + + /// + /// Used for for a property. + /// + Property, + + /// + /// Used for for a parameter. + /// + Parameter, + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadata.cs new file mode 100644 index 0000000000..51acc5de88 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadata.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// Validation metadata details for a . + /// + public class ValidationMetadata + { + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadataProviderContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadataProviderContext.cs new file mode 100644 index 0000000000..ca59eb76a3 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadataProviderContext.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + /// + /// A context for an . + /// + public class ValidationMetadataProviderContext + { + /// + /// Creates a new . + /// + /// The for the . + /// The attributes for the . + public ValidationMetadataProviderContext( + [NotNull] ModelMetadataIdentity key, + [NotNull] IReadOnlyList attributes) + { + Key = key; + Attributes = attributes; + ValidationMetadata = new ValidationMetadata(); + } + + /// + /// Gets the attributes. + /// + public IReadOnlyList Attributes { get; } + + /// + /// Gets the . + /// + public ModelMetadataIdentity Key { get; } + + /// + /// Gets the . + /// + public ValidationMetadata ValidationMetadata { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelExplorer.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelExplorer.cs similarity index 100% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelExplorer.cs rename to src/Microsoft.AspNet.Mvc.ModelBinding/ModelExplorer.cs diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelExplorerExtensions.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelExplorerExtensions.cs similarity index 100% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelExplorerExtensions.cs rename to src/Microsoft.AspNet.Mvc.ModelBinding/ModelExplorerExtensions.cs diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadata.cs new file mode 100644 index 0000000000..f3c91e3b5b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadata.cs @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.ModelBinding.Metadata; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// A metadata representation of a model type, property or parameter. + /// + public abstract class ModelMetadata + { + /// + /// The default value of . + /// + public static readonly int DefaultOrder = 10000; + + /// + /// Creates a new . + /// + /// The . + protected ModelMetadata([NotNull] ModelMetadataIdentity identity) + { + Identity = identity; + } + + /// + /// Gets the container type of this metadata if it represents a property, otherwise null. + /// + public Type ContainerType { get { return Identity.ContainerType; } } + + /// + /// Gets a value indicating the kind of metadata element represented by the current instance. + /// + public ModelMetadataKind MetadataKind { get { return Identity.MetadataKind; } } + + /// + /// Gets the model type represented by the current instance. + /// + public Type ModelType { get { return Identity.ModelType; } } + + /// + /// Gets the property name represented by the current instance. + /// + public string PropertyName + { + get + { + return Identity.Name; + } + } + + /// + /// Gets the key for the current instance. + /// + protected ModelMetadataIdentity Identity { get; } + + /// + /// Gets a collection of additional information about the model. + /// + public abstract IReadOnlyDictionary AdditionalValues { get; } + + /// + /// Gets the collection of instances for the model's properties. + /// + public abstract ModelPropertyCollection Properties { get; } + + /// + /// Gets the name of a model if specified explicitly using . + /// + public abstract string BinderModelName { get; } + + /// + /// Gets the of an or an + /// of a model if specified explicitly using + /// . + /// + public abstract Type BinderType { get; } + + /// + /// Gets a binder metadata for this model. + /// + public abstract BindingSource BindingSource { get; } + + /// + /// Gets a value indicating whether or not to convert an empty string value to null when + /// representing a model as text. + /// + public abstract bool ConvertEmptyStringToNull { get; } + + /// + /// Gets the name of the 's datatype. Overrides in some + /// display scenarios. + /// + /// null unless set manually or through additional metadata e.g. attributes. + public abstract string DataTypeName { get; } + + /// + /// Gets the description of the model. + /// + public abstract string Description { get; } + + /// + /// Gets the composite format (see + /// http://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to display the . + /// + public abstract string DisplayFormatString { get; } + + /// + /// Gets the display name of the model. + /// + public abstract string DisplayName { get; } + + /// + /// Gets the composite format (see + /// http://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to edit the . + /// + public abstract string EditFormatString { get; } + + /// + /// Gets a value indicating whether has a non-null, non-empty + /// value different from the default for the datatype. + /// + public abstract bool HasNonDefaultEditFormat { get; } + + /// + /// Gets a value indicating whether the value should be HTML-encoded. + /// + /// If true, value should be HTML-encoded. Default is true. + public abstract bool HtmlEncode { get; } + + /// + /// Gets a value indicating whether the "HiddenInput" display template should return + /// string.Empty (not the expression value) and whether the "HiddenInput" editor template should not + /// also return the expression value (together with the hidden <input> element). + /// + /// + /// If true, also causes the default display and editor templates to return HTML + /// lacking the usual per-property <div> wrapper around the associated property. Thus the default + /// display template effectively skips the property and the default + /// editor template returns only the hidden <input> element for the property. + /// + public abstract bool HideSurroundingHtml { get; } + + /// + /// Gets a value indicating whether or not the model value is read-only. This is only applicable when + /// the current instance represents a property. + /// + public abstract bool IsReadOnly { get; } + + /// + /// Gets a value indicating whether or not the model value is required. This is only applicable when + /// the current instance represents a property. + /// + public abstract bool IsRequired { get; } + + /// + /// Gets a value indicating where the current metadata should be ordered relative to other properties + /// in its containing type. + /// + /// + /// For example this property is used to order items in . + /// The default order is 10000. + /// + /// The order value of the current metadata. + public abstract int Order { get; } + + /// + /// Gets the text to display when the model is null. + /// + public abstract string NullDisplayText { get; } + + /// + /// Gets the , which can determine which properties + /// should be model bound. + /// + public abstract IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { get; } + + /// + /// Gets a value that indicates whether the property should be displayed in read-only views. + /// + public abstract bool ShowForDisplay { get; } + + /// + /// Gets a value that indicates whether the property should be displayed in editable views. + /// + public abstract bool ShowForEdit { get; } + + /// + /// Gets a value which is the name of the property used to display the model. + /// + public abstract string SimpleDisplayProperty { get; } + + /// + /// Gets a string used by the templating system to discover display-templates and editor-templates. + /// + public abstract string TemplateHint { get; } + + /// + /// Gets a value indicating whether is a simple type. + /// + /// + /// A simple type is defined as a which has a + /// that can convert from . + /// + public bool IsComplexType + { + get { return !TypeHelper.HasStringConverter(ModelType); } + } + + /// + /// Gets a value indicating whether or not is a . + /// + public bool IsNullableValueType + { + get { return ModelType.IsNullableValueType(); } + } + + /// + /// Gets a value indicating whether or not is a collection type. + /// + /// + /// A collection type is defined as a which is assignable to + /// , and is not a . + /// + public bool IsCollectionType + { + get { return TypeHelper.IsCollectionType(ModelType); } + } + + /// + /// Gets a display name for the model. + /// + /// + /// will return the first of the following expressions which has a + /// non-null value: DisplayName, PropertyName, ModelType.Name. + /// + /// The display name. + public string GetDisplayName() + { + return DisplayName ?? PropertyName ?? ModelType.Name; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataProviderExtensions.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadataProviderExtensions.cs similarity index 100% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataProviderExtensions.cs rename to src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadataProviderExtensions.cs diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelPropertyCollection.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelPropertyCollection.cs similarity index 100% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelPropertyCollection.cs rename to src/Microsoft.AspNet.Mvc.ModelBinding/ModelPropertyCollection.cs diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultObjectValidator.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultObjectValidator.cs index 99eb7a253d..40cdae9bfc 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultObjectValidator.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultObjectValidator.cs @@ -55,9 +55,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding RuntimeHelpers.EnsureSufficientExecutionStack(); var modelState = validationContext.ModelValidationContext.ModelState; - var bindingSourceMetadata = modelExplorer.Metadata.BinderMetadata as IBindingSourceMetadata; - var bindingSource = bindingSourceMetadata?.BindingSource; + var bindingSource = modelExplorer.Metadata.BindingSource; if (bindingSource != null && !bindingSource.IsFromRequest) { // Short circuit if the metadata represents something that was not bound using request data. diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/project.json b/src/Microsoft.AspNet.Mvc.ModelBinding/project.json index 3341fbd7e5..6f00c3dfde 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/project.json +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/project.json @@ -27,6 +27,7 @@ "System.Collections.Concurrent": "4.0.10-beta-*", "System.ComponentModel.Annotations": "4.0.10-beta-*", "System.ComponentModel.TypeConverter": "4.0.0-beta-*", + "System.ObjectModel": "4.0.10-beta-*", "System.Reflection.TypeExtensions": "4.0.0-beta-*", "System.Runtime.Serialization.Primitives": "4.0.0-beta-*" } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs index c77a9994e7..1de7079f47 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs @@ -91,7 +91,8 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim foreach (var parameter in candidate.Action.Parameters) { // We only consider parameters that are marked as bound from the URL. - var source = BindingSource.GetBindingSource(parameter.BinderMetadata); + var bindingSourceMetadata = parameter?.BinderMetadata as IBindingSourceMetadata; + var source = bindingSourceMetadata?.BindingSource; if (source == null) { continue; diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index 749dcf7340..2049547f8e 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -4,6 +4,7 @@ using System; using System.Xml.Linq; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.Razor; using Microsoft.Framework.OptionsModel; using Microsoft.Net.Http.Headers; @@ -59,6 +60,10 @@ namespace Microsoft.AspNet.Mvc options.ValueProviderFactories.Add(new QueryStringValueProviderFactory()); options.ValueProviderFactories.Add(new FormValueProviderFactory()); + // Set up metadata providers + options.ModelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider()); + options.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataDetailsProvider()); + // Set up validators options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider()); options.ModelValidatorProviders.Add(new DataMemberModelValidatorProvider()); diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 5a62a37070..40b3e2dc24 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -9,6 +9,7 @@ using Microsoft.AspNet.Mvc.Description; using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.OptionDescriptors; using Microsoft.AspNet.Mvc.Razor; using Microsoft.AspNet.Mvc.Razor.Directives; @@ -81,10 +82,16 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient(); yield return describe.Transient(); + // Dataflow - ModelBinding, Validation and Formatting - // The DataAnnotationsModelMetadataProvider does significant caching of reflection/attributes - // and thus needs to be singleton. - yield return describe.Singleton(); + // + // The DefaultModelMetadataProvider does significant caching and should be a singleton. + yield return describe.Singleton(); + yield return describe.Transient(services => + { + var options = services.GetRequiredService>().Options; + return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders); + }); yield return describe.Transient(); yield return describe.Scoped(); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs index 386b2537c3..248574f964 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs @@ -29,8 +29,10 @@ namespace Microsoft.AspNet.Mvc .Returns(Task.FromResult(new Person())) .Verifiable(); - var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object); - bindingContext.ModelMetadata.BinderMetadata = new FromBodyAttribute(); + var provider = new TestModelMetadataProvider(); + provider.ForType().BindingDetails(d => d.BindingSource = BindingSource.Body); + + var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider); var binder = GetBodyBinder(mockInputFormatter.Object); @@ -47,8 +49,10 @@ namespace Microsoft.AspNet.Mvc public async Task BindModel_NoInputFormatterFound_SetsModelStateError() { // Arrange - var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null); - bindingContext.ModelMetadata.BinderMetadata = new FromBodyAttribute(); + var provider = new TestModelMetadataProvider(); + provider.ForType().BindingDetails(d => d.BindingSource = BindingSource.Body); + + var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider); var binder = bindingContext.OperationBindingContext.ModelBinder; @@ -68,11 +72,10 @@ namespace Microsoft.AspNet.Mvc public async Task BindModel_IsGreedy() { // Arrange - var metadata = new Mock(); - metadata.SetupGet(m => m.BindingSource).Returns(BindingSource.Body); + var provider = new TestModelMetadataProvider(); + provider.ForType().BindingDetails(d => d.BindingSource = BindingSource.Body); - var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null); - bindingContext.ModelMetadata.BinderMetadata = metadata.Object; + var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider); var binder = bindingContext.OperationBindingContext.ModelBinder; @@ -88,11 +91,10 @@ namespace Microsoft.AspNet.Mvc public async Task BindModel_IsGreedy_IgnoresWrongSource() { // Arrange - var metadata = new Mock(); - metadata.SetupGet(m => m.BindingSource).Returns(BindingSource.Header); + var provider = new TestModelMetadataProvider(); + provider.ForType().BindingDetails(d => d.BindingSource = BindingSource.Header); - var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null); - bindingContext.ModelMetadata.BinderMetadata = metadata.Object; + var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider); var binder = bindingContext.OperationBindingContext.ModelBinder; @@ -107,11 +109,10 @@ namespace Microsoft.AspNet.Mvc public async Task BindModel_IsGreedy_IgnoresMetadataWithNoSource() { // Arrange - var metadata = new Mock(); - metadata.SetupGet(m => m.BindingSource).Returns((BindingSource)null); + var provider = new TestModelMetadataProvider(); + provider.ForType().BindingDetails(d => d.BindingSource = null); - var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null); - bindingContext.ModelMetadata.BinderMetadata = metadata.Object; + var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider); var binder = bindingContext.OperationBindingContext.ModelBinder; @@ -129,8 +130,15 @@ namespace Microsoft.AspNet.Mvc var httpContext = new DefaultHttpContext(); httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("Bad data!")); httpContext.Request.ContentType = "text/xyz"; - var bindingContext = GetBindingContext(httpContext, typeof(Person), inputFormatter: new XyzFormatter()); - bindingContext.ModelMetadata.BinderMetadata = new FromBodyAttribute(); + + var provider = new TestModelMetadataProvider(); + provider.ForType().BindingDetails(d => d.BindingSource = BindingSource.Body); + + var bindingContext = GetBindingContext( + typeof(Person), + inputFormatter: new XyzFormatter(), + httpContext: httpContext, + metadataProvider: provider); var binder = bindingContext.OperationBindingContext.ModelBinder; @@ -148,17 +156,22 @@ namespace Microsoft.AspNet.Mvc Assert.Equal("Your input is bad!", errorMessage); } - private static ModelBindingContext GetBindingContext(Type modelType, IInputFormatter inputFormatter) - { - return GetBindingContext(new DefaultHttpContext(), modelType, inputFormatter); - } - private static ModelBindingContext GetBindingContext( - HttpContext httpContext, - Type modelType, - IInputFormatter inputFormatter) + Type modelType, + IInputFormatter inputFormatter = null, + HttpContext httpContext = null, + IModelMetadataProvider metadataProvider = null) { - var metadataProvider = new EmptyModelMetadataProvider(); + if (httpContext == null) + { + httpContext = new DefaultHttpContext(); + } + + if (metadataProvider == null) + { + metadataProvider = new EmptyModelMetadataProvider(); + } + var operationBindingContext = new OperationBindingContext { ModelBinder = GetBodyBinder(httpContext, inputFormatter), diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index 0e442cdb1e..dd81d2e303 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -14,7 +14,6 @@ using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; using Microsoft.AspNet.WebUtilities; using Microsoft.AspNet.Http.Core; -using Microsoft.Framework.DependencyInjection; #if ASPNET50 using Moq; #endif @@ -41,7 +40,7 @@ namespace Microsoft.AspNet.Mvc.Test public void SettingViewData_AlsoUpdatesViewBag() { // Arrange - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var controller = new TestableController(); var originalViewData = controller.ViewData = new ViewDataDictionary(metadataProvider); var replacementViewData = new ViewDataDictionary(metadataProvider); @@ -920,7 +919,7 @@ namespace Microsoft.AspNet.Mvc.Test public async Task TryUpdateModel_FallsBackOnEmptyPrefix_IfNotSpecified() { // Arrange - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var valueProvider = Mock.Of(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) @@ -953,7 +952,7 @@ namespace Microsoft.AspNet.Mvc.Test // Arrange var modelName = "mymodel"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var valueProvider = Mock.Of(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) @@ -1199,7 +1198,7 @@ namespace Microsoft.AspNet.Mvc.Test // Arrange var modelName = "mymodel"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var valueProvider = Mock.Of(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) @@ -1232,7 +1231,7 @@ namespace Microsoft.AspNet.Mvc.Test // Arrange var modelName = "mymodel"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var valueProvider = Mock.Of(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) @@ -1456,7 +1455,7 @@ namespace Microsoft.AspNet.Mvc.Test private static Controller GetController(IModelBinder binder, IValueProvider provider) { - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var httpContext = new DefaultHttpContext(); var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs index b29416d60e..4d751c859e 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs @@ -918,7 +918,7 @@ namespace Microsoft.AspNet.Mvc.Description constraintResolver.Setup(c => c.ResolveConstraint("int")) .Returns(new IntRouteConstraint()); - var modelMetadataProvider = new DataAnnotationsModelMetadataProvider(); + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var provider = new DefaultApiDescriptionProvider( formattersProvider.Object, diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs index 15144a3243..2d27a2dfa5 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Mvc.ModelBinding; @@ -51,7 +52,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public void GetModelBindingContext_ReturnsOnlyIncludedProperties_UsingBindAttributeInclude() { // Arrange - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadata = metadataProvider.GetMetadataForType( typeof(TypeWithIncludedPropertiesUsingBindAttribute)); @@ -72,11 +73,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Arrange var type = typeof(ControllerActionArgumentBinderTests); var methodInfo = type.GetMethod("ParameterWithNoBindAttribute"); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "parameter").Single(); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadata = metadataProvider.GetMetadataForParameter( - methodInfo: methodInfo, - parameterName: "parameter"); + parameterInfo, + attributes: null); // Act var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( @@ -101,11 +103,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Arrange var type = typeof(ControllerActionArgumentBinderTests); var methodInfo = type.GetMethod(actionMethodName); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "parameter").Single(); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadata = metadataProvider.GetMetadataForParameter( - methodInfo: methodInfo, - parameterName: "parameter"); + parameterInfo, + attributes: null); // Act var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( @@ -130,11 +133,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Arrange var type = typeof(ControllerActionArgumentBinderTests); var methodInfo = type.GetMethod(actionMethodName); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "parameter1").Single(); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadata = metadataProvider.GetMetadataForParameter( - methodInfo: methodInfo, - parameterName: "parameter1"); + parameterInfo, + attributes: null); // Act var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( @@ -180,7 +184,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test ModelBinder = binder.Object, }; - var modelMetadataProvider = new DataAnnotationsModelMetadataProvider(); + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var inputFormattersProvider = new Mock(); inputFormattersProvider .SetupGet(o => o.InputFormatters) @@ -235,7 +239,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test .SetupGet(o => o.InputFormatters) .Returns(new List()); - var modelMetadataProvider = new DataAnnotationsModelMetadataProvider(); + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var invoker = new DefaultControllerActionArgumentBinder( modelMetadataProvider, new DefaultObjectValidator(Mock.Of(), modelMetadataProvider), @@ -345,7 +349,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test .Setup(o => o.Validate(It.IsAny())) .Verifiable(); var invoker = new DefaultControllerActionArgumentBinder( - new DataAnnotationsModelMetadataProvider(), + TestModelMetadataProvider.CreateDefaultProvider(), mockValidatorProvider.Object, new MockMvcOptionsAccessor()); @@ -394,7 +398,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test mockValidatorProvider.Setup(o => o.Validate(It.IsAny())) .Verifiable(); var invoker = new DefaultControllerActionArgumentBinder( - new DataAnnotationsModelMetadataProvider(), + TestModelMetadataProvider.CreateDefaultProvider(), mockValidatorProvider.Object, new MockMvcOptionsAccessor()); @@ -449,7 +453,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test var mockValidatorProvider = new Mock(MockBehavior.Strict); mockValidatorProvider.Setup(o => o.Validate(It.IsAny())); var invoker = new DefaultControllerActionArgumentBinder( - new DataAnnotationsModelMetadataProvider(), + TestModelMetadataProvider.CreateDefaultProvider(), mockValidatorProvider.Object, options); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs index b66561ab50..73eaee6117 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs @@ -23,10 +23,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task TryUpdateModel_ReturnsFalse_IfBinderReturnsFalse() { // Arrange - var metadataProvider = new Mock(); - metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny())) - .Returns(new ModelMetadata(metadataProvider.Object, null, typeof(MyModel), null)) - .Verifiable(); + var metadataProvider = new EmptyModelMetadataProvider(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) @@ -39,16 +36,15 @@ namespace Microsoft.AspNet.Mvc.Core.Test null, Mock.Of(), new ModelStateDictionary(), - metadataProvider.Object, + metadataProvider, GetCompositeBinder(binder.Object), Mock.Of(), - new DefaultObjectValidator(Mock.Of(), metadataProvider.Object), + new DefaultObjectValidator(Mock.Of(), metadataProvider), Mock.Of()); // Assert Assert.False(result); Assert.Null(model.MyProperty); - metadataProvider.Verify(); } [Fact] @@ -72,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { "", null } }; var valueProvider = new TestValueProvider(values); - var modelMetadataProvider = new DataAnnotationsModelMetadataProvider(); + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); // Act var result = await ModelBindingHelper.TryUpdateModelAsync( @@ -112,7 +108,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { "MyProperty", "MyPropertyValue" } }; var valueProvider = new TestValueProvider(values); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); // Act var result = await ModelBindingHelper.TryUpdateModelAsync( @@ -135,10 +131,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task TryUpdateModel_UsingIncludePredicateOverload_ReturnsFalse_IfBinderReturnsFalse() { // Arrange - var metadataProvider = new Mock(); - metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny())) - .Returns(new ModelMetadata(metadataProvider.Object, null, typeof(MyModel), null)) - .Verifiable(); + var metadataProvider = new EmptyModelMetadataProvider(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) @@ -152,7 +145,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test null, Mock.Of(), new ModelStateDictionary(), - metadataProvider.Object, + metadataProvider, GetCompositeBinder(binder.Object), Mock.Of(), Mock.Of(), @@ -164,7 +157,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test Assert.Null(model.MyProperty); Assert.Null(model.IncludedProperty); Assert.Null(model.ExcludedProperty); - metadataProvider.Verify(); } [Fact] @@ -200,7 +192,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase); var valueProvider = new TestValueProvider(values); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); // Act var result = await ModelBindingHelper.TryUpdateModelAsync( @@ -226,10 +218,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task TryUpdateModel_UsingIncludeExpressionOverload_ReturnsFalse_IfBinderReturnsFalse() { // Arrange - var metadataProvider = new Mock(); - metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny())) - .Returns(new ModelMetadata(metadataProvider.Object, null, typeof(MyModel), null)) - .Verifiable(); + var metadataProvider = new EmptyModelMetadataProvider(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) @@ -242,7 +231,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test null, Mock.Of(), new ModelStateDictionary(), - metadataProvider.Object, + metadataProvider, GetCompositeBinder(binder.Object), Mock.Of(), Mock.Of(), @@ -254,7 +243,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test Assert.Null(model.MyProperty); Assert.Null(model.IncludedProperty); Assert.Null(model.ExcludedProperty); - metadataProvider.Verify(); } [Fact] @@ -286,7 +274,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }; var valueProvider = new TestValueProvider(values); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); // Act var result = await ModelBindingHelper.TryUpdateModelAsync( @@ -294,7 +282,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test "", Mock.Of(), modelStateDictionary, - new DataAnnotationsModelMetadataProvider(), + TestModelMetadataProvider.CreateDefaultProvider(), GetCompositeBinder(binders), valueProvider, new DefaultObjectValidator(Mock.Of(), metadataProvider), @@ -338,7 +326,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }; var valueProvider = new TestValueProvider(values); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); // Act var result = await ModelBindingHelper.TryUpdateModelAsync( @@ -483,11 +471,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task TryUpdateModelNonGeneric_PredicateOverload_ReturnsFalse_IfBinderReturnsFalse() { // Arrange - var metadataProvider = new Mock(); - metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny())) - .Returns(new ModelMetadata(metadataProvider.Object, null, typeof(MyModel), null)) - .Verifiable(); - + var metadataProvider = new EmptyModelMetadataProvider(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) .Returns(Task.FromResult(null)); @@ -501,7 +485,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test prefix: null, httpContext: Mock.Of(), modelState: new ModelStateDictionary(), - metadataProvider: metadataProvider.Object, + metadataProvider: metadataProvider, modelBinder: GetCompositeBinder(binder.Object), valueProvider: Mock.Of(), objectModelValidator: Mock.Of(), @@ -513,7 +497,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test Assert.Null(model.MyProperty); Assert.Null(model.IncludedProperty); Assert.Null(model.ExcludedProperty); - metadataProvider.Verify(); } [Fact] @@ -550,7 +533,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase); var valueProvider = new TestValueProvider(values); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); // Act var result = await ModelBindingHelper.TryUpdateModelAsync( @@ -579,10 +562,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task TryUpdateModelNonGeneric_ModelTypeOverload_ReturnsFalse_IfBinderReturnsFalse() { // Arrange - var metadataProvider = new Mock(); - metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny())) - .Returns(new ModelMetadata(metadataProvider.Object, null, typeof(MyModel), null)) - .Verifiable(); + var metadataProvider = new EmptyModelMetadataProvider(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) @@ -596,7 +576,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test prefix: null, httpContext: Mock.Of(), modelState: new ModelStateDictionary(), - metadataProvider: metadataProvider.Object, + metadataProvider: metadataProvider, modelBinder: GetCompositeBinder(binder.Object), valueProvider: Mock.Of(), objectModelValidator: Mock.Of(), @@ -605,7 +585,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Assert Assert.False(result); Assert.Null(model.MyProperty); - metadataProvider.Verify(); } [Fact] @@ -628,7 +607,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { "MyProperty", "MyPropertyValue" } }; var valueProvider = new TestValueProvider(values); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); // Act var result = await ModelBindingHelper.TryUpdateModelAsync( @@ -637,7 +616,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test "", Mock.Of(), modelStateDictionary, - new DataAnnotationsModelMetadataProvider(), + TestModelMetadataProvider.CreateDefaultProvider(), GetCompositeBinder(binders), valueProvider, new DefaultObjectValidator( @@ -654,15 +633,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task TryUpdataModel_ModelTypeDifferentFromModel_Throws() { // Arrange - var metadataProvider = new Mock(); - metadataProvider - .Setup(m => m.GetMetadataForType(It.IsAny())) - .Returns(new ModelMetadata( - metadataProvider.Object, - containerType: null, - modelType: typeof(MyModel), - propertyName: null)) - .Verifiable(); + var metadataProvider = new EmptyModelMetadataProvider(); var binder = new Mock(); binder.Setup(b => b.BindModelAsync(It.IsAny())) @@ -679,12 +650,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test null, Mock.Of(), new ModelStateDictionary(), - metadataProvider.Object, + metadataProvider, GetCompositeBinder(binder.Object), Mock.Of(), new DefaultObjectValidator( Mock.Of(), - metadataProvider.Object), + metadataProvider), Mock.Of(), includePredicate)); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTest.cs index 3732739118..e082145386 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTest.cs @@ -70,9 +70,13 @@ namespace Microsoft.AspNet.Mvc.Core public void ObjectTemplateDisplaysNullDisplayTextWhenObjectIsNull() { // Arrange - var html = DefaultTemplatesUtilities.GetHtmlHelper(); + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.NullDisplayText = "(null value)"; + }); - html.ViewData.ModelMetadata.NullDisplayText = "(null value)"; + var html = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); // Act var result = DefaultDisplayTemplates.ObjectTemplate(html); @@ -92,10 +96,14 @@ namespace Microsoft.AspNet.Mvc.Core var model = new DefaultTemplatesUtilities.ObjectTemplateModel(); model.Property1 = simpleDisplayText; - var html = DefaultTemplatesUtilities.GetHtmlHelper(model); + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.HtmlEncode = htmlEncode; + dd.SimpleDisplayProperty = "Property1"; + }); - html.ViewData.ModelMetadata.HtmlEncode = htmlEncode; - html.ViewData.ModelMetadata.SimpleDisplayProperty = "Property1"; + var html = DefaultTemplatesUtilities.GetHtmlHelper(model, provider); html.ViewData.TemplateInfo.AddVisited("foo"); html.ViewData.TemplateInfo.AddVisited("bar"); @@ -141,10 +149,14 @@ namespace Microsoft.AspNet.Mvc.Core " SimpleDisplayText = (null)" + Environment.NewLine; var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null }; - var html = DefaultTemplatesUtilities.GetHtmlHelper(model); - var metadata = html.ViewData.ModelMetadata.Properties["Property1"]; - metadata.HideSurroundingHtml = true; + var provider = new TestModelMetadataProvider(); + provider.ForProperty("Property1").DisplayDetails(dd => + { + dd.HideSurroundingHtml = true; + }); + + var html = DefaultTemplatesUtilities.GetHtmlHelper(model, provider); // Act var result = DefaultDisplayTemplates.ObjectTemplate(html); @@ -219,9 +231,15 @@ namespace Microsoft.AspNet.Mvc.Core { // Arrange var model = "Model string"; - var html = DefaultTemplatesUtilities.GetHtmlHelper(model); + + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.HideSurroundingHtml = true; + }); + + var html = DefaultTemplatesUtilities.GetHtmlHelper(model, provider: provider); var viewData = html.ViewData; - viewData.ModelMetadata.HideSurroundingHtml = true; var templateInfo = viewData.TemplateInfo; templateInfo.HtmlFieldPrefix = "FieldPrefix"; diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTest.cs index 2e8b826023..5599d053ec 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTest.cs @@ -103,10 +103,14 @@ namespace Microsoft.AspNet.Mvc.Core public void ObjectTemplateDisplaysNullDisplayTextWithNullModelAndTemplateDepthGreaterThanOne() { // Arrange - var html = DefaultTemplatesUtilities.GetHtmlHelper(); + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.NullDisplayText = "Null Display Text"; + dd.SimpleDisplayProperty = "Property1"; + }); - html.ViewData.ModelMetadata.NullDisplayText = "Null Display Text"; - html.ViewData.ModelMetadata.SimpleDisplayProperty = "Property1"; + var html = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); html.ViewData.TemplateInfo.AddVisited("foo"); html.ViewData.TemplateInfo.AddVisited("bar"); @@ -131,11 +135,15 @@ namespace Microsoft.AspNet.Mvc.Core Property1 = simpleDisplayText, }; - var html = DefaultTemplatesUtilities.GetHtmlHelper(model); + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.HtmlEncode = htmlEncode; + dd.NullDisplayText = "Null Display Text"; + dd.SimpleDisplayProperty = "Property1"; + }); - html.ViewData.ModelMetadata.HtmlEncode = htmlEncode; - html.ViewData.ModelMetadata.NullDisplayText = "Null Display Text"; - html.ViewData.ModelMetadata.SimpleDisplayProperty = "Property1"; + var html = DefaultTemplatesUtilities.GetHtmlHelper(model, provider: provider); html.ViewData.TemplateInfo.AddVisited("foo"); html.ViewData.TemplateInfo.AddVisited("bar"); @@ -190,11 +198,14 @@ Environment.NewLine; "" + Environment.NewLine; - var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null }; - var html = DefaultTemplatesUtilities.GetHtmlHelper(model); + var provider = new TestModelMetadataProvider(); + provider.ForProperty("Property1").DisplayDetails(dd => + { + dd.HideSurroundingHtml = true; + }); - var metadata = html.ViewData.ModelMetadata.Properties["Property1"]; - metadata.HideSurroundingHtml = true; + var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null }; + var html = DefaultTemplatesUtilities.GetHtmlHelper(model, provider: provider); // Act var result = DefaultEditorTemplates.ObjectTemplate(html); @@ -276,11 +287,16 @@ Environment.NewLine; var expected = ""; var model = "Model string"; - var html = DefaultTemplatesUtilities.GetHtmlHelper(model); - var viewData = html.ViewData; - viewData.ModelMetadata.HideSurroundingHtml = true; - var templateInfo = viewData.TemplateInfo; + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.HideSurroundingHtml = true; + }); + + var html = DefaultTemplatesUtilities.GetHtmlHelper(model, provider: provider); + + var templateInfo = html.ViewData.TemplateInfo; templateInfo.HtmlFieldPrefix = "FieldPrefix"; templateInfo.FormattedModelValue = "Formatted string"; @@ -384,15 +400,21 @@ Environment.NewLine; viewEngine .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + + var provider = new TestModelMetadataProvider(); + provider.ForProperty("Property1").DisplayDetails(dd => + { + dd.DataTypeName = templateName; + }); + var helper = DefaultTemplatesUtilities.GetHtmlHelper( model, + null, viewEngine.Object, + provider, innerHelper => new StubbyHtmlHelper(innerHelper)); helper.ViewData["Property1"] = "True"; - var metadata = helper.ViewData.ModelMetadata.Properties["Property1"]; - metadata.DataTypeName = templateName; - // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. helper.ViewData.TemplateInfo.FormattedModelValue = "Formatted string"; @@ -417,14 +439,20 @@ Environment.NewLine; viewEngine .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + + var provider = new TestModelMetadataProvider(); + provider.ForProperty("Property1").DisplayDetails(dd => + { + dd.DataTypeName = templateName; + }); + var helper = DefaultTemplatesUtilities.GetHtmlHelper( model, + null, viewEngine.Object, + provider, innerHelper => new StubbyHtmlHelper(innerHelper)); - var metadata = helper.ViewData.ModelMetadata.Properties["Property1"]; - metadata.DataTypeName = templateName; - // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. helper.ViewData.TemplateInfo.FormattedModelValue = "Formatted string"; @@ -449,15 +477,21 @@ Environment.NewLine; viewEngine .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + + var provider = new TestModelMetadataProvider(); + provider.ForProperty("Property1").DisplayDetails(dd => + { + dd.TemplateHint = templateName; + }); + var helper = DefaultTemplatesUtilities.GetHtmlHelper( model, + null, viewEngine.Object, + provider, innerHelper => new StubbyHtmlHelper(innerHelper)); helper.ViewData["Property1"] = "True"; - var metadata = helper.ViewData.ModelMetadata.Properties["Property1"]; - metadata.TemplateHint = templateName; - // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. helper.ViewData.TemplateInfo.FormattedModelValue = "Formatted string"; @@ -482,14 +516,20 @@ Environment.NewLine; viewEngine .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + + var provider = new TestModelMetadataProvider(); + provider.ForProperty("Property1").DisplayDetails(dd => + { + dd.TemplateHint = templateName; + }); + var helper = DefaultTemplatesUtilities.GetHtmlHelper( model, + null, viewEngine.Object, + provider, innerHelper => new StubbyHtmlHelper(innerHelper)); - var metadata = helper.ViewData.ModelMetadata.Properties["Property1"]; - metadata.TemplateHint = templateName; - // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. helper.ViewData.TemplateInfo.FormattedModelValue = "Formatted string"; @@ -551,9 +591,19 @@ Environment.NewLine; viewEngine .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); - var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); - helper.ViewData.ModelMetadata.DataTypeName = dataTypeName; - helper.ViewData.ModelMetadata.EditFormatString = editFormatString; // What [DataType] does for given type. + + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.DataTypeName = dataTypeName; + dd.EditFormatString = editFormatString; // What [DataType] does for given type. + }); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper( + model, + null, + viewEngine.Object, + provider); helper.ViewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix"; // Act @@ -590,10 +640,20 @@ Environment.NewLine; viewEngine .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); - var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.DataTypeName = dataTypeName; + dd.EditFormatString = editFormatString; // What [DataType] does for given type. + }); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper( + model, + null, + viewEngine.Object, + provider); helper.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339; - helper.ViewData.ModelMetadata.DataTypeName = dataTypeName; - helper.ViewData.ModelMetadata.EditFormatString = editFormatString; // What [DataType] does for given type. helper.ViewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix"; // Act @@ -631,11 +691,23 @@ Environment.NewLine; viewEngine .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); - var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + + + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.DataTypeName = dataTypeName; + dd.EditFormatString = "Formatted as {0:O}"; // What [DataType] does for given type. + dd.HasNonDefaultEditFormat = true; + }); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper( + model, + null, + viewEngine.Object, + provider); + helper.Html5DateRenderingMode = renderingMode; // Ignored due to HasNonDefaultEditFormat. - helper.ViewData.ModelMetadata.DataTypeName = dataTypeName; - helper.ViewData.ModelMetadata.EditFormatString = "Formatted as {0:O}"; - helper.ViewData.ModelMetadata.HasNonDefaultEditFormat = true; helper.ViewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix"; // Act diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs index 8ad659601a..433bf546ed 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs @@ -58,12 +58,12 @@ namespace Microsoft.AspNet.Mvc.Rendering model: null, urlHelper: urlHelper, viewEngine: CreateViewEngine(), - provider: CreateModelMetadataProvider()); + provider: TestModelMetadataProvider.CreateDefaultProvider()); } public static HtmlHelper GetHtmlHelper(IHtmlGenerator htmlGenerator) { - var metadataProvider = CreateModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); return GetHtmlHelper( new ViewDataDictionary(metadataProvider), CreateUrlHelper(), @@ -79,7 +79,7 @@ namespace Microsoft.AspNet.Mvc.Rendering viewData, CreateUrlHelper(), CreateViewEngine(), - CreateModelMetadataProvider(), + TestModelMetadataProvider.CreateDefaultProvider(), innerHelperWrapper: null, htmlGenerator: null); } @@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.Rendering public static HtmlHelper GetHtmlHelper(TModel model, ICompositeViewEngine viewEngine) { - return GetHtmlHelper(model, CreateUrlHelper(), viewEngine, CreateModelMetadataProvider()); + return GetHtmlHelper(model, CreateUrlHelper(), viewEngine, TestModelMetadataProvider.CreateDefaultProvider()); } public static HtmlHelper GetHtmlHelper( @@ -129,7 +129,7 @@ namespace Microsoft.AspNet.Mvc.Rendering model, CreateUrlHelper(), viewEngine, - CreateModelMetadataProvider(), + TestModelMetadataProvider.CreateDefaultProvider(), innerHelperWrapper); } @@ -282,10 +282,5 @@ namespace Microsoft.AspNet.Mvc.Rendering { return Mock.Of(); } - - private static IModelMetadataProvider CreateModelMetadataProvider() - { - return new DataAnnotationsModelMetadataProvider(); - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperCheckboxTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperCheckboxTest.cs index 2ce35c5b1d..4fb53da03b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperCheckboxTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperCheckboxTest.cs @@ -278,7 +278,7 @@ namespace Microsoft.AspNet.Mvc.Rendering var expected = @"" + @""; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var viewDataDictionary = new ViewDataDictionary(metadataProvider) { Model = new ModelWithValidation() @@ -395,7 +395,7 @@ namespace Microsoft.AspNet.Mvc.Rendering private static ViewDataDictionary GetModelWithValidationViewData() { - var provider = new DataAnnotationsModelMetadataProvider(); + var provider = TestModelMetadataProvider.CreateDefaultProvider(); var viewData = new ViewDataDictionary(provider) { { "ComplexProperty.Property1", true }, diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs index 6040c57299..c35c0d1615 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs @@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.Core var provider = new TestModelMetadataProvider(); provider .ForType() - .Then(mm => mm.DisplayName = displayName); + .DisplayDetails(dd => dd.DisplayName = displayName); var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); var enumerableHelper = DefaultTemplatesUtilities.GetHtmlHelperForEnumerable(provider: provider); @@ -124,7 +124,7 @@ namespace Microsoft.AspNet.Mvc.Core var provider = new TestModelMetadataProvider(); provider .ForProperty("Property1") - .Then(mm => mm.DisplayName = displayName); + .DisplayDetails(dd => dd.DisplayName = displayName); var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); var enumerableHelper = DefaultTemplatesUtilities.GetHtmlHelperForEnumerable(provider: provider); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayTextTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayTextTest.cs index 95db4f502c..6a72a2cb35 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayTextTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayTextTest.cs @@ -43,8 +43,13 @@ namespace Microsoft.AspNet.Mvc.Rendering public void DisplayText_ReturnsNullDisplayText_IfSetAndValueNull() { // Arrange - var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: null); - helper.ViewData.ModelMetadata.NullDisplayText = "Null display Text"; + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.NullDisplayText = "Null display Text"; + }); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: null, provider: provider); // Act var result = helper.DisplayText(expression: string.Empty); @@ -57,8 +62,13 @@ namespace Microsoft.AspNet.Mvc.Rendering public void DisplayTextFor_ReturnsNullDisplayText_IfSetAndValueNull() { // Arrange - var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: null); - helper.ViewData.ModelMetadata.NullDisplayText = "Null display Text"; + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.NullDisplayText = "Null display Text"; + }); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: null, provider: provider); // Act var result = helper.DisplayTextFor(m => m); @@ -120,8 +130,13 @@ namespace Microsoft.AspNet.Mvc.Rendering SimpleDisplay = "Simple display text", }; - var helper = DefaultTemplatesUtilities.GetHtmlHelper(model); - helper.ViewData.ModelMetadata.SimpleDisplayProperty = nameof(OverriddenToStringModel.SimpleDisplay); + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.SimpleDisplayProperty = nameof(OverriddenToStringModel.SimpleDisplay); + }); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: model, provider: provider); // Act var result = helper.DisplayText(expression: string.Empty); @@ -138,9 +153,14 @@ namespace Microsoft.AspNet.Mvc.Rendering { SimpleDisplay = "Simple display text", }; - - var helper = DefaultTemplatesUtilities.GetHtmlHelper(model); - helper.ViewData.ModelMetadata.SimpleDisplayProperty = nameof(OverriddenToStringModel.SimpleDisplay); + + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.SimpleDisplayProperty = nameof(OverriddenToStringModel.SimpleDisplay); + }); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: model, provider: provider); // Act var result = helper.DisplayTextFor(m => m); @@ -159,8 +179,13 @@ namespace Microsoft.AspNet.Mvc.Rendering SimpleDisplay = "Simple display text", }; - var helper = DefaultTemplatesUtilities.GetHtmlHelper(model); - helper.ViewData.ModelMetadata.SimpleDisplayProperty = nameof(OverriddenToStringModel.SimpleDisplay); + var provider = new TestModelMetadataProvider(); + provider.ForType().DisplayDetails(dd => + { + dd.SimpleDisplayProperty = nameof(OverriddenToStringModel.SimpleDisplay); + }); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: model, provider: provider); // Act var result = helper.DisplayText("Name"); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs index 0580432877..730a11ddfd 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs @@ -62,33 +62,6 @@ namespace Microsoft.AspNet.Mvc.Core Assert.Equal("", labelForResult.ToString()); } - [Fact] - public void LabelHelpers_ReturnEmptyForModel_IfMetadataPropertyNameEmpty() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: string.Empty); - - var helper = DefaultTemplatesUtilities.GetHtmlHelper(); - helper.ViewData.ModelExplorer = new ModelExplorer(provider, metadata, model: null); - - // Act - var labelResult = helper.Label(expression: string.Empty); - var labelNullResult = helper.Label(expression: null); // null is another alias for current model - var labelForResult = helper.LabelFor(m => m); - var labelForModelResult = helper.LabelForModel(); - - // Assert - Assert.Empty(labelResult.ToString()); - Assert.Empty(labelNullResult.ToString()); - Assert.Empty(labelForResult.ToString()); - Assert.Empty(labelForModelResult.ToString()); - } - [Fact] public void LabelHelpers_DisplayMetadataPropertyNameForProperty() { @@ -134,8 +107,12 @@ namespace Microsoft.AspNet.Mvc.Core public void LabelHelpers_ReturnEmptyForModel_IfDisplayNameEmpty() { // Arrange - var helper = DefaultTemplatesUtilities.GetHtmlHelper(); - helper.ViewData.ModelMetadata.DisplayName = string.Empty; + var provider = new TestModelMetadataProvider(); + provider + .ForType() + .DisplayDetails(dd => dd.DisplayName = string.Empty); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); // Act var labelResult = helper.Label(expression: string.Empty); @@ -156,8 +133,12 @@ namespace Microsoft.AspNet.Mvc.Core public void LabelHelpers_DisplayDisplayName_IfNonNull(string displayName) { // Arrange - var helper = DefaultTemplatesUtilities.GetHtmlHelper(); - helper.ViewData.ModelMetadata.DisplayName = displayName; + var provider = new TestModelMetadataProvider(); + provider + .ForType() + .DisplayDetails(dd => dd.DisplayName = displayName); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); // Act var labelResult = helper.Label(expression: string.Empty); @@ -174,15 +155,16 @@ namespace Microsoft.AspNet.Mvc.Core public void LabelHelpers_ReturnEmptyForProperty_IfDisplayNameEmpty() { // Arrange - var provider = new EmptyModelMetadataProvider(); + var provider = new TestModelMetadataProvider(); + provider + .ForProperty("Property1") + .DisplayDetails(dd => dd.DisplayName = string.Empty); var modelExplorer = provider .GetModelExplorerForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel), model: null) .GetExplorerForProperty("Property1"); var helper = DefaultTemplatesUtilities.GetHtmlHelper(); - helper.ViewData.ModelExplorer = modelExplorer; - helper.ViewData.ModelMetadata.DisplayName = string.Empty; // Act var labelResult = helper.Label(expression: string.Empty); @@ -206,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.Core var provider = new TestModelMetadataProvider(); provider .ForProperty("Property1") - .Then(mm => mm.DisplayName = displayName); + .DisplayDetails(dd => dd.DisplayName = displayName); var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperNameExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperNameExtensionsTest.cs index e24a8bca0a..f247ddd9de 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperNameExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperNameExtensionsTest.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.Rendering; using Moq; using Xunit; @@ -140,11 +140,8 @@ namespace Microsoft.AspNet.Mvc.Core // Arrange var provider = new Mock(MockBehavior.Strict); var metadata = new Mock( - MockBehavior.Strict, - provider.Object, - null, - typeof(object), - null); + MockBehavior.Loose, + ModelMetadataIdentity.ForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel))); provider .Setup(m => m.GetMetadataForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel))) .Returns(metadata.Object); @@ -163,7 +160,7 @@ namespace Microsoft.AspNet.Mvc.Core // Only the ViewDataDictionary should do anything with metadata. provider.Verify( m => m.GetMetadataForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel)), - Times.Exactly(2)); + Times.Exactly(1)); } [Fact] @@ -172,15 +169,12 @@ namespace Microsoft.AspNet.Mvc.Core // Arrange var provider = new Mock(MockBehavior.Strict); var metadata = new Mock( - MockBehavior.Strict, - provider.Object, - null, - typeof(object), - null); - + MockBehavior.Loose, + ModelMetadataIdentity.ForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel))); provider .Setup(m => m.GetMetadataForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel))) .Returns(metadata.Object); + var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider.Object); // Act (do not throw) @@ -193,7 +187,7 @@ namespace Microsoft.AspNet.Mvc.Core // Only the ViewDataDictionary should do anything with metadata. provider.Verify( m => m.GetMetadataForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel)), - Times.Exactly(2)); + Times.Exactly(1)); } [Theory] diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/ViewDataOfTTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/ViewDataOfTTest.cs index 9d2d70a07d..6d1e92b614 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/ViewDataOfTTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/ViewDataOfTTest.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc public void SettingModelThrowsIfTheModelIsNull() { // Arrange - var viewDataOfT = new ViewDataDictionary(new DataAnnotationsModelMetadataProvider()); + var viewDataOfT = new ViewDataDictionary(new EmptyModelMetadataProvider()); ViewDataDictionary viewData = viewDataOfT; // Act and Assert @@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc public void SettingModelThrowsIfTheModelIsIncompatible() { // Arrange - var viewDataOfT = new ViewDataDictionary(new DataAnnotationsModelMetadataProvider()); + var viewDataOfT = new ViewDataDictionary(new EmptyModelMetadataProvider()); ViewDataDictionary viewData = viewDataOfT; // Act and Assert @@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc { // Arrange var value = "some value"; - var viewDataOfT = new ViewDataDictionary(new DataAnnotationsModelMetadataProvider()); + var viewDataOfT = new ViewDataDictionary(new EmptyModelMetadataProvider()); ViewDataDictionary viewData = viewDataOfT; // Act @@ -52,7 +52,7 @@ namespace Microsoft.AspNet.Mvc public void PropertiesInitializedCorrectly() { // Arrange - var viewData = new ViewDataDictionary(new DataAnnotationsModelMetadataProvider()); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); // Act & Assert Assert.Empty(viewData); @@ -79,7 +79,7 @@ namespace Microsoft.AspNet.Mvc public void TemplateInfoPropertiesAreNeverNull() { // Arrange - var viewData = new ViewDataDictionary(new DataAnnotationsModelMetadataProvider()); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); // Act viewData.TemplateInfo.FormattedModelValue = null; diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/TestModelMetadataProvider.cs b/test/Microsoft.AspNet.Mvc.Core.Test/TestModelMetadataProvider.cs index f339c6a062..45e59337df 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/TestModelMetadataProvider.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/TestModelMetadataProvider.cs @@ -3,114 +3,180 @@ using System; using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.Framework.Internal; +using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding { - public class TestModelMetadataProvider : EmptyModelMetadataProvider + public class TestModelMetadataProvider : DefaultModelMetadataProvider { - private List _builders = new List(); - - protected override ModelMetadata CreateMetadataFromPrototype([NotNull] ModelMetadata prototype) + // Creates a provider with all the defaults - includes data annotations + public static IModelMetadataProvider CreateDefaultProvider() { - var metadata = base.CreateMetadataFromPrototype(prototype); - - if (prototype.PropertyName == null) + var detailsProviders = new IMetadataDetailsProvider[] { - foreach (var builder in _builders) - { - builder.Apply(prototype.ModelType, metadata); - } - } - else - { - foreach (var builder in _builders) - { - builder.Apply(prototype.ContainerType, prototype.PropertyName, metadata); - } - } + new DefaultBindingMetadataProvider(), + new DataAnnotationsMetadataDetailsProvider(), + }; - return metadata; + var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); + return new DefaultModelMetadataProvider(compositeDetailsProvider); + } + + private readonly TestModelMetadataDetailsProvider _detailsProvider; + + public TestModelMetadataProvider() + : this(new TestModelMetadataDetailsProvider()) + { + } + + private TestModelMetadataProvider(TestModelMetadataDetailsProvider detailsProvider) + : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[] + { + new DefaultBindingMetadataProvider(), + new DataAnnotationsMetadataDetailsProvider(), + detailsProvider + })) + { + _detailsProvider = detailsProvider; } public IMetadataBuilder ForType(Type type) { - var builder = new MetadataBuilder(type); - _builders.Add(builder); + var key = ModelMetadataIdentity.ForType(type); + + var builder = new MetadataBuilder(key); + _detailsProvider.Builders.Add(builder); return builder; } public IMetadataBuilder ForType() { - var builder = new MetadataBuilder(typeof(TModel)); - _builders.Add(builder); - return builder; + return ForType(typeof(TModel)); } public IMetadataBuilder ForProperty(Type containerType, string propertyName) { - var builder = new MetadataBuilder(containerType, propertyName); - _builders.Add(builder); + var property = containerType.GetRuntimeProperty(propertyName); + Assert.NotNull(property); + + var key = ModelMetadataIdentity.ForProperty(property.PropertyType, property.Name, containerType); + + var builder = new MetadataBuilder(key); + _detailsProvider.Builders.Add(builder); return builder; } public IMetadataBuilder ForProperty(string propertyName) { - var builder = new MetadataBuilder(typeof(TContainer), propertyName); - _builders.Add(builder); - return builder; + return ForProperty(typeof(TContainer), propertyName); + } + + private class TestModelMetadataDetailsProvider : + IBindingMetadataProvider, + IDisplayMetadataProvider, + IValidationMetadataProvider + { + public List Builders { get; } = new List(); + + public void GetBindingMetadata([NotNull] BindingMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + + public void GetDisplayMetadata([NotNull] DisplayMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + + public void GetValidationMetadata([NotNull] ValidationMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } } public interface IMetadataBuilder { - IMetadataBuilder Then(Action action); + IMetadataBuilder BindingDetails(Action action); + + IMetadataBuilder DisplayDetails(Action action); + + IMetadataBuilder ValidationDetails(Action action); } private class MetadataBuilder : IMetadataBuilder { - private List> _actions = new List>(); + private List> _bindingActions = new List>(); + private List> _displayActions = new List>(); + private List> _valiationActions = new List>(); - private readonly Type _type; - private readonly Type _containerType; - private readonly string _propertyName; + private readonly ModelMetadataIdentity _key; - public MetadataBuilder(Type type) + public MetadataBuilder(ModelMetadataIdentity key) { - _type = type; + _key = key; } - public MetadataBuilder(Type containerType, string propertyName) + public void Apply(BindingMetadataProviderContext context) { - _containerType = containerType; - _propertyName = propertyName; + if (_key.Equals(context.Key)) + { + foreach (var action in _bindingActions) + { + action(context.BindingMetadata); + } + } } - public IMetadataBuilder Then(Action action) + public void Apply(DisplayMetadataProviderContext context) { - _actions.Add(action); + if (_key.Equals(context.Key)) + { + foreach (var action in _displayActions) + { + action(context.DisplayMetadata); + } + } + } + + public void Apply(ValidationMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _valiationActions) + { + action(context.ValidationMetadata); + } + } + } + + public IMetadataBuilder BindingDetails(Action action) + { + _bindingActions.Add(action); return this; } - public void Apply(Type type, ModelMetadata metadata) + public IMetadataBuilder DisplayDetails(Action action) { - if (type == _type) - { - foreach (var action in _actions) - { - action(metadata); - } - } + _displayActions.Add(action); + return this; } - public void Apply(Type containerType, string propertyName, ModelMetadata metadata) + public IMetadataBuilder ValidationDetails(Action action) { - if (containerType == _containerType && propertyName == _propertyName) - { - foreach (var action in _actions) - { - action(metadata); - } - } + _valiationActions.Add(action); + return this; } } } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorInstrumentationTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorInstrumentationTests.cs index 9b5d8e6273..c571c97a35 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorInstrumentationTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorInstrumentationTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { public class RazorInstrumentationTests { - private readonly IServiceProvider _services = TestHelper.CreateServices("RazorInstrumentationWebsite"); + private readonly IServiceProvider _services = TestHelper.CreateServices("RazorInstrumentationWebSite"); private readonly Action _app = new Startup().Configure; public static IEnumerable InstrumentationData diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ArrayModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ArrayModelBinderTest.cs index f880d554ce..d9ffd21d4b 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ArrayModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ArrayModelBinderTest.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { "someName[0]", "42" }, { "someName[1]", "84" } }; - ModelBindingContext bindingContext = GetBindingContext(valueProvider); + var bindingContext = GetBindingContext(valueProvider); var binder = new ArrayModelBinder(); // Act @@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task GetBinder_ValueProviderDoesNotContainPrefix_ReturnsNull() { // Arrange - ModelBindingContext bindingContext = GetBindingContext(new SimpleHttpValueProvider()); + var bindingContext = GetBindingContext(new SimpleHttpValueProvider()); var binder = new ArrayModelBinder(); // Act @@ -54,8 +54,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { { "foo[0]", "42" }, }; - ModelBindingContext bindingContext = GetBindingContext(valueProvider); - bindingContext.ModelMetadata.IsReadOnly = true; + var bindingContext = GetBindingContext(valueProvider, isReadOnly: true); var binder = new ArrayModelBinder(); // Act @@ -83,10 +82,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test return mockIntBinder.Object; } - private static ModelBindingContext GetBindingContext(IValueProvider valueProvider) + private static ModelBindingContext GetBindingContext( + IValueProvider valueProvider, + bool isReadOnly = false) { - var metadataProvider = new EmptyModelMetadataProvider(); - ModelBindingContext bindingContext = new ModelBindingContext + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider.ForType().BindingDetails(bd => bd.IsReadOnly = isReadOnly); + + var bindingContext = new ModelBindingContext { ModelMetadata = metadataProvider.GetMetadataForType(typeof(int[])), ModelName = "someName", diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BinderTypeBasedModelBinderModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BinderTypeBasedModelBinderModelBinderTest.cs index c185b017ab..c0c9daf9dc 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BinderTypeBasedModelBinderModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BinderTypeBasedModelBinderModelBinderTest.cs @@ -33,8 +33,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task BindModel_ReturnsTrueEvenIfSelectedBinderReturnsFalse() { // Arrange - var bindingContext = GetBindingContext(typeof(Person)); - bindingContext.ModelMetadata.BinderType = typeof(FalseModelBinder); + var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(FalseModelBinder)); var binder = new BinderTypeBasedModelBinder(); @@ -49,8 +48,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task BindModel_CallsBindAsync_OnProvidedModelBinder() { // Arrange - var bindingContext = GetBindingContext(typeof(Person)); - bindingContext.ModelMetadata.BinderType = typeof(TrueModelBinder); + var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(TrueModelBinder)); var model = new Person(); var innerModelBinder = new TrueModelBinder(); @@ -75,8 +73,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task BindModel_CallsBindAsync_OnProvidedModelBinderProvider() { // Arrange - var bindingContext = GetBindingContext(typeof(Person)); - bindingContext.ModelMetadata.BinderType = typeof(ModelBinderProvider); + var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(ModelBinderProvider)); var model = new Person(); var provider = new ModelBinderProvider(); @@ -102,8 +99,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public async Task BindModel_ForNonModelBinderAndModelBinderProviderTypes_Throws() { // Arrange - var bindingContext = GetBindingContext(typeof(Person)); - bindingContext.ModelMetadata.BinderType = typeof(Person); + var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(Person)); var binder = new BinderTypeBasedModelBinder(); var expected = "The type '" + typeof(Person).FullName + "' must implement either " + @@ -118,9 +114,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test Assert.Equal(expected, ex.Message); } - private static ModelBindingContext GetBindingContext(Type modelType) + private static ModelBindingContext GetBindingContext(Type modelType, Type binderType = null) { - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider.ForType(modelType).BindingDetails(bd => bd.BinderType = binderType); + var operationBindingContext = new OperationBindingContext { MetadataProvider = metadataProvider, diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs index ef6c7bd2c8..8a075f4ec4 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs @@ -52,13 +52,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public async Task BindingSourceModelBinder_ReturnsFalse_NonMatchingSource() { // Arrange - var context = new ModelBindingContext(); - context.ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(string)); + var provider = new TestModelMetadataProvider(); + provider.ForType().BindingDetails(d => d.BindingSource = BindingSource.Query); - context.ModelMetadata.BinderMetadata = new ModelBinderAttribute() - { - BindingSource = BindingSource.Query, - }; + var context = new ModelBindingContext(); + context.ModelMetadata = provider.GetMetadataForType(typeof(string)); var binder = new TestableBindingSourceModelBinder(BindingSource.Body); @@ -74,13 +72,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public async Task BindingSourceModelBinder_ReturnsTrue_MatchingSource() { // Arrange - var context = new ModelBindingContext(); - context.ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(string)); + var provider = new TestModelMetadataProvider(); + provider.ForType().BindingDetails(d => d.BindingSource = BindingSource.Body); - context.ModelMetadata.BinderMetadata = new ModelBinderAttribute() - { - BindingSource = BindingSource.Body, - }; + var context = new ModelBindingContext(); + context.ModelMetadata = provider.GetMetadataForType(typeof(string)); var binder = new TestableBindingSourceModelBinder(BindingSource.Body); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CollectionModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CollectionModelBinderTest.cs index 37721562d8..7cea3eda1a 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CollectionModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CollectionModelBinderTest.cs @@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { OperationBindingContext = new OperationBindingContext() { - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), }, }; diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ComplexModelDtoTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ComplexModelDtoTest.cs index 8c4876e0d3..f6c2057440 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ComplexModelDtoTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ComplexModelDtoTest.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test private static ModelMetadata GetModelMetadata() { - return new ModelMetadata(new EmptyModelMetadataProvider(), typeof(object), typeof(object), "PropertyName"); + return new EmptyModelMetadataProvider().GetMetadataForProperty(typeof(string), "Length"); } } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CompositeModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CompositeModelBinderTest.cs index ceecf6cfd7..bd9076fee4 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CompositeModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CompositeModelBinderTest.cs @@ -302,7 +302,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test IModelValidatorProvider validatorProvider = null) { validatorProvider = validatorProvider ?? GetValidatorProvider(); - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var bindingContext = new ModelBindingContext { FallbackToEmptyPrefix = true, diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs index d0bc5c1583..eab187aee4 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test [InlineData(typeof(object))] [InlineData(typeof(int))] [InlineData(typeof(int[]))] - [InlineData(typeof(TestFromHeader))] + [InlineData(typeof(BindingSource))] public async Task BindModelAsync_ReturnsTrue_ForAllTypes(Type type) { // Arrange @@ -74,7 +74,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test private static ModelBindingContext GetBindingContext(Type modelType) { - var metadataProvider = new EmptyModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider.ForType(modelType).BindingDetails(d => d.BindingSource = BindingSource.Header); + var bindingContext = new ModelBindingContext { ModelMetadata = metadataProvider.GetMetadataForType(modelType), @@ -87,13 +89,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test } }; - bindingContext.ModelMetadata.BinderMetadata = new TestFromHeader(); return bindingContext; } - - public class TestFromHeader : IBindingSourceMetadata - { - public BindingSource BindingSource { get; } = BindingSource.Header; - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs index 66d0c8b5f9..085219d750 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs @@ -23,24 +23,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding [InlineData(typeof(Person), false)] [InlineData(typeof(EmptyModel), true)] [InlineData(typeof(EmptyModel), false)] - public async Task - CanCreateModel_CreatesModel_ForTopLevelObjectIfThereIsExplicitPrefix(Type modelType, bool isPrefixProvided) + public async Task CanCreateModel_CreatesModel_ForTopLevelObjectIfThereIsExplicitPrefix( + Type modelType, + bool isPrefixProvided) { var mockValueProvider = new Mock(); mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny())) .Returns(Task.FromResult(false)); + var metadataProvider = new TestModelMetadataProvider(); + if (isPrefixProvided) + { + metadataProvider.ForType().BindingDetails(bd => bd.BinderModelName = "prefix"); + } + var bindingContext = new MutableObjectBinderContext { ModelBindingContext = new ModelBindingContext { // Random type. - ModelMetadata = GetMetadataForType(typeof(Person)), + ModelMetadata = metadataProvider.GetMetadataForType(typeof(Person)), ValueProvider = mockValueProvider.Object, OperationBindingContext = new OperationBindingContext { ValueProvider = mockValueProvider.Object, - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = metadataProvider, ValidatorProvider = Mock.Of(), }, @@ -49,7 +56,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } }; - bindingContext.ModelBindingContext.ModelMetadata.BinderModelName = isPrefixProvided ? "prefix" : null; var mutableBinder = new TestableMutableObjectModelBinder(); bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties( bindingContext.ModelBindingContext); @@ -84,7 +90,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { ValidatorProvider = Mock.Of(), ValueProvider = mockValueProvider.Object, - MetadataProvider = new DataAnnotationsModelMetadataProvider() + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider() } } }; @@ -170,7 +176,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { ValidatorProvider = Mock.Of(), ValueProvider = mockValueProvider.Object, - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), }, // Setting it to empty ensures that model does not get created becasue of no model name. @@ -214,7 +220,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { ValidatorProvider = Mock.Of(), ValueProvider = mockValueProvider.Object, - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), }, // Setting it to empty ensures that model does not get created becasue of no model name. ModelName = "dummyName" @@ -269,7 +275,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding OperationBindingContext = new OperationBindingContext { ValueProvider = mockOriginalValueProvider.Object, - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), ValidatorProvider = Mock.Of(), }, @@ -314,7 +320,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { ValidatorProvider = Mock.Of(), ValueProvider = mockOriginalValueProvider.Object, - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), }, // Setting it to empty ensures that model does not get created becasue of no model name. ModelName = "dummyName" @@ -349,7 +355,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding OperationBindingContext = new OperationBindingContext { ModelBinder = mockDtoBinder.Object, - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), ValidatorProvider = Mock.Of() } }; @@ -401,7 +407,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding OperationBindingContext = new OperationBindingContext { ModelBinder = mockDtoBinder.Object, - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), ValidatorProvider = Mock.Of() } }; @@ -576,7 +582,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding OperationBindingContext = new OperationBindingContext { ValidatorProvider = Mock.Of(), - MetadataProvider = new DataAnnotationsModelMetadataProvider() + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider() } }; @@ -611,7 +617,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding OperationBindingContext = new OperationBindingContext { ValidatorProvider = Mock.Of(), - MetadataProvider = new DataAnnotationsModelMetadataProvider() + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider() }, }; @@ -640,7 +646,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding RequestServices = CreateServices() }, ValidatorProvider = Mock.Of(), - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), } }; @@ -665,7 +671,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding OperationBindingContext = new OperationBindingContext { ValidatorProvider = Mock.Of(), - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), } }; @@ -719,7 +725,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ModelName = "theModel", OperationBindingContext = new OperationBindingContext { - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), ValidatorProvider = Mock.Of() } }; @@ -770,7 +776,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ModelState = new ModelStateDictionary(), OperationBindingContext = new OperationBindingContext { - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), ValidatorProvider = Mock.Of() } }; @@ -1367,7 +1373,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ModelName = "theModel", OperationBindingContext = new OperationBindingContext { - MetadataProvider = new DataAnnotationsModelMetadataProvider(), + MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(), ValidatorProvider = new CompositeModelValidatorProvider(providers) } }; @@ -1383,24 +1389,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private static ModelMetadata GetMetadataForCanUpdateProperty(string propertyName) { - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); return metadataProvider.GetMetadataForProperty(typeof(MyModelTestingCanUpdateProperty), propertyName); } private static ModelMetadata GetMetadataForType(Type t) { - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); return metadataProvider.GetMetadataForType(t); } - private static ModelMetadata GetMetadataForParameter(MethodInfo methodInfo, string parameterName) - { - var metadataProvider = new DataAnnotationsModelMetadataProvider(); - return metadataProvider.GetMetadataForParameter( - methodInfo: methodInfo, - parameterName: parameterName); - } - private class EmptyModel { } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs deleted file mode 100644 index 8742f0b0dc..0000000000 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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; -#if ASPNET50 -using System.ComponentModel; -#endif -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Microsoft.AspNet.Testing; -using Xunit; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - public class AssociatedMetadataProviderTest - { - // GetMetadataForProperties - - [Fact] - public void GetMetadataForPropertiesCreatesMetadataForAllPropertiesOnModelWithPropertyValues() - { - // Arrange - var model = new PropertyModel { LocalAttributes = 42, MetadataAttributes = "hello", MixedAttributes = 21.12 }; - var provider = new TestableAssociatedMetadataProvider(); - - // Act - // Call ToList() to force the lazy evaluation to evaluate - provider.GetMetadataForProperties(typeof(PropertyModel)).ToList(); - - // Assert - var local = Assert.Single( - provider.CreateMetadataPrototypeLog, - m => m.ContainerType == typeof(PropertyModel) && m.PropertyName == "LocalAttributes"); - Assert.Equal(typeof(int), local.ModelType); - Assert.True(local.Attributes.Any(a => a is RequiredAttribute)); - - var metadata = Assert.Single( - provider.CreateMetadataPrototypeLog, - m => m.ContainerType == typeof(PropertyModel) && m.PropertyName == "MetadataAttributes"); - Assert.Equal(typeof(string), metadata.ModelType); - Assert.True(metadata.Attributes.Any(a => a is RangeAttribute)); - - var mixed = Assert.Single( - provider.CreateMetadataPrototypeLog, - m => m.ContainerType == typeof(PropertyModel) && m.PropertyName == "MixedAttributes"); - Assert.Equal(typeof(double), mixed.ModelType); - Assert.True(mixed.Attributes.Any(a => a is RequiredAttribute)); - Assert.True(mixed.Attributes.Any(a => a is RangeAttribute)); - } - - [Fact] - public void GetMetadataForProperties_ExcludesIndexers() - { - // Arrange - var model = new ModelWithIndexer(); - var provider = new TestableAssociatedMetadataProvider(); - var modelType = model.GetType(); - - // Act - provider.GetMetadataForProperties(modelType).ToList(); - - // Assert - Assert.Equal(2, provider.CreateMetadataFromPrototypeLog.Count); - - var valueMetadata = Assert.Single( - provider.CreateMetadataPrototypeLog, - m => m.ContainerType == modelType && m.PropertyName == "Value"); - Assert.Equal(typeof(string), valueMetadata.ModelType); - Assert.Single(valueMetadata.Attributes.OfType()); - - var testPropertyMetadata = Assert.Single( - provider.CreateMetadataPrototypeLog, - m => m.ContainerType == modelType && m.PropertyName == "TestProperty"); - Assert.Equal(typeof(string), testPropertyMetadata.ModelType); - } - - [Fact] - public void GetMetadataForParameterNullOrEmptyPropertyNameThrows() - { - // Arrange - var provider = new TestableAssociatedMetadataProvider(); - - // Act & Assert - ExceptionAssert.ThrowsArgumentNullOrEmpty( - () => provider.GetMetadataForParameter(methodInfo: null, parameterName: null), - "parameterName"); - ExceptionAssert.ThrowsArgumentNullOrEmpty( - () => provider.GetMetadataForParameter(methodInfo: null, parameterName: null), - "parameterName"); - } - - // GetMetadata and access metadata for a property - - [Fact] - public void GetMetadataForProperty_WithLocalAttributes() - { - // Arrange - var provider = new TestableAssociatedMetadataProvider(); - var metadata = new ModelMetadata(provider, null, typeof(PropertyModel), null); - - var propertyMetadata = new ModelMetadata(provider, typeof(PropertyModel), typeof(int), "LocalAttributes"); - provider.CreateMetadataFromPrototypeReturnValue = propertyMetadata; - - // Act - var result = provider.GetMetadataForProperty(typeof(PropertyModel), "LocalAttributes"); - - // Assert - Assert.Same(propertyMetadata, result); - var localAttributes = Assert.Single( - provider.CreateMetadataPrototypeLog, - parameters => parameters.PropertyName == "LocalAttributes"); - Assert.Single(localAttributes.Attributes, a => a is RequiredAttribute); - } - - [Fact] - public void GetMetadataForProperty_WithMetadataAttributes() - { - // Arrange - var provider = new TestableAssociatedMetadataProvider(); - var metadata = new ModelMetadata(provider, null, typeof(PropertyModel), null); - - var propertyMetadata = new ModelMetadata(provider, typeof(PropertyModel), typeof(string), "MetadataAttributes"); - provider.CreateMetadataFromPrototypeReturnValue = propertyMetadata; - - // Act - var result = metadata.Properties["MetadataAttributes"]; - - // Assert - Assert.Same(propertyMetadata, result); - var parmaters = Assert.Single( - provider.CreateMetadataPrototypeLog, - p => p.PropertyName == "MetadataAttributes"); - Assert.Single(parmaters.Attributes, a => a is RangeAttribute); - } - - [Fact] - public void GetMetadataForProperty_WithMixedAttributes() - { - // Arrange - var provider = new TestableAssociatedMetadataProvider(); - var metadata = new ModelMetadata(provider, null, typeof(PropertyModel), null); - - var propertyMetadata = new ModelMetadata(provider, typeof(PropertyModel), typeof(double), "MixedAttributes"); - provider.CreateMetadataFromPrototypeReturnValue = propertyMetadata; - - // Act - var result = metadata.Properties["MixedAttributes"]; - - // Assert - Assert.Same(propertyMetadata, result); - var parms = Assert.Single(provider.CreateMetadataPrototypeLog, p => p.PropertyName == "MixedAttributes"); - Assert.Single(parms.Attributes, a => a is RequiredAttribute); - Assert.Single(parms.Attributes, a => a is RangeAttribute); - } - - // GetMetadataForType - -#if ASPNET50 // No ReadOnlyAttribute in K - [Fact] - public void GetMetadataForTypeIncludesAttributesOnType() - { - var provider = new TestableAssociatedMetadataProvider(); - var metadata = new ModelMetadata(provider, null, typeof(TypeModel), null); - provider.CreateMetadataFromPrototypeReturnValue = metadata; - - // Act - var result = provider.GetMetadataForType(typeof(TypeModel)); - - // Assert - Assert.Same(metadata, result); - var parms = Assert.Single(provider.CreateMetadataPrototypeLog, p => p.ModelType == typeof(TypeModel)); - Assert.Single(parms.Attributes, a => a is ReadOnlyAttribute); - } -#endif - - // Helpers - - private class PropertyModel - { - [Required] - public int LocalAttributes { get; set; } - - [Range(10, 100)] - public string MetadataAttributes { get; set; } - - [Required] - [Range(10, 100)] - public double MixedAttributes { get; set; } - } - - private class BaseType - { - public string TestProperty { get; set; } - } - - private class ModelWithIndexer : BaseType - { - public string this[string x] - { - get { return string.Empty; } - set { } - } - - [MinLength(4)] - public string Value { get; set; } - } - -#if ASPNET50 // No [ReadOnly] in K - [ReadOnly(true)] - private class TypeModel - { - } -#endif - - private class TestableAssociatedMetadataProvider : AssociatedMetadataProvider - { - public List CreateMetadataPrototypeLog = new List(); - public List CreateMetadataFromPrototypeLog = new List(); - public ModelMetadata CreateMetadataPrototypeReturnValue = null; - public ModelMetadata CreateMetadataFromPrototypeReturnValue = null; - - protected override ModelMetadata CreateMetadataPrototype(IEnumerable attributes, Type containerType, Type modelType, string propertyName) - { - CreateMetadataPrototypeLog.Add(new CreateMetadataPrototypeParams - { - Attributes = attributes, - ContainerType = containerType, - ModelType = modelType, - PropertyName = propertyName - }); - - return CreateMetadataPrototypeReturnValue; - } - - protected override ModelMetadata CreateMetadataFromPrototype(ModelMetadata prototype) - { - CreateMetadataFromPrototypeLog.Add(new CreateMetadataFromPrototypeParams - { - Prototype = prototype, - }); - - return CreateMetadataFromPrototypeReturnValue; - } - } - - private class CreateMetadataPrototypeParams - { - public IEnumerable Attributes { get; set; } - public Type ContainerType { get; set; } - public Type ModelType { get; set; } - public string PropertyName { get; set; } - } - - private class CreateMetadataFromPrototypeParams - { - public ModelMetadata Prototype { get; set; } - } - } -} diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs deleted file mode 100644 index 87853f7324..0000000000 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Xunit; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - /// - /// Test the class. - /// - public class CachedDataAnnotationsMetadataAttributesTest - { - [Fact] - public void Constructor_SetsDefaultValuesForAllProperties() - { - // Arrange - var attributes = Enumerable.Empty(); - - // Act - var cache = new CachedDataAnnotationsMetadataAttributes(attributes); - - // Assert - Assert.Null(cache.DataType); - Assert.Null(cache.Display); - Assert.Null(cache.DisplayColumn); - Assert.Null(cache.DisplayFormat); - Assert.Null(cache.Editable); - Assert.Null(cache.HiddenInput); - Assert.Null(cache.Required); - Assert.Null(cache.ScaffoldColumn); - Assert.Null(cache.UIHint); - Assert.Null(cache.BinderMetadata); - Assert.Null(cache.BinderModelNameProvider); - Assert.Empty(cache.PropertyBindingPredicateProviders); - } - - public static TheoryData> - ExpectedAttributeData - { - get - { - return new TheoryData> - { - { new DataTypeAttribute(DataType.Duration), cache => cache.DataType }, - { new DisplayAttribute(), cache => cache.Display }, - { new DisplayColumnAttribute("Property"), cache => cache.DisplayColumn }, - { new DisplayFormatAttribute(), cache => cache.DisplayFormat }, - { new EditableAttribute(allowEdit: false), cache => cache.Editable }, - { new HiddenInputAttribute(), cache => cache.HiddenInput }, - { new RequiredAttribute(), cache => cache.Required }, - { new ScaffoldColumnAttribute(scaffold: true), cache => cache.ScaffoldColumn }, - { new UIHintAttribute("hintHint"), cache => cache.UIHint }, - { new TestBinderMetadata(), cache => cache.BinderMetadata }, - { new TestModelNameProvider(), cache => cache.BinderModelNameProvider }, - }; - } - } - - [Theory] - [MemberData(nameof(ExpectedAttributeData))] - public void Constructor_FindsExpectedAttribute( - object attribute, - Func accessor) - { - // Arrange - var attributes = new[] { attribute }; - - // Act - var cache = new CachedDataAnnotationsMetadataAttributes(attributes); - var result = accessor(cache); - - // Assert - Assert.Same(attribute, result); - } - - [Fact] - public void Constructor_FindsPropertyBindingInfo() - { - // Arrange - var providers = new[] { new TestPredicateProvider(), new TestPredicateProvider() }; - - // Act - var cache = new CachedDataAnnotationsMetadataAttributes(providers); - var result = cache.PropertyBindingPredicateProviders.ToArray(); - - // Assert - Assert.Equal(providers.Length, result.Length); - for (var index = 0; index < providers.Length; index++) - { - Assert.Same(providers[index], result[index]); - } - } - - [Fact] - public void Constructor_FindsBinderTypeProviders() - { - // Arrange - var providers = new[] { new TestBinderTypeProvider(), new TestBinderTypeProvider() }; - - // Act - var cache = new CachedDataAnnotationsMetadataAttributes(providers); - var result = cache.BinderTypeProviders.ToArray(); - - // Assert - Assert.Equal(providers.Length, result.Length); - for (var index = 0; index < providers.Length; index++) - { - Assert.Same(providers[index], result[index]); - } - } - - [Fact] - public void Constructor_FindsDisplayFormat_FromDataType() - { - // Arrange - var dataType = new DataTypeAttribute(DataType.Currency); - var displayFormat = dataType.DisplayFormat; // Non-null for DataType.Currency. - var attributes = new[] { dataType, }; - - // Act - var cache = new CachedDataAnnotationsMetadataAttributes(attributes); - var result = cache.DisplayFormat; - - // Assert - Assert.Same(displayFormat, result); - } - - [Fact] - public void Constructor_FindsDisplayFormat_OverridingDataType() - { - // Arrange - var dataType = new DataTypeAttribute(DataType.Time); // Has a non-null DisplayFormat. - var displayFormat = new DisplayFormatAttribute(); - var attributes = new Attribute[] { dataType, displayFormat, }; - - // Act - var cache = new CachedDataAnnotationsMetadataAttributes(attributes); - var result = cache.DisplayFormat; - - // Assert - Assert.Same(displayFormat, result); - } - - private class TestBinderTypeProvider : IBinderTypeProviderMetadata - { - public Type BinderType { get; set; } - - public BindingSource BindingSource { get; set; } - } - - private class TestPredicateProvider : IPropertyBindingPredicateProvider - { - public Func PropertyFilter - { - get - { - throw new NotImplementedException(); - } - } - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs deleted file mode 100644 index 4009794183..0000000000 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Reflection; -using Xunit; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - public class CachedDataAnnotationsModelMetadataProviderTest - { - [Fact] - public void DataAnnotationsModelMetadataProvider_UsesPredicateOnType() - { - // Arrange - var type = typeof(User); - - var provider = new DataAnnotationsModelMetadataProvider(); - var context = new ModelBindingContext(); - - var expected = new[] { "IsAdmin", "UserName" }; - - // Act - var metadata = provider.GetMetadataForType(type); - - // Assert - var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter; - - var matched = new HashSet(); - foreach (var property in metadata.Properties) - { - if (predicate(context, property.PropertyName)) - { - matched.Add(property.PropertyName); - } - } - - Assert.Equal(expected, matched); - } - - [Fact] - public void DataAnnotationsModelMetadataProvider_UsesPredicateOnParameter() - { - // Arrange - var type = GetType(); - var methodInfo = type.GetMethod( - "ActionWithoutBindAttribute", - BindingFlags.Instance | BindingFlags.NonPublic); - - var provider = new DataAnnotationsModelMetadataProvider(); - var context = new ModelBindingContext(); - - // Note it does an intersection for included -- only properties that - // pass both predicates will be bound. - var expected = new[] { "IsAdmin", "UserName" }; - - // Act - var metadata = provider.GetMetadataForParameter( - methodInfo: methodInfo, - parameterName: "param"); - - // Assert - var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter; - Assert.NotNull(predicate); - - var matched = new HashSet(); - foreach (var property in metadata.Properties) - { - if (predicate(context, property.PropertyName)) - { - matched.Add(property.PropertyName); - } - } - - Assert.Equal(expected, matched); - } - - [Fact] - public void DataAnnotationsModelMetadataProvider_UsesPredicateOnParameter_Merge() - { - // Arrange - var type = GetType(); - var methodInfo = type.GetMethod( - "ActionWithBindAttribute", - BindingFlags.Instance | BindingFlags.NonPublic); - - var provider = new DataAnnotationsModelMetadataProvider(); - var context = new ModelBindingContext(); - - // Note it does an intersection for included -- only properties that - // pass both predicates will be bound. - var expected = new[] { "IsAdmin" }; - - // Act - var metadata = provider.GetMetadataForParameter( - methodInfo: methodInfo, - parameterName: "param"); - - // Assert - var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter; - Assert.NotNull(predicate); - - var matched = new HashSet(); - foreach (var property in metadata.Properties) - { - if (predicate(context, property.PropertyName)) - { - matched.Add(property.PropertyName); - } - } - - Assert.Equal(expected, matched); - } - - [Fact] - public void DataAnnotationsModelMetadataProvider_ReadsModelNameProperty_ForParameters() - { - // Arrange - var type = GetType(); - var methodInfo = type.GetMethod( - "ActionWithBindAttribute", - BindingFlags.Instance | BindingFlags.NonPublic); - - var provider = new DataAnnotationsModelMetadataProvider(); - - // Act - var metadata = provider.GetMetadataForParameter( - methodInfo: methodInfo, - parameterName: "param"); - - // Assert - Assert.Equal("ParameterPrefix", metadata.BinderModelName); - } - - [Fact] - public void DataAnnotationsModelMetadataProvider_ReadsModelNameProperty_ForTypes() - { - // Arrange - var type = typeof(User); - var provider = new DataAnnotationsModelMetadataProvider(); - - // Act - var metadata = provider.GetMetadataForType(type); - - // Assert - Assert.Equal("TypePrefix", metadata.BinderModelName); - } - - - [Fact] - public void DataAnnotationsModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForDisplay() - { - // Arrange - var type = typeof(ScaffoldColumnModel); - var provider = new DataAnnotationsModelMetadataProvider(); - - // Act & Assert - Assert.True(provider.GetMetadataForProperty(type, "NoAttribute").ShowForDisplay); - Assert.True(provider.GetMetadataForProperty(type, "ScaffoldColumnTrue").ShowForDisplay); - Assert.False(provider.GetMetadataForProperty(type, "ScaffoldColumnFalse").ShowForDisplay); - } - - [Fact] - public void DataAnnotationsModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForEdit() - { - // Arrange - var type = typeof(ScaffoldColumnModel); - var provider = new DataAnnotationsModelMetadataProvider(); - - // Act & Assert - Assert.True(provider.GetMetadataForProperty(type, "NoAttribute").ShowForEdit); - Assert.True(provider.GetMetadataForProperty(type, "ScaffoldColumnTrue").ShowForEdit); - Assert.False(provider.GetMetadataForProperty(type, "ScaffoldColumnFalse").ShowForEdit); - } - - [Fact] - public void HiddenInputWorksOnProperty_ForHideSurroundingHtml() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = provider.GetMetadataForType(modelType: typeof(ClassWithHiddenProperties)); - var property = metadata.Properties["DirectlyHidden"]; - - // Act - var result = property.HideSurroundingHtml; - - // Assert - Assert.True(result); - } - - [Fact] - public void HiddenInputWorksOnPropertyType_ForHideSurroundingHtml() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = provider.GetMetadataForType(typeof(ClassWithHiddenProperties)); - var property = metadata.Properties["OfHiddenType"]; - - // Act - var result = property.HideSurroundingHtml; - - // Assert - Assert.True(result); - } - - [Fact] - public void HiddenInputWorksOnProperty_ForTemplateHint() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = provider.GetMetadataForType(typeof(ClassWithHiddenProperties)); - var property = metadata.Properties["DirectlyHidden"]; - - // Act - var result = property.TemplateHint; - - // Assert - Assert.Equal("HiddenInput", result); - } - - [Fact] - public void HiddenInputWorksOnPropertyType_ForTemplateHint() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = provider.GetMetadataForType(typeof(ClassWithHiddenProperties)); - var property = metadata.Properties["OfHiddenType"]; - - // Act - var result = property.TemplateHint; - - // Assert - Assert.Equal("HiddenInput", result); - } - - [Fact] - public void GetMetadataForProperty_WithNoBinderMetadata_GetsItFromType() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - - // Act - var propertyMetadata = provider.GetMetadataForProperty(typeof(Person), nameof(Person.Parent)); - - // Assert - Assert.NotNull(propertyMetadata.BinderMetadata); - var attribute = Assert.IsType(propertyMetadata.BinderMetadata); - Assert.Equal("PersonType", propertyMetadata.BinderModelName); - } - - [Fact] - public void GetMetadataForProperty_WithBinderMetadataOnPropertyAndType_GetsMetadataFromProperty() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - - // Act - var propertyMetadata = provider.GetMetadataForProperty(typeof(Person), nameof(Person.GrandParent)); - - // Assert - Assert.NotNull(propertyMetadata.BinderMetadata); - var attribute = Assert.IsType(propertyMetadata.BinderMetadata); - Assert.Equal("GrandParentProperty", propertyMetadata.BinderModelName); - } - - [Fact] - public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - - // Act - var parameterMetadata = provider.GetMetadataForParameter( - typeof(Person).GetMethod("Update"), - "person"); - - // Assert - Assert.NotNull(parameterMetadata.BinderMetadata); - var attribute = Assert.IsType(parameterMetadata.BinderMetadata); - Assert.Equal("PersonType", parameterMetadata.BinderModelName); - } - - [Fact] - public void GetMetadataForParameter_WithBinderDataOnParameterAndType_GetsMetadataFromParameter() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - - // Act - var parameterMetadata = provider.GetMetadataForParameter( - typeof(Person).GetMethod("Save"), - "person"); - - // Assert - Assert.NotNull(parameterMetadata.BinderMetadata); - var attribute = Assert.IsType(parameterMetadata.BinderMetadata); - Assert.Equal("PersonParameter", parameterMetadata.BinderModelName); - } - - private void ActionWithoutBindAttribute(User param) - { - } - - private void ActionWithBindAttribute([Bind(new string[] { "IsAdmin" }, Prefix = "ParameterPrefix")] User param) - { - } - - public class TypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider - { - public string Name { get; set; } - } - - public class NonTypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider - { - public string Name { get; set; } - } - - [TypeBasedBinder(Name = "PersonType")] - public class Person - { - public Person Parent { get; set; } - - [NonTypeBasedBinder(Name = "GrandParentProperty")] - public Person GrandParent { get; set; } - - public void Update(Person person) - { - } - - public void Save([NonTypeBasedBinder(Name = "PersonParameter")] Person person) - { - } - } - - private class ScaffoldColumnModel - { - public int NoAttribute { get; set; } - - [ScaffoldColumn(scaffold: true)] - public int ScaffoldColumnTrue { get; set; } - - [ScaffoldColumn(scaffold: false)] - public int ScaffoldColumnFalse { get; set; } - } - - [HiddenInput(DisplayValue = false)] - private class HiddenClass - { - public string Property { get; set; } - } - - private class ClassWithHiddenProperties - { - [HiddenInput(DisplayValue = false)] - public string DirectlyHidden { get; set; } - - public HiddenClass OfHiddenType { get; set; } - } - - [Bind(new[] { nameof(IsAdmin), nameof(UserName) }, Prefix = "TypePrefix")] - private class User - { - public int Id { get; set; } - - public bool IsAdmin { get; set; } - - public int UserName { get; set; } - - public int NotIncludedOrExcluded { get; set; } - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs deleted file mode 100644 index 9d5caa7e52..0000000000 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs +++ /dev/null @@ -1,614 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.ComponentModel.DataAnnotations; -using System.Linq; -using Xunit; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - /// - /// Test the class. - /// - public class CachedDataAnnotationsModelMetadataTest - { - [Fact] - public void Constructor_DefersDefaultsToBaseModelMetadata() - { - // Arrange - var attributes = Enumerable.Empty(); - var provider = new DataAnnotationsModelMetadataProvider(); - - // Act - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: attributes); - - // Assert - Assert.NotNull(metadata.AdditionalValues); - Assert.Empty(metadata.AdditionalValues); - Assert.Null(metadata.ContainerType); - - Assert.True(metadata.ConvertEmptyStringToNull); - Assert.False(metadata.HasNonDefaultEditFormat); - Assert.False(metadata.HideSurroundingHtml); - Assert.True(metadata.HtmlEncode); - Assert.False(metadata.IsCollectionType); - Assert.True(metadata.IsComplexType); - Assert.False(metadata.IsNullableValueType); - Assert.False(metadata.IsReadOnly); - Assert.False(metadata.IsRequired); - Assert.True(metadata.ShowForDisplay); - Assert.True(metadata.ShowForEdit); - - Assert.Null(metadata.DataTypeName); - Assert.Null(metadata.Description); - Assert.Null(metadata.DisplayFormatString); - Assert.Null(metadata.DisplayName); - Assert.Null(metadata.EditFormatString); - Assert.Null(metadata.NullDisplayText); - Assert.Null(metadata.SimpleDisplayProperty); - Assert.Null(metadata.TemplateHint); - - Assert.Equal(typeof(object), metadata.ModelType); - Assert.Null(metadata.PropertyName); - - Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order); - - Assert.Null(metadata.BinderModelName); - Assert.Null(metadata.BinderMetadata); - Assert.Null(metadata.PropertyBindingPredicateProvider); - Assert.Null(metadata.BinderType); - } - - public static TheoryData> ExpectedAttributeDataStrings - { - get - { - return new TheoryData> - { - { - new DataTypeAttribute("value"), metadata => metadata.DataTypeName - }, - { - new DataTypeWithCustomDisplayFormat(), metadata => metadata.DisplayFormatString - }, - { - new DataTypeWithCustomEditFormat(), metadata => metadata.EditFormatString - }, - { - new DisplayAttribute { Description = "value" }, metadata => metadata.Description - }, - { - new DisplayAttribute { Name = "value" }, metadata => metadata.DisplayName - }, - { - new DisplayFormatAttribute { DataFormatString = "value" }, - metadata => metadata.DisplayFormatString - }, - { - // DisplayFormatString does not ignore [DisplayFormat] if ApplyFormatInEditMode==true. - new DisplayFormatAttribute { ApplyFormatInEditMode = true, DataFormatString = "value" }, - metadata => metadata.DisplayFormatString - }, - { - new DisplayFormatAttribute { ApplyFormatInEditMode = true, DataFormatString = "value" }, - metadata => metadata.EditFormatString - }, - { - new DisplayFormatAttribute { NullDisplayText = "value" }, metadata => metadata.NullDisplayText - }, - { - new TestModelNameProvider { Name = "value" }, metadata => metadata.BinderModelName - }, - { - new UIHintAttribute("value"), metadata => metadata.TemplateHint - }, - }; - } - } - - [Theory] - [MemberData(nameof(ExpectedAttributeDataStrings))] - public void AttributesOverrideMetadataStrings(object attribute, Func accessor) - { - // Arrange - var attributes = new[] { attribute }; - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(ClassWithDisplayableColumn), - propertyName: null, - attributes: attributes); - - // Act - var result = accessor(metadata); - - // Assert - Assert.Equal("value", result); - } - - [Fact] - public void AttributesOverrideMetadataStrings_SimpleDisplayProperty() - { - // Arrange - var attributes = new[] { new DisplayColumnAttribute("Property") }; - - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(ClassWithDisplayableColumn), - propertyName: null, - attributes: attributes); - - // Act - var result = metadata.SimpleDisplayProperty; - - // Assert - Assert.Equal("Property", result); - } - - public static TheoryData, bool> ExpectedAttributeDataBooleans - { - get - { - return new TheoryData, bool> - { - { - // Edit formats from [DataType] subclass affect HasNonDefaultEditFormat. - new DataTypeWithCustomEditFormat(), - metadata => metadata.HasNonDefaultEditFormat, - true - }, - { - // Edit formats from [DataType] do not affect HasNonDefaultEditFormat. - new DataTypeAttribute(DataType.Date), - metadata => metadata.HasNonDefaultEditFormat, - false - }, - { - new DisplayFormatAttribute { ConvertEmptyStringToNull = false }, - metadata => metadata.ConvertEmptyStringToNull, - false - }, - { - new DisplayFormatAttribute { ConvertEmptyStringToNull = true }, - metadata => metadata.ConvertEmptyStringToNull, - true - }, - { - // Changes only to DisplayFormatString do not affect HasNonDefaultEditFormat. - new DisplayFormatAttribute { DataFormatString = "value" }, - metadata => metadata.HasNonDefaultEditFormat, - false - }, - { - new DisplayFormatAttribute { ApplyFormatInEditMode = true, DataFormatString = "value" }, - metadata => metadata.HasNonDefaultEditFormat, - true - }, - { - new DisplayFormatAttribute { HtmlEncode = false }, - metadata => metadata.HtmlEncode, - false - }, - { - new DisplayFormatAttribute { HtmlEncode = true }, - metadata => metadata.HtmlEncode, - true - }, - { - new EditableAttribute(allowEdit: false), - metadata => metadata.IsReadOnly, - true - }, - { - new EditableAttribute(allowEdit: true), - metadata => metadata.IsReadOnly, - false - }, - { - new HiddenInputAttribute { DisplayValue = false }, - metadata => metadata.HideSurroundingHtml, - true - }, - { - new HiddenInputAttribute { DisplayValue = true }, - metadata => metadata.HideSurroundingHtml, - false - }, - { - new HiddenInputAttribute(), - metadata => string.Equals("HiddenInput", metadata.TemplateHint, StringComparison.Ordinal), - true - }, - { - new RequiredAttribute(), - metadata => metadata.IsRequired, - true - }, - }; - } - } - - [Theory] - [MemberData(nameof(ExpectedAttributeDataBooleans))] - public void AttributesOverrideMetadataBooleans( - Attribute attribute, - Func accessor, - bool expectedResult) - { - // Arrange - var attributes = new[] { attribute }; - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: attributes); - - // Act - var result = accessor(metadata); - - // Assert - Assert.Equal(expectedResult, result); - } - - public static TheoryData DisplayAttribute_OverridesOrderData - { - get - { - return new TheoryData - { - { - new DisplayAttribute(), ModelMetadata.DefaultOrder - }, - { - new DisplayAttribute { Order = int.MinValue }, int.MinValue - }, - { - new DisplayAttribute { Order = -100 }, -100 - }, - { - new DisplayAttribute { Order = -1 }, -1 - }, - { - new DisplayAttribute { Order = 0 }, 0 - }, - { - new DisplayAttribute { Order = 1 }, 1 - }, - { - new DisplayAttribute { Order = 200 }, 200 - }, - { - new DisplayAttribute { Order = int.MaxValue }, int.MaxValue - }, - }; - } - } - - [Theory] - [MemberData(nameof(DisplayAttribute_OverridesOrderData))] - public void DisplayAttribute_OverridesOrder(DisplayAttribute attribute, int expectedOrder) - { - // Arrange - var attributes = new[] { attribute }; - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: attributes); - - // Act - var result = metadata.Order; - - // Assert - Assert.Equal(expectedOrder, result); - } - - [Fact] - public void BinderMetadataIfPresent_Overrides_DefaultBinderMetadata() - { - // Arrange - var firstBinderMetadata = new TestBinderMetadata(); - var secondBinderMetadata = new TestBinderMetadata(); - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: new object[] { firstBinderMetadata, secondBinderMetadata }); - - // Act - var result = metadata.BinderMetadata; - - // Assert - Assert.Same(firstBinderMetadata, result); - } - - [Fact] - public void DataTypeName_Null_IfHtmlEncodeTrue() - { - // Arrange - var displayFormat = new DisplayFormatAttribute { HtmlEncode = true, }; - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: new Attribute[] { displayFormat }); - - // Act - var result = metadata.DataTypeName; - - // Assert - Assert.Null(result); - } - - [Fact] - public void DataTypeName_Html_IfHtmlEncodeFalse() - { - // Arrange - var expected = "Html"; - var displayFormat = new DisplayFormatAttribute { HtmlEncode = false, }; - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: new Attribute[] { displayFormat }); - - // Act - var result = metadata.DataTypeName; - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void DataTypeName_AttributesHaveExpectedPrecedence() - { - // Arrange - var expected = "MultilineText"; - var dataType = new DataTypeAttribute(DataType.MultilineText); - var displayFormat = new DisplayFormatAttribute { HtmlEncode = false, }; - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: new Attribute[] { dataType, displayFormat }); - - // Act - var result = metadata.DataTypeName; - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void DisplayFormatString_AttributesHaveExpectedPrecedence() - { - // Arrange - var expected = "custom format"; - var dataType = new DataTypeAttribute(DataType.Currency); - var displayFormat = new DisplayFormatAttribute { DataFormatString = expected, }; - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: new Attribute[] { dataType, displayFormat }); - - // Act - var result = metadata.DisplayFormatString; - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void EditFormatString_AttributesHaveExpectedPrecedence() - { - // Arrange - var expected = "custom format"; - var dataType = new DataTypeAttribute(DataType.Currency); - var displayFormat = new DisplayFormatAttribute - { - ApplyFormatInEditMode = true, - DataFormatString = expected, - }; - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: new Attribute[] { dataType, displayFormat }); - - // Act - var result = metadata.EditFormatString; - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void EditFormatString_DoesNotAffectDisplayFormat() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: Enumerable.Empty()); - - // Act - metadata.EditFormatString = "custom format"; - - // Assert - Assert.Null(metadata.DisplayFormatString); - } - - [Fact] - public void DisplayFormatString_DoesNotAffectEditFormat() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: Enumerable.Empty()); - - // Act - metadata.DisplayFormatString = "custom format"; - - // Assert - Assert.Null(metadata.EditFormatString); - } - - [Fact] - public void TemplateHint_AttributesHaveExpectedPrecedence() - { - // Arrange - var expected = "this is a hint"; - var hidden = new HiddenInputAttribute(); - var uiHint = new UIHintAttribute(expected); - var provider = new DataAnnotationsModelMetadataProvider(); - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: new Attribute[] { hidden, uiHint }); - - // Act - var result = metadata.TemplateHint; - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void Constructor_FindsBinderTypeProviders_Null() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - - var binderProviders = new[] { new TestBinderTypeProvider(), new TestBinderTypeProvider() }; - - // Act - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: binderProviders); - - // Assert - Assert.Null(metadata.BinderType); - } - - [Fact] - public void Constructor_FindsBinderTypeProviders_Fallback() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - - var binderProviders = new[] - { - new TestBinderTypeProvider(), - new TestBinderTypeProvider() { BinderType = typeof(string) } - }; - - // Act - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: binderProviders); - - // Assert - Assert.Same(typeof(string), metadata.BinderType); - } - - [Fact] - public void Constructor_FindsBinderTypeProviders_FirstAttributeHasPrecedence() - { - // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); - - var binderProviders = new[] - { - new TestBinderTypeProvider() { BinderType = typeof(int) }, - new TestBinderTypeProvider() { BinderType = typeof(string) } - }; - - // Act - var metadata = new CachedDataAnnotationsModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null, - attributes: binderProviders); - - // Assert - Assert.Same(typeof(int), metadata.BinderType); - } - - private class TestBinderTypeProvider : IBinderTypeProviderMetadata - { - public Type BinderType { get; set; } - - public BindingSource BindingSource { get; set; } - } - - private class DataTypeWithCustomDisplayFormat : DataTypeAttribute - { - public DataTypeWithCustomDisplayFormat() : base("Custom datatype") - { - DisplayFormat = new DisplayFormatAttribute - { - DataFormatString = "value", - }; - } - } - - private class DataTypeWithCustomEditFormat : DataTypeAttribute - { - public DataTypeWithCustomEditFormat() : base("Custom datatype") - { - DisplayFormat = new DisplayFormatAttribute - { - ApplyFormatInEditMode = true, - DataFormatString = "value", - }; - } - } - - private class ClassWithDisplayableColumn - { - public string Property { get; set; } - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DataAnnotationsMetadataDetailsProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DataAnnotationsMetadataDetailsProviderTest.cs new file mode 100644 index 0000000000..d26820caf5 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DataAnnotationsMetadataDetailsProviderTest.cs @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.ComponentModel.DataAnnotations; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + public class DataAnnotationsMetadataDetailsProviderTest + { + // Includes attributes with a 'simple' effect on display details. + public static TheoryData, object> DisplayDetailsData + { + get + { + return new TheoryData, object> + { + { new DataTypeAttribute(DataType.Duration), d => d.DataTypeName, DataType.Duration.ToString() }, + + { new DisplayAttribute() { Description = "d" }, d => d.Description, "d" }, + { new DisplayAttribute() { Name = "DN" }, d => d.DisplayName, "DN" }, + { new DisplayAttribute() { Order = 3 }, d => d.Order, 3 }, + + { new DisplayColumnAttribute("Property"), d => d.SimpleDisplayProperty, "Property" }, + + { new DisplayFormatAttribute() { ConvertEmptyStringToNull = true }, d => d.ConvertEmptyStringToNull, true }, + { new DisplayFormatAttribute() { DataFormatString = "{0:G}" }, d => d.DisplayFormatString, "{0:G}" }, + { + new DisplayFormatAttribute() { DataFormatString = "{0:G}", ApplyFormatInEditMode = true }, + d => d.EditFormatString, + "{0:G}" + }, + { new DisplayFormatAttribute() { HtmlEncode = false }, d => d.HtmlEncode, false }, + { new DisplayFormatAttribute() { NullDisplayText = "(null)" }, d => d.NullDisplayText, "(null)" }, + + { new HiddenInputAttribute() { DisplayValue = false }, d => d.HideSurroundingHtml, true }, + + { new ScaffoldColumnAttribute(scaffold: false), d => d.ShowForDisplay, false }, + { new ScaffoldColumnAttribute(scaffold: false), d => d.ShowForEdit, false }, + + { new UIHintAttribute("hintHint"), d => d.TemplateHint, "hintHint" }, + }; + } + } + + [Theory] + [MemberData(nameof(DisplayDetailsData))] + public void GetDisplayDetails_SimpleAttributes( + object attribute, + Func accessor, + object expected) + { + // Arrange + var provider = new DataAnnotationsMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new DisplayMetadataProviderContext(key, new object[] { attribute }); + + // Act + provider.GetDisplayMetadata(context); + + // Assert + var value = accessor(context.DisplayMetadata); + Assert.Equal(expected, value); + } + + [Fact] + public void GetDisplayDetails_FindsDisplayFormat_FromDataType() + { + // Arrange + var provider = new DataAnnotationsMetadataDetailsProvider(); + + var dataType = new DataTypeAttribute(DataType.Currency); + var displayFormat = dataType.DisplayFormat; // Non-null for DataType.Currency. + + var attributes = new[] { dataType, }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new DisplayMetadataProviderContext(key, attributes); + + // Act + provider.GetDisplayMetadata(context); + + // Assert + Assert.Same(displayFormat.DataFormatString, context.DisplayMetadata.DisplayFormatString); + } + + [Fact] + public void GetDisplayDetails_FindsDisplayFormat_OverridingDataType() + { + // Arrange + var provider = new DataAnnotationsMetadataDetailsProvider(); + + var dataType = new DataTypeAttribute(DataType.Time); // Has a non-null DisplayFormat. + var displayFormat = new DisplayFormatAttribute() // But these values override the values from DataType + { + DataFormatString = "Cool {0}", + }; + + var attributes = new Attribute[] { dataType, displayFormat, }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new DisplayMetadataProviderContext(key, attributes); + + // Act + provider.GetDisplayMetadata(context); + + // Assert + Assert.Same(displayFormat.DataFormatString, context.DisplayMetadata.DisplayFormatString); + } + + [Fact] + public void GetDisplayDetails_EditableAttribute_SetsReadOnly() + { + // Arrange + var provider = new DataAnnotationsMetadataDetailsProvider(); + + var editable = new EditableAttribute(allowEdit: false); + + var attributes = new Attribute[] { editable }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new BindingMetadataProviderContext(key, attributes); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Equal(true, context.BindingMetadata.IsReadOnly); + } + + [Fact] + public void GetDisplayDetails_RequiredAttribute_SetsRequired() + { + // Arrange + var provider = new DataAnnotationsMetadataDetailsProvider(); + + var required = new RequiredAttribute(); + + var attributes = new Attribute[] { required }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new BindingMetadataProviderContext(key, attributes); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Equal(true, context.BindingMetadata.IsRequired); + } + + // This is IMPORTANT. Product code needs to use GetName() instead of .Name. It's easy to regress. + [Fact] + public void GetDisplayDetails_DisplayAttribute_NameFromResources() + { + // Arrange + var provider = new DataAnnotationsMetadataDetailsProvider(); + + var display = new DisplayAttribute() + { + Name = "DisplayAttribute_Name", + ResourceType = typeof(Test.Resources), + }; + + var attributes = new Attribute[] { display }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new DisplayMetadataProviderContext(key, attributes); + + // Act + provider.GetDisplayMetadata(context); + + // Assert + Assert.Equal("name from resources", context.DisplayMetadata.DisplayName); + } + + // This is IMPORTANT. Product code needs to use GetDescription() instead of .Description. It's easy to regress. + [Fact] + public void GetDisplayDetails_DisplayAttribute_DescriptionFromResources() + { + // Arrange + var provider = new DataAnnotationsMetadataDetailsProvider(); + + var display = new DisplayAttribute() + { + Description = "DisplayAttribute_Description", + ResourceType = typeof(Test.Resources), + }; + + var attributes = new Attribute[] { display }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new DisplayMetadataProviderContext(key, attributes); + + // Act + provider.GetDisplayMetadata(context); + + // Assert + Assert.Equal("description from resources", context.DisplayMetadata.Description); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs new file mode 100644 index 0000000000..8fdae63905 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + public class DefaultModelMetadataBindingDetailsProviderTest + { + [Fact] + public void GetBindingDetails_FindsBinderTypeProvider() + { + // Arrange + var attributes = new object[] + { + new ModelBinderAttribute() { BinderType = typeof(HeaderModelBinder) }, + new ModelBinderAttribute() { BinderType = typeof(ArrayModelBinder) }, + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForType(typeof(string)), + attributes); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Equal(typeof(HeaderModelBinder), context.BindingMetadata.BinderType); + } + + [Fact] + public void GetBindingDetails_FindsBinderTypeProvider_IfNullFallsBack() + { + // Arrange + var attributes = new object[] + { + new ModelBinderAttribute(), + new ModelBinderAttribute() { BinderType = typeof(HeaderModelBinder) }, + new ModelBinderAttribute() { BinderType = typeof(ArrayModelBinder) }, + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForType(typeof(string)), + attributes); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Equal(typeof(HeaderModelBinder), context.BindingMetadata.BinderType); + } + + [Fact] + public void GetBindingDetails_FindsModelName() + { + // Arrange + var attributes = new object[] + { + new ModelBinderAttribute() { Name = "Product" }, + new ModelBinderAttribute() { Name = "Order" }, + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForType(typeof(string)), + attributes); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Equal("Product", context.BindingMetadata.BinderModelName); + } + + [Fact] + public void GetBindingDetails_FindsModelName_IfEmpty() + { + // Arrange + var attributes = new object[] + { + new ModelBinderAttribute(), + new ModelBinderAttribute() { Name = "Product" }, + new ModelBinderAttribute() { Name = "Order" }, + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForType(typeof(string)), + attributes); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Null(context.BindingMetadata.BinderModelName); + } + + [Fact] + public void GetBindingDetails_FindsBindingSource() + { + // Arrange + var attributes = new object[] + { + new ModelBinderAttribute() { BindingSource = BindingSource.Body }, + new ModelBinderAttribute() { BindingSource = BindingSource.Query }, + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForType(typeof(string)), + attributes); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Equal(BindingSource.Body, context.BindingMetadata.BindingSource); + } + + [Fact] + public void GetBindingDetails_FindsBindingSource_IfNullFallsBack() + { + // Arrange + var attributes = new object[] + { + new ModelBinderAttribute(), + new ModelBinderAttribute() { BindingSource = BindingSource.Body }, + new ModelBinderAttribute() { BindingSource = BindingSource.Query }, + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForType(typeof(string)), + attributes); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Equal(BindingSource.Body, context.BindingMetadata.BindingSource); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataProviderTest.cs new file mode 100644 index 0000000000..96b213aa49 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataProviderTest.cs @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + public class DefaultModelMetadataProviderTest + { + [Fact] + public void GetMetadataForType_IncludesAttributes() + { + // Arrange + var provider = CreateProvider(); + + // Act + var metadata = provider.GetMetadataForType(typeof(ModelType)); + + // Assert + var defaultMetadata = Assert.IsType(metadata); + + var attribute = Assert.IsType(Assert.Single(defaultMetadata.Attributes)); + Assert.Equal("OnType", attribute.Value); + } + + // The attributes and other 'details' are cached + [Fact] + public void GetMetadataForType_Cached() + { + // Arrange + var provider = CreateProvider(); + + // Act + var metadata1 = Assert.IsType(provider.GetMetadataForType(typeof(ModelType))); + var metadata2 = Assert.IsType(provider.GetMetadataForType(typeof(ModelType))); + + // Assert + Assert.Same(metadata1.Attributes, metadata2.Attributes); + Assert.Same(metadata1.BindingMetadata, metadata2.BindingMetadata); + Assert.Same(metadata1.DisplayMetadata, metadata2.DisplayMetadata); + Assert.Same(metadata1.ValidationMetadata, metadata2.ValidationMetadata); + } + + [Fact] + public void GetMetadataForProperties_IncludesAllProperties() + { + // Arrange + var provider = CreateProvider(); + + // Act + var metadata = provider.GetMetadataForProperties(typeof(ModelType)).ToArray(); + + // Assert + Assert.Equal(2, metadata.Length); + Assert.Single(metadata, m => m.PropertyName == "Property1"); + Assert.Single(metadata, m => m.PropertyName == "Property2"); + } + + [Fact] + public void GetMetadataForProperties_IncludesAllProperties_ExceptIndexer() + { + // Arrange + var provider = CreateProvider(); + + // Act + var metadata = provider.GetMetadataForProperties(typeof(ModelTypeWithIndexer)).ToArray(); + + // Assert + Assert.Equal(1, metadata.Length); + Assert.Single(metadata, m => m.PropertyName == "Property1"); + } + + [Fact] + public void GetMetadataForProperties_Cached() + { + // Arrange + var provider = CreateProvider(); + + // Act + var metadata1 = provider.GetMetadataForProperties(typeof(ModelType)).Cast().ToArray(); + var metadata2 = provider.GetMetadataForProperties(typeof(ModelType)).Cast().ToArray(); + + // Assert + for (var i = 0; i < metadata1.Length; i++) + { + Assert.Same(metadata1[i].Attributes, metadata2[i].Attributes); + Assert.Same(metadata1[i].BindingMetadata, metadata2[i].BindingMetadata); + Assert.Same(metadata1[i].DisplayMetadata, metadata2[i].DisplayMetadata); + Assert.Same(metadata1[i].ValidationMetadata, metadata2[i].ValidationMetadata); + } + } + + [Fact] + public void GetMetadataForProperties_IncludesMergedAttributes() + { + // Arrange + var provider = CreateProvider(); + + // Act + var metadata = provider.GetMetadataForProperties(typeof(ModelType)).First(); + + // Assert + var defaultMetadata = Assert.IsType(metadata); + + var attributes = defaultMetadata.Attributes.ToArray(); + Assert.Equal("OnProperty", Assert.IsType(attributes[0]).Value); + Assert.Equal("OnPropertyType", Assert.IsType(attributes[1]).Value); + } + + [Fact] + public void GetMetadataForParameter_IncludesMergedAttributes() + { + // Arrange + var provider = CreateProvider(); + + var methodInfo = GetType().GetMethod( + "GetMetadataForParameterTestMethod", + BindingFlags.Instance | BindingFlags.NonPublic); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "parameter").Single(); + + var additionalAttributes = new object[] + { + new ModelAttribute("Extra"), + }; + + // Act + var metadata = provider.GetMetadataForParameter(parameterInfo, additionalAttributes); + + // Assert + var defaultMetadata = Assert.IsType(metadata); + + var attributes = defaultMetadata.Attributes.ToArray(); + Assert.Equal("Extra", Assert.IsType(attributes[0]).Value); + Assert.Equal("OnParameter", Assert.IsType(attributes[1]).Value); + Assert.Equal("OnType", Assert.IsType(attributes[2]).Value); + } + + // The 'attributes' are assumed to be the same every time. We can safely omit them after + // the first call. + [Fact] + public void GetMetadataForParameter_Cached() + { + // Arrange + var provider = CreateProvider(); + + var methodInfo = GetType().GetMethod( + "GetMetadataForParameterTestMethod", + BindingFlags.Instance | BindingFlags.NonPublic); + + var additionalAttributes = new object[] + { + new ModelAttribute("Extra"), + }; + + // Act + var metadata1 = Assert.IsType(provider.GetMetadataForParameter( + methodInfo.GetParameters().Where(p => p.Name == "parameter").Single(), + additionalAttributes)); + var metadata2 = Assert.IsType(provider.GetMetadataForParameter( + methodInfo.GetParameters().Where(p => p.Name == "parameter").Single(), + attributes: null)); + + // Assert + Assert.Same(metadata1.Attributes, metadata2.Attributes); + Assert.Same(metadata1.BindingMetadata, metadata2.BindingMetadata); + Assert.Same(metadata1.DisplayMetadata, metadata2.DisplayMetadata); + Assert.Same(metadata1.ValidationMetadata, metadata2.ValidationMetadata); + } + + private static DefaultModelMetadataProvider CreateProvider() + { + return new DefaultModelMetadataProvider(new EmptyCompositeMetadataDetailsProvider()); + } + + [Model("OnType")] + private class ModelType + { + [Model("OnProperty")] + public PropertyType Property1 { get; } + + public PropertyType Property2 { get; set; } + } + + [Model("OnPropertyType")] + private class PropertyType + { + } + + private class ModelAttribute : Attribute + { + public ModelAttribute(string value) + { + Value = value; + } + + public string Value { get; } + } + + private class ModelTypeWithIndexer + { + public PropertyType this[string key] { get { return null; } } + + public PropertyType Property1 { get; set; } + } + + private void GetMetadataForParameterTestMethod([Model("OnParameter")] ModelType parameter) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataTest.cs new file mode 100644 index 0000000000..3193ce5cce --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataTest.cs @@ -0,0 +1,438 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +#if !ASPNETCORE50 +using Moq; +#endif +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + public class DefaultModelMetadataTest + { + [Fact] + public void DefaultValues() + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new DefaultCompositeMetadataDetailsProvider( + Enumerable.Empty()); + + var key = ModelMetadataIdentity.ForType(typeof(string)); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + // Act + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Assert + Assert.NotNull(metadata.AdditionalValues); + Assert.Empty(metadata.AdditionalValues); + Assert.Equal(typeof(string), metadata.ModelType); + + Assert.True(metadata.ConvertEmptyStringToNull); + Assert.False(metadata.HasNonDefaultEditFormat); + Assert.False(metadata.HideSurroundingHtml); + Assert.True(metadata.HtmlEncode); + Assert.False(metadata.IsComplexType); + Assert.False(metadata.IsCollectionType); + Assert.False(metadata.IsNullableValueType); + Assert.False(metadata.IsReadOnly); + Assert.False(metadata.IsRequired); + Assert.True(metadata.ShowForDisplay); + Assert.True(metadata.ShowForEdit); + + Assert.Null(metadata.DataTypeName); + Assert.Null(metadata.Description); + Assert.Null(metadata.DisplayFormatString); + Assert.Null(metadata.DisplayName); + Assert.Null(metadata.EditFormatString); + Assert.Null(metadata.NullDisplayText); + Assert.Null(metadata.TemplateHint); + Assert.Null(metadata.SimpleDisplayProperty); + + Assert.Equal(10000, ModelMetadata.DefaultOrder); + Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order); + + Assert.Null(metadata.BinderModelName); + Assert.Null(metadata.BinderType); + Assert.Null(metadata.BindingSource); + Assert.Null(metadata.PropertyBindingPredicateProvider); + } + + [Fact] + public void CreateMetadataForType() + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new DefaultCompositeMetadataDetailsProvider( + Enumerable.Empty()); + + var key = ModelMetadataIdentity.ForType(typeof(Exception)); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + // Act + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Assert + Assert.Equal(typeof(Exception), metadata.ModelType); + Assert.Null(metadata.PropertyName); + Assert.Null(metadata.ContainerType); + } + + [Fact] + public void CreateMetadataForProperty() + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForProperty(typeof(string), "Message", typeof(Exception)); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + // Act + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Assert + Assert.Equal(typeof(string), metadata.ModelType); + Assert.Equal("Message", metadata.PropertyName); + Assert.Equal(typeof(Exception), metadata.ContainerType); + } + + [Fact] + public void CreateMetadataForParameter() + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var methodInfo = GetType().GetMethod( + "ActionMethod", + BindingFlags.Instance | BindingFlags.NonPublic); + + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "input").Single(); + + var key = ModelMetadataIdentity.ForParameter(parameterInfo); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + // Act + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + Assert.Equal(typeof(string), metadata.ModelType); + Assert.Equal("input", metadata.PropertyName); + Assert.Null(metadata.ContainerType); + } + + [Theory] + [InlineData(typeof(string))] + [InlineData(typeof(IDisposable))] + [InlineData(typeof(Nullable))] + public void IsRequired_ReturnsFalse_ForNullableTypes(Type modelType) + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForType(modelType); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Act + var isRequired = metadata.IsRequired; + + // Assert + Assert.False(isRequired); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DayOfWeek))] + public void IsRequired_ReturnsTrue_ForNonNullableTypes(Type modelType) + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForType(modelType); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Act + var isRequired = metadata.IsRequired; + + // Assert + Assert.True(isRequired); + } + +#if !ASPNETCORE50 + [Fact] + public void PropertiesProperty_CallsProvider() + { + // Arrange + var provider = new Mock(MockBehavior.Strict); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var expectedProperties = new DefaultModelMetadata[] + { + new DefaultModelMetadata( + provider.Object, + detailsProvider, + new DefaultMetadataDetailsCache( + ModelMetadataIdentity.ForProperty(typeof(int), "Prop1", typeof(string)), + attributes: null)), + new DefaultModelMetadata( + provider.Object, + detailsProvider, + new DefaultMetadataDetailsCache( + ModelMetadataIdentity.ForProperty(typeof(int), "Prop2", typeof(string)), + attributes: null)), + }; + + provider + .Setup(p => p.GetMetadataForProperties(typeof(string))) + .Returns(expectedProperties); + + var key = ModelMetadataIdentity.ForType(typeof(string)); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache); + + // Act + var properties = metadata.Properties; + + // Assert + Assert.Equal(expectedProperties.Length, properties.Count); + + for (var i = 0; i < expectedProperties.Length; i++) + { + Assert.Same(expectedProperties[i], properties[i]); + } + } + + // Input (original) property names and expected (ordered) property names. + public static TheoryData, IEnumerable> PropertyNamesTheoryData + { + get + { + // ModelMetadata does not reorder properties the provider returns without an Order override. + return new TheoryData, IEnumerable> + { + { + new List { "Property1", "Property2", "Property3", "Property4", }, + new List { "Property1", "Property2", "Property3", "Property4", } + }, + { + new List { "Property4", "Property3", "Property2", "Property1", }, + new List { "Property4", "Property3", "Property2", "Property1", } + }, + { + new List { "Delta", "Bravo", "Charlie", "Alpha", }, + new List { "Delta", "Bravo", "Charlie", "Alpha", } + }, + { + new List { "John", "Jonathan", "Jon", "Joan", }, + new List { "John", "Jonathan", "Jon", "Joan", } + }, + }; + } + } + + [Theory] + [MemberData(nameof(PropertyNamesTheoryData))] + public void PropertiesProperty_WithDefaultOrder_OrdersPropertyNamesAsProvided( + IEnumerable originalNames, + IEnumerable expectedNames) + { + // Arrange + var provider = new Mock(MockBehavior.Strict); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var expectedProperties = new List(); + foreach (var originalName in originalNames) + { + expectedProperties.Add(new DefaultModelMetadata( + provider.Object, + detailsProvider, + new DefaultMetadataDetailsCache( + ModelMetadataIdentity.ForProperty(typeof(int), originalName, typeof(string)), + attributes: null))); + } + + provider + .Setup(p => p.GetMetadataForProperties(typeof(string))) + .Returns(expectedProperties); + + var key = ModelMetadataIdentity.ForType(typeof(string)); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache); + + // Act + var properties = metadata.Properties; + + // Assert + Assert.Equal(expectedNames.Count(), properties.Count); + Assert.Equal(expectedNames.ToArray(), properties.Select(p => p.PropertyName).ToArray()); + } + + // Input (original) property names, Order values, and expected (ordered) property names. + public static TheoryData>, IEnumerable> + PropertyNamesAndOrdersTheoryData + { + get + { + return new TheoryData>, IEnumerable> + { + { + new List> + { + new KeyValuePair("Property1", 23), + new KeyValuePair("Property2", 23), + new KeyValuePair("Property3", 23), + new KeyValuePair("Property4", 23), + }, + new List { "Property1", "Property2", "Property3", "Property4", } + }, + // Same order if already ordered using Order. + { + new List> + { + new KeyValuePair("Property4", 23), + new KeyValuePair("Property3", 24), + new KeyValuePair("Property2", 25), + new KeyValuePair("Property1", 26), + }, + new List { "Property4", "Property3", "Property2", "Property1", } + }, + // Rest of the orderings get updated within ModelMetadata. + { + new List> + { + new KeyValuePair("Property1", 26), + new KeyValuePair("Property2", 25), + new KeyValuePair("Property3", 24), + new KeyValuePair("Property4", 23), + }, + new List { "Property4", "Property3", "Property2", "Property1", } + }, + { + new List> + { + new KeyValuePair("Alpha", 26), + new KeyValuePair("Bravo", 24), + new KeyValuePair("Charlie", 23), + new KeyValuePair("Delta", 25), + }, + new List { "Charlie", "Bravo", "Delta", "Alpha", } + }, + // Jonathan and Jon will not be reordered. + { + new List> + { + new KeyValuePair("Joan", 1), + new KeyValuePair("Jonathan", 0), + new KeyValuePair("Jon", 0), + new KeyValuePair("John", -1), + }, + new List { "John", "Jonathan", "Jon", "Joan", } + }, + }; + } + } + + [Theory] + [MemberData(nameof(PropertyNamesAndOrdersTheoryData))] + public void PropertiesProperty_OrdersPropertyNamesUsingOrder_ThenAsProvided( + IEnumerable> originalNamesAndOrders, + IEnumerable expectedNames) + { + // Arrange + var provider = new Mock(MockBehavior.Strict); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var expectedProperties = new List(); + foreach (var kvp in originalNamesAndOrders) + { + var propertyCache = new DefaultMetadataDetailsCache( + ModelMetadataIdentity.ForProperty(typeof(int), kvp.Key, typeof(string)), + attributes: null); + + propertyCache.DisplayMetadata = new DisplayMetadata(); + propertyCache.DisplayMetadata.Order = kvp.Value; + + expectedProperties.Add(new DefaultModelMetadata( + provider.Object, + detailsProvider, + propertyCache)); + } + + provider + .Setup(p => p.GetMetadataForProperties(typeof(string))) + .Returns(expectedProperties); + + var key = ModelMetadataIdentity.ForType(typeof(string)); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache); + + // Act + var properties = metadata.Properties; + + // Assert + Assert.Equal(expectedNames.Count(), properties.Count); + Assert.Equal(expectedNames.ToArray(), properties.Select(p => p.PropertyName).ToArray()); + } +#endif + + [Fact] + public void PropertiesSetOnce() + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForType(typeof(string)); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Act + var firstPropertiesEvaluation = metadata.Properties; + var secondPropertiesEvaluation = metadata.Properties; + + // Assert + // Same IEnumerable object. + Assert.Same(firstPropertiesEvaluation, secondPropertiesEvaluation); + } + + [Fact] + public void PropertiesEnumerationEvaluatedOnce() + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForType(typeof(string)); + var cache = new DefaultMetadataDetailsCache(key, new object[0]); + + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Act + var firstPropertiesEvaluation = metadata.Properties.ToList(); + var secondPropertiesEvaluation = metadata.Properties.ToList(); + + // Assert + // Identical ModelMetadata objects every time we run through the Properties collection. + Assert.Equal(firstPropertiesEvaluation, secondPropertiesEvaluation); + } + + private void ActionMethod(string input) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/EmptyCompositeMetadataDetailsProvider.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/EmptyCompositeMetadataDetailsProvider.cs new file mode 100644 index 0000000000..e8054d87f7 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/EmptyCompositeMetadataDetailsProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + public class EmptyCompositeMetadataDetailsProvider : DefaultCompositeMetadataDetailsProvider + { + public EmptyCompositeMetadataDetailsProvider() + : base(new IMetadataDetailsProvider[0]) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs new file mode 100644 index 0000000000..72bbba3bd5 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs @@ -0,0 +1,868 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using Microsoft.Framework.Internal; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + // Integration tests for the default provider configuration. + public class ModelMetadataProviderTest + { + [Fact] + public void ModelMetadataProvider_UsesPredicateOnType() + { + // Arrange + var type = typeof(User); + + var provider = CreateProvider(); + var context = new ModelBindingContext(); + + var expected = new[] { "IsAdmin", "UserName" }; + + // Act + var metadata = provider.GetMetadataForType(type); + + // Assert + var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter; + + var matched = new HashSet(); + foreach (var property in metadata.Properties) + { + if (predicate(context, property.PropertyName)) + { + matched.Add(property.PropertyName); + } + } + + Assert.Equal(expected, matched); + } + + [Fact] + public void ModelMetadataProvider_UsesPredicateOnParameter() + { + // Arrange + var type = GetType(); + var methodInfo = type.GetMethod( + "ActionWithoutBindAttribute", + BindingFlags.Instance | BindingFlags.NonPublic); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "param").Single(); + + var provider = CreateProvider(); + var context = new ModelBindingContext(); + + // Note it does an intersection for included -- only properties that + // pass both predicates will be bound. + var expected = new[] { "IsAdmin", "UserName" }; + + // Act + var metadata = provider.GetMetadataForParameter( + parameterInfo, + attributes: null); + + // Assert + var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter; + Assert.NotNull(predicate); + + var matched = new HashSet(); + foreach (var property in metadata.Properties) + { + if (predicate(context, property.PropertyName)) + { + matched.Add(property.PropertyName); + } + } + + Assert.Equal(expected, matched); + } + + [Fact] + public void ModelMetadataProvider_UsesPredicateOnParameter_Merge() + { + // Arrange + var type = GetType(); + var methodInfo = type.GetMethod( + "ActionWithBindAttribute", + BindingFlags.Instance | BindingFlags.NonPublic); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "param").Single(); + + var provider = CreateProvider(); + var context = new ModelBindingContext(); + + // Note it does an intersection for included -- only properties that + // pass both predicates will be bound. + var expected = new[] { "IsAdmin" }; + + // Act + var metadata = provider.GetMetadataForParameter( + parameterInfo, + attributes: null); + + // Assert + var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter; + Assert.NotNull(predicate); + + var matched = new HashSet(); + foreach (var property in metadata.Properties) + { + if (predicate(context, property.PropertyName)) + { + matched.Add(property.PropertyName); + } + } + + Assert.Equal(expected, matched); + } + + [Fact] + public void ModelMetadataProvider_ReadsModelNameProperty_ForParameters() + { + // Arrange + var type = GetType(); + var methodInfo = type.GetMethod( + "ActionWithBindAttribute", + BindingFlags.Instance | BindingFlags.NonPublic); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "param").Single(); + + var provider = CreateProvider(); + + // Act + var metadata = provider.GetMetadataForParameter( + parameterInfo, + attributes: null); + + // Assert + Assert.Equal("ParameterPrefix", metadata.BinderModelName); + } + + [Fact] + public void ModelMetadataProvider_ReadsModelNameProperty_ForTypes() + { + // Arrange + var type = typeof(User); + var provider = CreateProvider(); + + // Act + var metadata = provider.GetMetadataForType(type); + + // Assert + Assert.Equal("TypePrefix", metadata.BinderModelName); + } + + + [Fact] + public void ModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForDisplay() + { + // Arrange + var type = typeof(ScaffoldColumnModel); + var provider = CreateProvider(); + + // Act & Assert + Assert.True(provider.GetMetadataForProperty(type, "NoAttribute").ShowForDisplay); + Assert.True(provider.GetMetadataForProperty(type, "ScaffoldColumnTrue").ShowForDisplay); + Assert.False(provider.GetMetadataForProperty(type, "ScaffoldColumnFalse").ShowForDisplay); + } + + [Fact] + public void ModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForEdit() + { + // Arrange + var type = typeof(ScaffoldColumnModel); + var provider = CreateProvider(); + + // Act & Assert + Assert.True(provider.GetMetadataForProperty(type, "NoAttribute").ShowForEdit); + Assert.True(provider.GetMetadataForProperty(type, "ScaffoldColumnTrue").ShowForEdit); + Assert.False(provider.GetMetadataForProperty(type, "ScaffoldColumnFalse").ShowForEdit); + } + + [Fact] + public void HiddenInputWorksOnProperty_ForHideSurroundingHtml() + { + // Arrange + var provider = CreateProvider(); + var metadata = provider.GetMetadataForType(modelType: typeof(ClassWithHiddenProperties)); + var property = metadata.Properties["DirectlyHidden"]; + + // Act + var result = property.HideSurroundingHtml; + + // Assert + Assert.True(result); + } + + [Fact] + public void HiddenInputWorksOnPropertyType_ForHideSurroundingHtml() + { + // Arrange + var provider = CreateProvider(); + var metadata = provider.GetMetadataForType(typeof(ClassWithHiddenProperties)); + var property = metadata.Properties["OfHiddenType"]; + + // Act + var result = property.HideSurroundingHtml; + + // Assert + Assert.True(result); + } + + [Fact] + public void HiddenInputWorksOnProperty_ForTemplateHint() + { + // Arrange + var provider = CreateProvider(); + var metadata = provider.GetMetadataForType(typeof(ClassWithHiddenProperties)); + var property = metadata.Properties["DirectlyHidden"]; + + // Act + var result = property.TemplateHint; + + // Assert + Assert.Equal("HiddenInput", result); + } + + [Fact] + public void HiddenInputWorksOnPropertyType_ForTemplateHint() + { + // Arrange + var provider = CreateProvider(); + var metadata = provider.GetMetadataForType(typeof(ClassWithHiddenProperties)); + var property = metadata.Properties["OfHiddenType"]; + + // Act + var result = property.TemplateHint; + + // Assert + Assert.Equal("HiddenInput", result); + } + + [Fact] + public void GetMetadataForProperty_WithNoBinderModelName_GetsItFromType() + { + // Arrange + var provider = CreateProvider(); + + // Act + var propertyMetadata = provider.GetMetadataForProperty(typeof(Person), nameof(Person.Parent)); + + // Assert + Assert.Equal("PersonType", propertyMetadata.BinderModelName); + } + + [Fact] + public void GetMetadataForProperty_WithBinderMetadataOnPropertyAndType_GetsMetadataFromProperty() + { + // Arrange + var provider = CreateProvider(); + + // Act + var propertyMetadata = provider.GetMetadataForProperty(typeof(Person), nameof(Person.GrandParent)); + + // Assert + Assert.Equal("GrandParentProperty", propertyMetadata.BinderModelName); + } + + [Fact] + public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType() + { + // Arrange + var provider = CreateProvider(); + + var methodInfo = typeof(Person).GetMethod("Update"); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "person").Single(); + + // Act + var parameterMetadata = provider.GetMetadataForParameter( + parameterInfo, + attributes: null); + + // Assert + Assert.Equal("PersonType", parameterMetadata.BinderModelName); + } + + [Fact] + public void GetMetadataForParameter_WithBinderDataOnParameterAndType_GetsMetadataFromParameter() + { + // Arrange + var provider = CreateProvider(); + + var methodInfo = typeof(Person).GetMethod("Save"); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "person").Single(); + + // Act + var parameterMetadata = provider.GetMetadataForParameter( + parameterInfo, + attributes: null); + + // Assert + Assert.Equal("PersonParameter", parameterMetadata.BinderModelName); + } + + [Fact] + public void GetMetadataForParameter_BinderModelNameOverride() + { + // Arrange + var provider = CreateProvider(); + + var methodInfo = typeof(Person).GetMethod("Save"); + var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "person").Single(); + + // Act + var parameterMetadata = provider.GetMetadataForParameter( + parameterInfo, + attributes: new object[] { new ModelBinderAttribute() { Name = "Override" } }); + + // Assert + Assert.Equal("Override", parameterMetadata.BinderModelName); + } + + public static TheoryData> ExpectedAttributeDataStrings + { + get + { + return new TheoryData> + { + { + new DataTypeAttribute("value"), metadata => metadata.DataTypeName + }, + { + new DataTypeWithCustomDisplayFormat(), metadata => metadata.DisplayFormatString + }, + { + new DataTypeWithCustomEditFormat(), metadata => metadata.EditFormatString + }, + { + new DisplayAttribute { Description = "value" }, metadata => metadata.Description + }, + { + new DisplayAttribute { Name = "value" }, metadata => metadata.DisplayName + }, + { + new DisplayFormatAttribute { DataFormatString = "value" }, + metadata => metadata.DisplayFormatString + }, + { + // DisplayFormatString does not ignore [DisplayFormat] if ApplyFormatInEditMode==true. + new DisplayFormatAttribute { ApplyFormatInEditMode = true, DataFormatString = "value" }, + metadata => metadata.DisplayFormatString + }, + { + new DisplayFormatAttribute { ApplyFormatInEditMode = true, DataFormatString = "value" }, + metadata => metadata.EditFormatString + }, + { + new DisplayFormatAttribute { NullDisplayText = "value" }, metadata => metadata.NullDisplayText + }, + { + new TestModelNameProvider { Name = "value" }, metadata => metadata.BinderModelName + }, + { + new UIHintAttribute("value"), metadata => metadata.TemplateHint + }, + }; + } + } + + [Theory] + [MemberData(nameof(ExpectedAttributeDataStrings))] + public void AttributesOverrideMetadataStrings(object attribute, Func accessor) + { + // Arrange + var attributes = new[] { attribute }; + var provider = CreateProvider(attributes); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = accessor(metadata); + + // Assert + Assert.Equal("value", result); + } + + [Fact] + public void AttributesOverrideMetadataStrings_SimpleDisplayProperty() + { + // Arrange + var attributes = new[] { new DisplayColumnAttribute("Property") }; + var provider = CreateProvider(attributes); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = metadata.SimpleDisplayProperty; + + // Assert + Assert.Equal("Property", result); + } + + public static TheoryData, bool> ExpectedAttributeDataBooleans + { + get + { + return new TheoryData, bool> + { + { + // Edit formats from [DataType] subclass affect HasNonDefaultEditFormat. + new DataTypeWithCustomEditFormat(), + metadata => metadata.HasNonDefaultEditFormat, + true + }, + { + // Edit formats from [DataType] do not affect HasNonDefaultEditFormat. + new DataTypeAttribute(DataType.Date), + metadata => metadata.HasNonDefaultEditFormat, + false + }, + { + new DisplayFormatAttribute { ConvertEmptyStringToNull = false }, + metadata => metadata.ConvertEmptyStringToNull, + false + }, + { + new DisplayFormatAttribute { ConvertEmptyStringToNull = true }, + metadata => metadata.ConvertEmptyStringToNull, + true + }, + { + // Changes only to DisplayFormatString do not affect HasNonDefaultEditFormat. + new DisplayFormatAttribute { DataFormatString = "value" }, + metadata => metadata.HasNonDefaultEditFormat, + false + }, + { + new DisplayFormatAttribute { ApplyFormatInEditMode = true, DataFormatString = "value" }, + metadata => metadata.HasNonDefaultEditFormat, + true + }, + { + new DisplayFormatAttribute { HtmlEncode = false }, + metadata => metadata.HtmlEncode, + false + }, + { + new DisplayFormatAttribute { HtmlEncode = true }, + metadata => metadata.HtmlEncode, + true + }, + { + new EditableAttribute(allowEdit: false), + metadata => metadata.IsReadOnly, + true + }, + { + new EditableAttribute(allowEdit: true), + metadata => metadata.IsReadOnly, + false + }, + { + new HiddenInputAttribute { DisplayValue = false }, + metadata => metadata.HideSurroundingHtml, + true + }, + { + new HiddenInputAttribute { DisplayValue = true }, + metadata => metadata.HideSurroundingHtml, + false + }, + { + new HiddenInputAttribute(), + metadata => string.Equals("HiddenInput", metadata.TemplateHint, StringComparison.Ordinal), + true + }, + { + new RequiredAttribute(), + metadata => metadata.IsRequired, + true + }, + }; + } + } + + [Theory] + [MemberData(nameof(ExpectedAttributeDataBooleans))] + public void AttributesOverrideMetadataBooleans( + Attribute attribute, + Func accessor, + bool expectedResult) + { + // Arrange + var attributes = new[] { attribute }; + var provider = CreateProvider(attributes); + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = accessor(metadata); + + // Assert + Assert.Equal(expectedResult, result); + } + + public static TheoryData DisplayAttribute_OverridesOrderData + { + get + { + return new TheoryData + { + { + new DisplayAttribute(), ModelMetadata.DefaultOrder + }, + { + new DisplayAttribute { Order = int.MinValue }, int.MinValue + }, + { + new DisplayAttribute { Order = -100 }, -100 + }, + { + new DisplayAttribute { Order = -1 }, -1 + }, + { + new DisplayAttribute { Order = 0 }, 0 + }, + { + new DisplayAttribute { Order = 1 }, 1 + }, + { + new DisplayAttribute { Order = 200 }, 200 + }, + { + new DisplayAttribute { Order = int.MaxValue }, int.MaxValue + }, + }; + } + } + + [Theory] + [MemberData(nameof(DisplayAttribute_OverridesOrderData))] + public void DisplayAttribute_OverridesOrder(DisplayAttribute attribute, int expectedOrder) + { + // Arrange + var attributes = new[] { attribute }; + var provider = CreateProvider(attributes); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = metadata.Order; + + // Assert + Assert.Equal(expectedOrder, result); + } + + [Fact] + public void DisplayAttribute_Description() + { + // Arrange + var display = new DisplayAttribute() { Description = "description" }; + var provider = CreateProvider(new[] { display }); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = metadata.Description; + + // Assert + Assert.Equal("description", result); + } + + [Fact] + public void DataTypeName_Null_IfHtmlEncodeTrue() + { + // Arrange + var displayFormat = new DisplayFormatAttribute { HtmlEncode = true, }; + var provider = CreateProvider(new[] { displayFormat }); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = metadata.DataTypeName; + + // Assert + Assert.Null(result); + } + + [Fact] + public void DataTypeName_Html_IfHtmlEncodeFalse() + { + // Arrange + var expected = "Html"; + var displayFormat = new DisplayFormatAttribute { HtmlEncode = false, }; + var provider = CreateProvider(new[] { displayFormat }); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = metadata.DataTypeName; + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void DataTypeName_AttributesHaveExpectedPrecedence() + { + // Arrange + var expected = "MultilineText"; + var dataType = new DataTypeAttribute(DataType.MultilineText); + var displayFormat = new DisplayFormatAttribute { HtmlEncode = false, }; + var provider = CreateProvider(new object[] { dataType, displayFormat }); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = metadata.DataTypeName; + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void DisplayFormatString_AttributesHaveExpectedPrecedence() + { + // Arrange + var expected = "custom format"; + var dataType = new DataTypeAttribute(DataType.Currency); + var displayFormat = new DisplayFormatAttribute { DataFormatString = expected, }; + var provider = CreateProvider(new object[] { displayFormat, dataType, }); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = metadata.DisplayFormatString; + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void EditFormatString_AttributesHaveExpectedPrecedence() + { + // Arrange + var expected = "custom format"; + var dataType = new DataTypeAttribute(DataType.Currency); + var displayFormat = new DisplayFormatAttribute + { + ApplyFormatInEditMode = true, + DataFormatString = expected, + }; + + var provider = CreateProvider(new object[] { displayFormat, dataType, }); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = metadata.EditFormatString; + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void TemplateHint_AttributesHaveExpectedPrecedence() + { + // Arrange + var expected = "this is a hint"; + var hidden = new HiddenInputAttribute(); + var uiHint = new UIHintAttribute(expected); + var provider = CreateProvider(new object[] { hidden, uiHint, }); + + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act + var result = metadata.TemplateHint; + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void BinderTypeProviders_Null() + { + // Arrange + var binderProviders = new[] + { + new TestBinderTypeProvider(), + new TestBinderTypeProvider(), + }; + + var provider = CreateProvider(binderProviders); + + // Act + var metadata = provider.GetMetadataForType(typeof(string)); + + // Assert + Assert.Null(metadata.BinderType); + } + + [Fact] + public void BinderTypeProviders_Fallback() + { + // Arrange + var attributes = new[] + { + new TestBinderTypeProvider(), + new TestBinderTypeProvider() { BinderType = typeof(string) } + }; + + var provider = CreateProvider(attributes); + + // Act + var metadata = provider.GetMetadataForType(typeof(string)); + + // Assert + Assert.Same(typeof(string), metadata.BinderType); + } + + [Fact] + public void BinderTypeProviders_FirstAttributeHasPrecedence() + { + // Arrange + var attributes = new[] + { + new TestBinderTypeProvider() { BinderType = typeof(int) }, + new TestBinderTypeProvider() { BinderType = typeof(string) } + }; + + var provider = CreateProvider(attributes); + + // Act + var metadata = provider.GetMetadataForType(typeof(string)); + + // Assert + Assert.Same(typeof(int), metadata.BinderType); + } + + private IModelMetadataProvider CreateProvider(params object[] attributes) + { + return new AttributeInjectModelMetadataProvider(attributes); + } + + private class TestBinderTypeProvider : IBinderTypeProviderMetadata + { + public Type BinderType { get; set; } + + public BindingSource BindingSource { get; set; } + } + + private class DataTypeWithCustomDisplayFormat : DataTypeAttribute + { + public DataTypeWithCustomDisplayFormat() : base("Custom datatype") + { + DisplayFormat = new DisplayFormatAttribute + { + DataFormatString = "value", + }; + } + } + + private class DataTypeWithCustomEditFormat : DataTypeAttribute + { + public DataTypeWithCustomEditFormat() : base("Custom datatype") + { + DisplayFormat = new DisplayFormatAttribute + { + ApplyFormatInEditMode = true, + DataFormatString = "value", + }; + } + } + + private void ActionWithoutBindAttribute(User param) + { + } + + private void ActionWithBindAttribute([Bind(new string[] { "IsAdmin" }, Prefix = "ParameterPrefix")] User param) + { + } + + public class TypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider + { + public string Name { get; set; } + } + + public class NonTypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider + { + public string Name { get; set; } + } + + [TypeBasedBinder(Name = "PersonType")] + public class Person + { + public Person Parent { get; set; } + + [NonTypeBasedBinder(Name = "GrandParentProperty")] + public Person GrandParent { get; set; } + + public void Update(Person person) + { + } + + public void Save([NonTypeBasedBinder(Name = "PersonParameter")] Person person) + { + } + } + + private class ScaffoldColumnModel + { + public int NoAttribute { get; set; } + + [ScaffoldColumn(scaffold: true)] + public int ScaffoldColumnTrue { get; set; } + + [ScaffoldColumn(scaffold: false)] + public int ScaffoldColumnFalse { get; set; } + } + + [HiddenInput(DisplayValue = false)] + private class HiddenClass + { + public string Property { get; set; } + } + + private class ClassWithHiddenProperties + { + [HiddenInput(DisplayValue = false)] + public string DirectlyHidden { get; set; } + + public HiddenClass OfHiddenType { get; set; } + } + + [Bind(new[] { nameof(IsAdmin), nameof(UserName) }, Prefix = "TypePrefix")] + private class User + { + public int Id { get; set; } + + public bool IsAdmin { get; set; } + + public int UserName { get; set; } + + public int NotIncludedOrExcluded { get; set; } + } + + private class AttributeInjectModelMetadataProvider : DefaultModelMetadataProvider + { + private readonly object[] _attributes; + + public AttributeInjectModelMetadataProvider(object[] attributes) + : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[] + { + new DefaultBindingMetadataProvider(), + new DataAnnotationsMetadataDetailsProvider(), + })) + { + _attributes = attributes; + } + + protected override DefaultMetadataDetailsCache CreateTypeCacheEntry(ModelMetadataIdentity key) + { + var entry = base.CreateTypeCacheEntry(key); + return new DefaultMetadataDetailsCache(key, _attributes.Concat(entry.Attributes).ToArray()); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs index e07800d2d1..0c4b72aee3 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs @@ -5,162 +5,14 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; -using System.Reflection; -using Microsoft.Framework.Internal; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding { public class ModelMetadataTest { - public static TheoryData, Func, object> MetadataModifierData - { - get - { - var emptycontainerModel = new DummyModelContainer(); - var contactModel = new DummyContactModel { FirstName = "test" }; - var nonEmptycontainerModel = new DummyModelContainer { Model = contactModel }; - - var binderMetadata = new TestBinderMetadata(); - var predicateProvider = new DummyPropertyBindingPredicateProvider(); - - return new TheoryData, Func, object> - { - { m => m.ConvertEmptyStringToNull = false, m => m.ConvertEmptyStringToNull, false }, - { m => m.HasNonDefaultEditFormat = true, m => m.HasNonDefaultEditFormat, true }, - { m => m.HideSurroundingHtml = true, m => m.HideSurroundingHtml, true }, - { m => m.HtmlEncode = false, m => m.HtmlEncode, false }, - { m => m.IsReadOnly = true, m => m.IsReadOnly, true }, - { m => m.IsRequired = true, m => m.IsRequired, true }, - { m => m.ShowForDisplay = false, m => m.ShowForDisplay, false }, - { m => m.ShowForEdit = false, m => m.ShowForEdit, false }, - - { m => m.DataTypeName = "New data type name", m => m.DataTypeName, "New data type name" }, - { m => m.Description = "New description", m => m.Description, "New description" }, - { m => m.DisplayFormatString = "New display format", m => m.DisplayFormatString, "New display format" }, - { m => m.DisplayName = "New display name", m => m.DisplayName, "New display name" }, - { m => m.EditFormatString = "New edit format", m => m.EditFormatString, "New edit format" }, - { m => m.NullDisplayText = "New null display", m => m.NullDisplayText, "New null display" }, - { m => m.SimpleDisplayProperty = "NewSimpleDisplay", m => m.SimpleDisplayProperty, "NewSimpleDisplay" }, - { m => m.TemplateHint = "New template hint", m => m.TemplateHint, "New template hint" }, - - { m => m.Order = 23, m => m.Order, 23 }, - - { m => m.BinderMetadata = null, m => m.BinderMetadata, null }, - { m => m.BinderMetadata = binderMetadata, m => m.BinderMetadata, binderMetadata }, - { m => m.BinderModelName = null, m => m.BinderModelName, null }, - { m => m.BinderModelName = "newModelName", m => m.BinderModelName, "newModelName" }, - { m => m.BinderModelName = string.Empty, m => m.BinderModelName, string.Empty }, - { m => m.BinderType = null, m => m.BinderType, null }, - { m => m.BinderType = typeof(string), m => m.BinderType, typeof(string) }, - { m => m.PropertyBindingPredicateProvider = null, m => m.PropertyBindingPredicateProvider, null }, - { m => m.PropertyBindingPredicateProvider = predicateProvider, m => m.PropertyBindingPredicateProvider, predicateProvider }, - }; - } - } - - // Constructor - - [Fact] - public void DefaultValues() - { - // Arrange - var provider = new EmptyModelMetadataProvider(); - - // Act - var metadata = new ModelMetadata(provider, typeof(Exception), typeof(string), "propertyName"); - - // Assert - Assert.NotNull(metadata.AdditionalValues); - Assert.Empty(metadata.AdditionalValues); - Assert.Equal(typeof(Exception), metadata.ContainerType); - - Assert.True(metadata.ConvertEmptyStringToNull); - Assert.False(metadata.HasNonDefaultEditFormat); - Assert.False(metadata.HideSurroundingHtml); - Assert.True(metadata.HtmlEncode); - Assert.False(metadata.IsComplexType); - Assert.False(metadata.IsCollectionType); - Assert.False(metadata.IsNullableValueType); - Assert.False(metadata.IsReadOnly); - Assert.False(metadata.IsRequired); - Assert.True(metadata.ShowForDisplay); - Assert.True(metadata.ShowForEdit); - - Assert.Null(metadata.DataTypeName); - Assert.Null(metadata.Description); - Assert.Null(metadata.DisplayFormatString); - Assert.Null(metadata.DisplayName); - Assert.Null(metadata.EditFormatString); - Assert.Null(metadata.NullDisplayText); - Assert.Null(metadata.TemplateHint); - Assert.Null(metadata.SimpleDisplayProperty); - - Assert.Equal(typeof(string), metadata.ModelType); - Assert.Equal("propertyName", metadata.PropertyName); - - Assert.Equal(10000, ModelMetadata.DefaultOrder); - Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order); - - Assert.Null(metadata.BinderModelName); - Assert.Null(metadata.BinderType); - Assert.Null(metadata.BinderMetadata); - Assert.Null(metadata.PropertyBindingPredicateProvider); - } - - - // AdditionalValues - - [Fact] - public void AdditionalValues_CreatedOnce() - { - - // Arrange - var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null); - - // Act - var result1 = metadata.AdditionalValues; - var result2 = metadata.AdditionalValues; - - // Assert - Assert.Same(result1, result2); - } - - [Fact] - public void AdditionalValues_ChangesPersist() - { - // Arrange - var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: typeof(object), - propertyName: null); - var valuesDictionary = new Dictionary - { - { "key1", new object() }, - { "key2", "value2" }, - { "key3", new object() }, - }; - - // Act - foreach (var keyValuePair in valuesDictionary) - { - metadata.AdditionalValues.Add(keyValuePair); - } - - // Assert - Assert.Equal(valuesDictionary, metadata.AdditionalValues); - } - // IsComplexType - private struct IsComplexTypeModel { } @@ -175,11 +27,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var provider = new EmptyModelMetadataProvider(); // Act - var modelMetadata = new ModelMetadata( - provider, - containerType: null, - modelType: type, - propertyName: null); + var modelMetadata = new TestModelMetadata(type); // Assert Assert.False(modelMetadata.IsComplexType); @@ -196,11 +44,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var provider = new EmptyModelMetadataProvider(); // Act - var modelMetadata = new ModelMetadata( - provider, - containerType: null, - modelType: type, - propertyName: null); + var modelMetadata = new TestModelMetadata(type); // Assert Assert.True(modelMetadata.IsComplexType); @@ -217,11 +61,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var provider = new EmptyModelMetadataProvider(); // Act - var modelMetadata = new ModelMetadata( - provider, - containerType: null, - modelType: type, - propertyName: null); + var modelMetadata = new TestModelMetadata(type); // Assert Assert.False(modelMetadata.IsCollectionType); @@ -241,11 +81,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var provider = new EmptyModelMetadataProvider(); // Act - var modelMetadata = new ModelMetadata( - provider, - containerType: null, - modelType: type, - propertyName: null); + var modelMetadata = new TestModelMetadata(type); // Assert Assert.True(modelMetadata.IsCollectionType); @@ -261,312 +97,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // IsNullableValueType - [Fact] - public void IsNullableValueTypeTests() + [Theory] + [InlineData(typeof(string), false)] + [InlineData(typeof(IDisposable), false)] + [InlineData(typeof(Nullable), true)] + [InlineData(typeof(int), false)] + public void IsNullableValueTypeTests(Type modelType, bool expected) { // Arrange - var provider = new EmptyModelMetadataProvider(); + var modelMetadata = new TestModelMetadata(modelType); // Act & Assert - Assert.False(new ModelMetadata(provider, null, typeof(string), null).IsNullableValueType); - Assert.False(new ModelMetadata(provider, null, typeof(IDisposable), null).IsNullableValueType); - Assert.True(new ModelMetadata(provider, null, typeof(Nullable), null).IsNullableValueType); - Assert.False(new ModelMetadata(provider, null, typeof(int), null).IsNullableValueType); - } - - // IsRequired - - [Theory] - [InlineData(typeof(string))] - [InlineData(typeof(IDisposable))] - [InlineData(typeof(Nullable))] - public void IsRequired_ReturnsFalse_ForNullableTypes(Type modelType) - { - // Arrange - var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata(provider, - containerType: null, - modelType: modelType, - propertyName: null); - - // Act - var isRequired = metadata.IsRequired; - - // Assert - Assert.False(isRequired); - } - - [Theory] - [InlineData(typeof(int))] - [InlineData(typeof(DayOfWeek))] - public void IsRequired_ReturnsTrue_ForNonNullableTypes(Type modelType) - { - // Arrange - var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata(provider, - containerType: null, - modelType: modelType, - propertyName: null); - - // Act - var isRequired = metadata.IsRequired; - - // Assert - Assert.True(isRequired); - } - - // Properties - - [Fact] - public void PropertiesProperty_CallsProvider() - { - // Arrange - var modelType = typeof(object); - var provider = new PropertiesModelMetadataProvider(new List()); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: modelType, - propertyName: null); - - // Act - var result = metadata.Properties; - - // Assert - Assert.Empty(result); - Assert.Equal(1, provider.GetMetadataForPropertiesCalls); - } - - // Input (original) property names and expected (ordered) property names. - public static TheoryData, IEnumerable> PropertyNamesTheoryData - { - get - { - // ModelMetadata does not reorder properties the provider returns without an Order override. - return new TheoryData, IEnumerable> - { - { - new List { "Property1", "Property2", "Property3", "Property4", }, - new List { "Property1", "Property2", "Property3", "Property4", } - }, - { - new List { "Property4", "Property3", "Property2", "Property1", }, - new List { "Property4", "Property3", "Property2", "Property1", } - }, - { - new List { "Delta", "Bravo", "Charlie", "Alpha", }, - new List { "Delta", "Bravo", "Charlie", "Alpha", } - }, - { - new List { "John", "Jonathan", "Jon", "Joan", }, - new List { "John", "Jonathan", "Jon", "Joan", } - }, - }; - } - } - - [Theory] - [MemberData(nameof(PropertyNamesTheoryData))] - public void PropertiesProperty_WithDefaultOrder_OrdersPropertyNamesAsProvided( - IEnumerable originalNames, - IEnumerable expectedNames) - { - // Arrange - var modelType = typeof(object); - var provider = new PropertiesModelMetadataProvider(originalNames); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: modelType, - propertyName: null); - - // Act - var result = metadata.Properties; - - // Assert - var propertyNames = result.Select(property => property.PropertyName); - Assert.Equal(expectedNames, propertyNames); - } - - // Input (original) property names, Order values, and expected (ordered) property names. - public static TheoryData>, IEnumerable> - PropertyNamesAndOrdersTheoryData - { - get - { - return new TheoryData>, IEnumerable> - { - { - new List> - { - new KeyValuePair("Property1", 23), - new KeyValuePair("Property2", 23), - new KeyValuePair("Property3", 23), - new KeyValuePair("Property4", 23), - }, - new List { "Property1", "Property2", "Property3", "Property4", } - }, - // Same order if already ordered using Order. - { - new List> - { - new KeyValuePair("Property4", 23), - new KeyValuePair("Property3", 24), - new KeyValuePair("Property2", 25), - new KeyValuePair("Property1", 26), - }, - new List { "Property4", "Property3", "Property2", "Property1", } - }, - // Rest of the orderings get updated within ModelMetadata. - { - new List> - { - new KeyValuePair("Property1", 26), - new KeyValuePair("Property2", 25), - new KeyValuePair("Property3", 24), - new KeyValuePair("Property4", 23), - }, - new List { "Property4", "Property3", "Property2", "Property1", } - }, - { - new List> - { - new KeyValuePair("Alpha", 26), - new KeyValuePair("Bravo", 24), - new KeyValuePair("Charlie", 23), - new KeyValuePair("Delta", 25), - }, - new List { "Charlie", "Bravo", "Delta", "Alpha", } - }, - // Jonathan and Jon will not be reordered. - { - new List> - { - new KeyValuePair("Joan", 1), - new KeyValuePair("Jonathan", 0), - new KeyValuePair("Jon", 0), - new KeyValuePair("John", -1), - }, - new List { "John", "Jonathan", "Jon", "Joan", } - }, - }; - } - } - - [Theory] - [MemberData(nameof(PropertyNamesAndOrdersTheoryData))] - public void PropertiesProperty_OrdersPropertyNamesUsingOrder_ThenAsProvided( - IEnumerable> originalNamesAndOrders, - IEnumerable expectedNames) - { - // Arrange - var modelType = typeof(object); - var provider = new PropertiesModelMetadataProvider(originalNamesAndOrders); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: modelType, - propertyName: null); - - // Act - var result = metadata.Properties; - - // Assert - var propertyNames = result.Select(property => property.PropertyName); - Assert.Equal(expectedNames, propertyNames); - } - - [Theory] - [MemberData(nameof(MetadataModifierData))] - public void PropertiesPropertyChangesPersist( - Action setter, - Func getter, - object expected) - { - // Arrange - var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: typeof(Class1), - propertyName: null); - - // Act - foreach (var property in metadata.Properties) - { - setter(property); - } - - // Assert - foreach (var property in metadata.Properties) - { - // Due to boxing of structs, can't Assert.Same(). - Assert.Equal(expected, getter(property)); - } - } - - [Theory] - [MemberData(nameof(MetadataModifierData))] - public void PropertyChangesPersist( - Action setter, - Func getter, - object expected) - { - // Arrange - var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: typeof(Class1), - propertyName: null); - - // Act - setter(metadata); - var result = getter(metadata); - - // Assert - // Due to boxing of structs, can't Assert.Same(). - Assert.Equal(expected, result); - } - - [Fact] - public void PropertiesSetOnce() - { - // Arrange - var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: typeof(Class1), - propertyName: null); - - // Act - var firstPropertiesEvaluation = metadata.Properties; - var secondPropertiesEvaluation = metadata.Properties; - - // Assert - // Same IEnumerable object. - Assert.Same(firstPropertiesEvaluation, secondPropertiesEvaluation); - } - - [Fact] - public void PropertiesEnumerationEvaluatedOnce() - { - // Arrange - var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata( - provider, - containerType: null, - modelType: typeof(Class1), - propertyName: null); - - // Act - var firstPropertiesEvaluation = metadata.Properties.ToList(); - var secondPropertiesEvaluation = metadata.Properties.ToList(); - - // Assert - // Identical ModelMetadata objects every time we run through the Properties collection. - Assert.Equal(firstPropertiesEvaluation, secondPropertiesEvaluation); + Assert.Equal(expected, modelMetadata.IsNullableValueType); } private class Class1 @@ -590,10 +132,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { // Arrange var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata(provider, null, typeof(object), "unusedName") - { - DisplayName = "displayName", - }; + var metadata = new TestModelMetadata(typeof(int), "Length", typeof(string)); + metadata.SetDisplayName("displayName"); // Act var result = metadata.GetDisplayName(); @@ -607,13 +147,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { // Arrange var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata(provider, null, typeof(object), "PropertyName"); + var metadata = new TestModelMetadata(typeof(int), "Length", typeof(string)); // Act var result = metadata.GetDisplayName(); // Assert - Assert.Equal("PropertyName", result); + Assert.Equal("Length", result); } [Fact] @@ -621,109 +161,216 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { // Arrange var provider = new EmptyModelMetadataProvider(); - var metadata = new ModelMetadata(provider, null, typeof(object), null); + var metadata = new TestModelMetadata(typeof(string)); // Act var result = metadata.GetDisplayName(); // Assert - Assert.Equal("Object", result); + Assert.Equal("String", result); } - private class ClassWithNoProperties + private class TestModelMetadata : ModelMetadata { - public override string ToString() + private string _displayName; + + public TestModelMetadata(Type modelType) + : base(ModelMetadataIdentity.ForType(modelType)) { - return null; } - } - private class ComplexClass - { - public Class1 Prop1 { get; set; } - } - - // Helpers - private class DummyContactModel - { - public int IntField = 0; - public string FirstName { get; set; } - public string LastName { get; set; } - public int? NullableIntValue { get; set; } - public int[] Array { get; set; } - - public string this[int index] + public TestModelMetadata(Type modelType, string propertyName, Type containerType) + : base(ModelMetadataIdentity.ForProperty(modelType, propertyName, containerType)) { - get { return "Indexed into " + index; } } - } - private class DummyModelContainer - { - public DummyContactModel Model { get; set; } - } - - private class DummyPropertyBindingPredicateProvider : IPropertyBindingPredicateProvider - { - public Func PropertyFilter { get; set; } - } - - // Gives object type properties with provided names or names and Order values. - private class PropertiesModelMetadataProvider : IModelMetadataProvider - { - private List _properties = new List(); - - public PropertiesModelMetadataProvider(IEnumerable propertyNames) + public override IReadOnlyDictionary AdditionalValues { - foreach (var propertyName in propertyNames) + get { - var metadata = new ModelMetadata( - this, - containerType: typeof(DummyContactModel), - modelType: typeof(string), - propertyName: propertyName); - - _properties.Add(metadata); + throw new NotImplementedException(); } } - public PropertiesModelMetadataProvider(IEnumerable> propertyNamesAndOrders) + public override string BinderModelName { - foreach (var keyValuePair in propertyNamesAndOrders) + get { - var metadata = new ModelMetadata( - this, - containerType: typeof(DummyContactModel), - modelType: typeof(string), - propertyName: keyValuePair.Key) - { - Order = keyValuePair.Value, - }; - - _properties.Add(metadata); + throw new NotImplementedException(); } } - public int GetMetadataForPropertiesCalls { get; private set; } - - public ModelMetadata GetMetadataForParameter( - [NotNull] MethodInfo methodInfo, - [NotNull] string parameterName) + public override Type BinderType { - throw new NotImplementedException(); + get + { + throw new NotImplementedException(); + } } - public IEnumerable GetMetadataForProperties([NotNull] Type containerType) + public override BindingSource BindingSource { - Assert.Equal(typeof(object), containerType); - GetMetadataForPropertiesCalls++; - - return _properties; + get + { + throw new NotImplementedException(); + } } - public ModelMetadata GetMetadataForType([NotNull] Type modelType) + public override bool ConvertEmptyStringToNull { - throw new NotImplementedException(); + get + { + throw new NotImplementedException(); + } + } + + public override string DataTypeName + { + get + { + throw new NotImplementedException(); + } + } + + public override string Description + { + get + { + throw new NotImplementedException(); + } + } + + public override string DisplayFormatString + { + get + { + throw new NotImplementedException(); + } + } + + public override string DisplayName + { + get + { + return _displayName; + } + } + + public void SetDisplayName(string displayName) + { + _displayName = displayName; + } + + public override string EditFormatString + { + get + { + throw new NotImplementedException(); + } + } + + public override bool HasNonDefaultEditFormat + { + get + { + throw new NotImplementedException(); + } + } + + public override bool HideSurroundingHtml + { + get + { + throw new NotImplementedException(); + } + } + + public override bool HtmlEncode + { + get + { + throw new NotImplementedException(); + } + } + + public override bool IsReadOnly + { + get + { + throw new NotImplementedException(); + } + } + + public override bool IsRequired + { + get + { + throw new NotImplementedException(); + } + } + + public override string NullDisplayText + { + get + { + throw new NotImplementedException(); + } + } + + public override int Order + { + get + { + throw new NotImplementedException(); + } + } + + public override ModelPropertyCollection Properties + { + get + { + throw new NotImplementedException(); + } + } + + public override IPropertyBindingPredicateProvider PropertyBindingPredicateProvider + { + get + { + throw new NotImplementedException(); + } + } + + public override bool ShowForDisplay + { + get + { + throw new NotImplementedException(); + } + } + + public override bool ShowForEdit + { + get + { + throw new NotImplementedException(); + } + } + + public override string SimpleDisplayProperty + { + get + { + throw new NotImplementedException(); + } + } + + public override string TemplateHint + { + get + { + throw new NotImplementedException(); + } } } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs index 35e49f5cc1..97e895f9f0 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs @@ -26,6 +26,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test return string.Format(CultureInfo.CurrentCulture, GetString("CompareAttributeTestResource"), p0, p1); } + /// + /// description from resources + /// + internal static string DisplayAttribute_Description + { + get { return GetString("DisplayAttribute_Description"); } + } + + /// + /// description from resources + /// + internal static string FormatDisplayAttribute_Description() + { + return GetString("DisplayAttribute_Description"); + } + + /// + /// name from resources + /// + internal static string DisplayAttribute_Name + { + get { return GetString("DisplayAttribute_Name"); } + } + + /// + /// name from resources + /// + internal static string FormatDisplayAttribute_Name() + { + return GetString("DisplayAttribute_Name"); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Resources.resx b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Resources.resx index 2dd144ef85..9ddc5418f7 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Resources.resx +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Resources.resx @@ -1,17 +1,17 @@  - @@ -120,4 +120,10 @@ Comparing {0} to {1}. + + description from resources + + + name from resources + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/TestModelMetadataProvider.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/TestModelMetadataProvider.cs new file mode 100644 index 0000000000..45e59337df --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/TestModelMetadataProvider.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; +using Microsoft.Framework.Internal; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class TestModelMetadataProvider : DefaultModelMetadataProvider + { + // Creates a provider with all the defaults - includes data annotations + public static IModelMetadataProvider CreateDefaultProvider() + { + var detailsProviders = new IMetadataDetailsProvider[] + { + new DefaultBindingMetadataProvider(), + new DataAnnotationsMetadataDetailsProvider(), + }; + + var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); + return new DefaultModelMetadataProvider(compositeDetailsProvider); + } + + private readonly TestModelMetadataDetailsProvider _detailsProvider; + + public TestModelMetadataProvider() + : this(new TestModelMetadataDetailsProvider()) + { + } + + private TestModelMetadataProvider(TestModelMetadataDetailsProvider detailsProvider) + : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[] + { + new DefaultBindingMetadataProvider(), + new DataAnnotationsMetadataDetailsProvider(), + detailsProvider + })) + { + _detailsProvider = detailsProvider; + } + + public IMetadataBuilder ForType(Type type) + { + var key = ModelMetadataIdentity.ForType(type); + + var builder = new MetadataBuilder(key); + _detailsProvider.Builders.Add(builder); + return builder; + } + + public IMetadataBuilder ForType() + { + return ForType(typeof(TModel)); + } + + public IMetadataBuilder ForProperty(Type containerType, string propertyName) + { + var property = containerType.GetRuntimeProperty(propertyName); + Assert.NotNull(property); + + var key = ModelMetadataIdentity.ForProperty(property.PropertyType, property.Name, containerType); + + var builder = new MetadataBuilder(key); + _detailsProvider.Builders.Add(builder); + return builder; + } + + public IMetadataBuilder ForProperty(string propertyName) + { + return ForProperty(typeof(TContainer), propertyName); + } + + private class TestModelMetadataDetailsProvider : + IBindingMetadataProvider, + IDisplayMetadataProvider, + IValidationMetadataProvider + { + public List Builders { get; } = new List(); + + public void GetBindingMetadata([NotNull] BindingMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + + public void GetDisplayMetadata([NotNull] DisplayMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + + public void GetValidationMetadata([NotNull] ValidationMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + } + + public interface IMetadataBuilder + { + IMetadataBuilder BindingDetails(Action action); + + IMetadataBuilder DisplayDetails(Action action); + + IMetadataBuilder ValidationDetails(Action action); + } + + private class MetadataBuilder : IMetadataBuilder + { + private List> _bindingActions = new List>(); + private List> _displayActions = new List>(); + private List> _valiationActions = new List>(); + + private readonly ModelMetadataIdentity _key; + + public MetadataBuilder(ModelMetadataIdentity key) + { + _key = key; + } + + public void Apply(BindingMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _bindingActions) + { + action(context.BindingMetadata); + } + } + } + + public void Apply(DisplayMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _displayActions) + { + action(context.DisplayMetadata); + } + } + } + + public void Apply(ValidationMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _valiationActions) + { + action(context.ValidationMetadata); + } + } + } + + public IMetadataBuilder BindingDetails(Action action) + { + _bindingActions.Add(action); + return this; + } + + public IMetadataBuilder DisplayDetails(Action action) + { + _displayActions.Add(action); + return this; + } + + public IMetadataBuilder ValidationDetails(Action action) + { + _valiationActions.Add(action); + return this; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/AssociatedValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/AssociatedValidatorProviderTest.cs index 84c308a1c3..c4941318a8 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/AssociatedValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/AssociatedValidatorProviderTest.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public class AssociatedValidatorProviderTest { - private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider(); + private readonly IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); [Fact] public void GetValidatorsForPropertyWithLocalAttributes() diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/CompareAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/CompareAttributeAdapterTest.cs index b7d11ccc52..0a0d647084 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/CompareAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/CompareAttributeAdapterTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void ClientRulesWithCompareAttribute_ErrorMessageUsesDisplayName() { // Arrange - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = metadataProvider.GetMetadataForProperty(typeof(PropertyDisplayNameModel), "MyProperty"); var attribute = new CompareAttribute("OtherProperty"); @@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void ClientRulesWithCompareAttribute_ErrorMessageUsesPropertyName() { // Arrange - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = metadataProvider.GetMetadataForProperty(typeof(PropertyNameModel), "MyProperty"); var attribute = new CompareAttribute("OtherProperty"); var serviceCollection = new ServiceCollection(); @@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void ClientRulesWithCompareAttribute_ErrorMessageUsesOverride() { // Arrange - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = metadataProvider.GetMetadataForProperty( typeof(PropertyNameModel), "MyProperty"); var attribute = new CompareAttribute("OtherProperty") { @@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } // Arrange - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = metadataProvider.GetMetadataForProperty(typeof(PropertyNameModel), "MyProperty"); var attribute = new CompareAttribute("OtherProperty") { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs index 52fb9af0b2..3c1ddaa2e6 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public class DataAnnotationsModelValidatorProviderTest { - private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider(); + private readonly IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); #if ASPNET50 [Fact] diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorTest.cs index 32c119b597..c38fffb97b 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorTest.cs @@ -18,8 +18,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public class DataAnnotationsModelValidatorTest { - private static DataAnnotationsModelMetadataProvider _metadataProvider = - new DataAnnotationsModelMetadataProvider(); + private static IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); [Fact] public void ValuesSet() diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataMemberModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataMemberModelValidatorProviderTest.cs index 8ace91535e..96af57fe73 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataMemberModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataMemberModelValidatorProviderTest.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public class DataMemberModelValidatorProviderTest { - private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider(); + private readonly IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); [Fact] public void ClassWithoutAttributes_NoValidator() diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultObjectValidatorTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultObjectValidatorTests.cs index 00d2b5b089..bd3acaf8cc 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultObjectValidatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultObjectValidatorTests.cs @@ -495,7 +495,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new DataMemberModelValidatorProvider() }; - var modelMetadataProvider = new DataAnnotationsModelMetadataProvider(); + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var excludedValidationTypesPredicate = new List(); if (excludedTypes != null) diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MaxLengthAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MaxLengthAttributeAdapterTest.cs index 69b10541a4..55e6bd8bf1 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MaxLengthAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MaxLengthAttributeAdapterTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void ClientRulesWithMaxLengthAttribute() { // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); + var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new MaxLengthAttribute(10); var adapter = new MaxLengthAttributeAdapter(attribute); @@ -41,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var propertyName = "Length"; var message = "{0} must be at most {1}"; - var provider = new DataAnnotationsModelMetadataProvider(); + var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), propertyName); var attribute = new MaxLengthAttribute(5) { ErrorMessage = message }; var adapter = new MaxLengthAttributeAdapter(attribute); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MinLengthAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MinLengthAttributeAdapterTest.cs index 8a15b5f5bb..b655485bad 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MinLengthAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MinLengthAttributeAdapterTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void ClientRulesWithMinLengthAttribute() { // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); + var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new MinLengthAttribute(6); var adapter = new MinLengthAttributeAdapter(attribute); @@ -41,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var propertyName = "Length"; var message = "Array must have at least {1} items."; - var provider = new DataAnnotationsModelMetadataProvider(); + var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), propertyName); var attribute = new MinLengthAttribute(2) { ErrorMessage = message }; var adapter = new MinLengthAttributeAdapter(attribute); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RangeAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RangeAttributeAdapterTest.cs index 1da2995548..0dcf505634 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RangeAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RangeAttributeAdapterTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void GetClientValidationRules_ReturnsValidationParameters() { // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); + var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new RangeAttribute(typeof(decimal), "0", "100"); var adapter = new RangeAttributeAdapter(attribute); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RequiredAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RequiredAttributeAdapterTest.cs index 582a9aefef..03dbbe0146 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RequiredAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RequiredAttributeAdapterTest.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { // Arrange var expected = ValidationAttributeUtil.GetRequiredErrorMessage("Length"); - var provider = new DataAnnotationsModelMetadataProvider(); + var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new RequiredAttribute(); var adapter = new RequiredAttributeAdapter(attribute); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/StringLengthAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/StringLengthAttributeAdapterTest.cs index afb0640023..36243d40f6 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/StringLengthAttributeAdapterTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/StringLengthAttributeAdapterTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void GetClientValidationRules_WithMaxLength_ReturnsValidationParameters() { // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); + var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new StringLengthAttribute(8); var adapter = new StringLengthAttributeAdapter(attribute); @@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void GetClientValidationRules_WithMinAndMaxLength_ReturnsValidationParameters() { // Arrange - var provider = new DataAnnotationsModelMetadataProvider(); + var provider = TestModelMetadataProvider.CreateDefaultProvider(); var metadata = provider.GetMetadataForProperty(typeof(string), "Length"); var attribute = new StringLengthAttribute(10) { MinimumLength = 3 }; var adapter = new StringLengthAttributeAdapter(attribute); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs index 5b966d2793..ec75e99425 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs @@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.Razor private static ViewContext CreateViewContext(RazorPageCreateModelExpressionModel model) { - return CreateViewContext(model, new DataAnnotationsModelMetadataProvider()); + return CreateViewContext(model, new TestModelMetadataProvider()); } private static ViewContext CreateViewContext( diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/TestModelMetadataProvider.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/TestModelMetadataProvider.cs new file mode 100644 index 0000000000..7bea755490 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/TestModelMetadataProvider.cs @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class TestModelMetadataProvider : DefaultModelMetadataProvider + { + // Creates a provider with all the defaults - includes data annotations + public static IModelMetadataProvider CreateDefaultProvider() + { + var detailsProviders = new IMetadataDetailsProvider[] + { + new DefaultBindingMetadataProvider(), + new DataAnnotationsMetadataDetailsProvider(), + }; + + var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); + return new DefaultModelMetadataProvider(compositeDetailsProvider); + } + + private readonly TestModelMetadataDetailsProvider _detailsProvider; + + public TestModelMetadataProvider() + : this(new TestModelMetadataDetailsProvider()) + { + } + + private TestModelMetadataProvider(TestModelMetadataDetailsProvider detailsProvider) + : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[] + { + new DefaultBindingMetadataProvider(), + new DataAnnotationsMetadataDetailsProvider(), + detailsProvider + })) + { + _detailsProvider = detailsProvider; + } + + public IMetadataBuilder ForType(Type type) + { + var key = ModelMetadataIdentity.ForType(type); + + var builder = new MetadataBuilder(key); + _detailsProvider.Builders.Add(builder); + return builder; + } + + public IMetadataBuilder ForType() + { + return ForType(typeof(TModel)); + } + + public IMetadataBuilder ForProperty(Type containerType, string propertyName) + { + var property = containerType.GetRuntimeProperty(propertyName); + Assert.NotNull(property); + + var key = ModelMetadataIdentity.ForProperty(property.PropertyType, propertyName, containerType); + + var builder = new MetadataBuilder(key); + _detailsProvider.Builders.Add(builder); + return builder; + } + + public IMetadataBuilder ForProperty(string propertyName) + { + return ForProperty(typeof(TContainer), propertyName); + } + + private class TestModelMetadataDetailsProvider : + IBindingMetadataProvider, + IDisplayMetadataProvider, + IValidationMetadataProvider + { + public List Builders { get; } = new List(); + + public void GetBindingMetadata(BindingMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + + public void GetDisplayMetadata(DisplayMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + + public void GetValidationMetadata(ValidationMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + } + + public interface IMetadataBuilder + { + IMetadataBuilder BindingDetails(Action action); + + IMetadataBuilder DisplayDetails(Action action); + + IMetadataBuilder ValidationDetails(Action action); + } + + private class MetadataBuilder : IMetadataBuilder + { + private List> _bindingActions = new List>(); + private List> _displayActions = new List>(); + private List> _valiationActions = new List>(); + + private readonly ModelMetadataIdentity _key; + + public MetadataBuilder(ModelMetadataIdentity key) + { + _key = key; + } + + public void Apply(BindingMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _bindingActions) + { + action(context.BindingMetadata); + } + } + } + + public void Apply(DisplayMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _displayActions) + { + action(context.DisplayMetadata); + } + } + } + + public void Apply(ValidationMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _valiationActions) + { + action(context.ValidationMetadata); + } + } + } + + public IMetadataBuilder BindingDetails(Action action) + { + _bindingActions.Add(action); + return this; + } + + public IMetadataBuilder DisplayDetails(Action action) + { + _displayActions.Add(action); + return this; + } + + public IMetadataBuilder ValidationDetails(Action action) + { + _valiationActions.Add(action); + return this; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs index 51544a5c16..04de6c4c07 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // Arrange var expectedTagName = "not-a"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var tagHelperContext = new TagHelperContext( allAttributes: new Dictionary diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs index 9079d5c24b..cf24d16e39 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // Arrange var expectedTagName = "not-form"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var tagHelperContext = new TagHelperContext( allAttributes: new Dictionary { @@ -408,8 +408,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers return new ViewContext( actionContext, Mock.Of(), - new ViewDataDictionary( - new DataAnnotationsModelMetadataProvider()), + new ViewDataDictionary(new TestModelMetadataProvider()), TextWriter.Null); } } diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs index 4db0ea1581..1bf28c1fd3 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs @@ -132,7 +132,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers containerType, model, propertyName: nameof(Model.Text), - expressionName: nameAndId.Name); + expressionName: nameAndId.Name, + metadataProvider: null); // Act await tagHelper.ProcessAsync(context, output); @@ -274,9 +275,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers output.Content.SetContent(expectedContent); output.PostContent.SetContent(expectedPostContent); + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider.ForProperty("Text").DisplayDetails(dd => dd.DataTypeName = dataTypeName); + var htmlGenerator = new Mock(MockBehavior.Strict); - var tagHelper = GetTagHelper(htmlGenerator.Object, model, nameof(Model.Text)); - tagHelper.For.Metadata.DataTypeName = dataTypeName; + var tagHelper = GetTagHelper( + htmlGenerator.Object, + model, + nameof(Model.Text), + metadataProvider: metadataProvider); tagHelper.InputTypeName = inputTypeName; var tagBuilder = new TagBuilder("input", new HtmlEncoder()) @@ -367,9 +374,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers output.Content.SetContent(expectedContent); output.PostContent.SetContent(expectedPostContent); + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider.ForProperty("Text").DisplayDetails(dd => dd.DataTypeName = dataTypeName); + var htmlGenerator = new Mock(MockBehavior.Strict); - var tagHelper = GetTagHelper(htmlGenerator.Object, model, nameof(Model.Text)); - tagHelper.For.Metadata.DataTypeName = dataTypeName; + var tagHelper = GetTagHelper( + htmlGenerator.Object, + model, + nameof(Model.Text), + metadataProvider: metadataProvider); tagHelper.InputTypeName = inputTypeName; var tagBuilder = new TagBuilder("input", new HtmlEncoder()) @@ -562,9 +575,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers output.Content.SetContent(expectedContent); output.PostContent.SetContent(expectedPostContent); + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider.ForProperty("Text").DisplayDetails(dd => dd.DataTypeName = dataTypeName); + var htmlGenerator = new Mock(MockBehavior.Strict); - var tagHelper = GetTagHelper(htmlGenerator.Object, model, nameof(Model.Text)); - tagHelper.For.Metadata.DataTypeName = dataTypeName; + var tagHelper = GetTagHelper( + htmlGenerator.Object, + model, + nameof(Model.Text), + metadataProvider: metadataProvider); tagHelper.InputTypeName = inputTypeName; var tagBuilder = new TagBuilder("input", new HtmlEncoder()) @@ -655,15 +674,25 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(expectedTagName, output.TagName); } - private static InputTagHelper GetTagHelper(IHtmlGenerator htmlGenerator, object model, string propertyName) + private static InputTagHelper GetTagHelper( + IHtmlGenerator htmlGenerator, + object model, + string propertyName, + IModelMetadataProvider metadataProvider = null) { + if (metadataProvider == null) + { + metadataProvider = new TestModelMetadataProvider(); + } + return GetTagHelper( htmlGenerator, container: new Model(), containerType: typeof(Model), model: model, propertyName: propertyName, - expressionName: propertyName); + expressionName: propertyName, + metadataProvider: metadataProvider); } [Fact] @@ -704,10 +733,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Type containerType, object model, string propertyName, - string expressionName) + string expressionName, + IModelMetadataProvider metadataProvider) { - var metadataProvider = new EmptyModelMetadataProvider(); - var containerMetadata = metadataProvider.GetMetadataForType(containerType); var containerExplorer = metadataProvider.GetModelExplorerForType(containerType, container); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs index 29d2dcdee6..48f52fa3ab 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs @@ -170,7 +170,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "class", "form-control" }, { "for", tagHelperOutputContent.ExpectedId } }; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var containerMetadata = metadataProvider.GetMetadataForType(containerType); var containerExplorer = metadataProvider.GetModelExplorerForType(containerType, model); @@ -241,7 +241,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedPostContent = "original post-content"; var expectedTagName = "label"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var modelExplorer = metadataProvider .GetModelExplorerForType(typeof(Model), model: null) .GetExplorerForProperty(nameof(Model.Text)); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs index 92cc3da776..413124c11f 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs @@ -189,7 +189,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedPostContent = originalPostContent; var expectedTagName = "not-select"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var containerMetadata = metadataProvider.GetMetadataForType(containerType); var containerExplorer = metadataProvider.GetModelExplorerForType(containerType, model); @@ -278,7 +278,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedPostContent = originalPostContent + expectedOptions; var expectedTagName = "select"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var containerMetadata = metadataProvider.GetMetadataForType(containerType); var containerExplorer = metadataProvider.GetModelExplorerForType(containerType, model); @@ -382,7 +382,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedPostContent = originalPostContent + expectedOptions; var expectedTagName = "select"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var containerMetadata = metadataProvider.GetMetadataForType(containerType); var containerExplorer = metadataProvider.GetModelExplorerForType(containerType, model); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestModelMetadataProvider.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestModelMetadataProvider.cs new file mode 100644 index 0000000000..864349fd13 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestModelMetadataProvider.cs @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; +using Microsoft.Framework.Internal; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class TestModelMetadataProvider : DefaultModelMetadataProvider + { + // Creates a provider with all the defaults - includes data annotations + public static IModelMetadataProvider CreateDefaultProvider() + { + var detailsProviders = new IMetadataDetailsProvider[] + { + new DefaultBindingMetadataProvider(), + new DataAnnotationsMetadataDetailsProvider(), + }; + + var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); + return new DefaultModelMetadataProvider(compositeDetailsProvider); + } + + private readonly TestModelMetadataDetailsProvider _detailsProvider; + + public TestModelMetadataProvider() + : this(new TestModelMetadataDetailsProvider()) + { + + } + + private TestModelMetadataProvider(TestModelMetadataDetailsProvider detailsProvider) + : base(new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[] + { + new DefaultBindingMetadataProvider(), + new DataAnnotationsMetadataDetailsProvider(), + detailsProvider + })) + { + _detailsProvider = detailsProvider; + } + + public IMetadataBuilder ForType(Type type) + { + var key = ModelMetadataIdentity.ForType(type); + + var builder = new MetadataBuilder(key); + _detailsProvider.Builders.Add(builder); + return builder; + } + + public IMetadataBuilder ForType() + { + return ForType(typeof(TModel)); + } + + public IMetadataBuilder ForProperty(Type containerType, string propertyName) + { + var property = containerType.GetRuntimeProperty(propertyName); + Assert.NotNull(property); + + var key = ModelMetadataIdentity.ForProperty(property.PropertyType, propertyName, containerType); + + var builder = new MetadataBuilder(key); + _detailsProvider.Builders.Add(builder); + return builder; + } + + public IMetadataBuilder ForProperty(string propertyName) + { + return ForProperty(typeof(TContainer), propertyName); + } + + private class TestModelMetadataDetailsProvider : + IBindingMetadataProvider, + IDisplayMetadataProvider, + IValidationMetadataProvider + { + public List Builders { get; } = new List(); + + public void GetBindingMetadata(BindingMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + + public void GetDisplayMetadata(DisplayMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + + public void GetValidationMetadata(ValidationMetadataProviderContext context) + { + foreach (var builder in Builders) + { + builder.Apply(context); + } + } + } + + public interface IMetadataBuilder + { + IMetadataBuilder BindingDetails(Action action); + + IMetadataBuilder DisplayDetails(Action action); + + IMetadataBuilder ValidationDetails(Action action); + } + + private class MetadataBuilder : IMetadataBuilder + { + private List> _bindingActions = new List>(); + private List> _displayActions = new List>(); + private List> _valiationActions = new List>(); + + private readonly ModelMetadataIdentity _key; + + public MetadataBuilder(ModelMetadataIdentity key) + { + _key = key; + } + + public void Apply(BindingMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _bindingActions) + { + action(context.BindingMetadata); + } + } + } + + public void Apply(DisplayMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _displayActions) + { + action(context.DisplayMetadata); + } + } + } + + public void Apply(ValidationMetadataProviderContext context) + { + if (_key.Equals(context.Key)) + { + foreach (var action in _valiationActions) + { + action(context.ValidationMetadata); + } + } + } + + public IMetadataBuilder BindingDetails(Action action) + { + _bindingActions.Add(action); + return this; + } + + public IMetadataBuilder DisplayDetails(Action action) + { + _displayActions.Add(action); + return this; + } + + public IMetadataBuilder ValidationDetails(Action action) + { + _valiationActions.Add(action); + return this; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs index b38dfd9634..88628c9829 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs @@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; var expectedTagName = "not-textarea"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var containerMetadata = metadataProvider.GetMetadataForType(containerType); var containerExplorer = metadataProvider.GetModelExplorerForType(containerType, container); @@ -167,7 +167,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedPostContent = "original post-content"; var expectedTagName = "textarea"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var modelExplorer = metadataProvider .GetModelExplorerForType(typeof(Model), model: null) .GetExplorerForProperty(nameof(Model.Text)); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs index 4d899b4b90..3344f0007e 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // Arrange var expectedTagName = "not-span"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var modelExpression = CreateModelExpression("Name"); var validationMessageTagHelper = new ValidationMessageTagHelper { @@ -287,17 +287,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers private static ModelExpression CreateModelExpression(string name) { - var modelMetadataProvider = new Mock().Object; + var modelMetadataProvider = new EmptyModelMetadataProvider(); return new ModelExpression( name, - new ModelExplorer( - modelMetadataProvider, - new ModelMetadata( - modelMetadataProvider, - containerType: null, - modelType: typeof(object), - propertyName: string.Empty), - model: null)); + modelMetadataProvider.GetModelExplorerForType(typeof(object), model: null)); } private static ViewContext CreateViewContext() @@ -311,7 +304,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers actionContext, Mock.Of(), new ViewDataDictionary( - new DataAnnotationsModelMetadataProvider()), + new EmptyModelMetadataProvider()), TextWriter.Null); } } diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs index 50e8c3aa96..5ad599609b 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs @@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // Arrange var expectedTagName = "not-div"; - var metadataProvider = new DataAnnotationsModelMetadataProvider(); + var metadataProvider = new TestModelMetadataProvider(); var validationSummaryTagHelper = new ValidationSummaryTagHelper { ValidationSummary = ValidationSummary.All, diff --git a/test/WebSites/ModelBindingWebSite/AdditionalValuesMetadataProvider.cs b/test/WebSites/ModelBindingWebSite/AdditionalValuesMetadataProvider.cs index 2a28f76ec7..d7f2438acf 100644 --- a/test/WebSites/ModelBindingWebSite/AdditionalValuesMetadataProvider.cs +++ b/test/WebSites/ModelBindingWebSite/AdditionalValuesMetadataProvider.cs @@ -2,40 +2,32 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using ModelBindingWebSite.Models; namespace ModelBindingWebSite { - public class AdditionalValuesMetadataProvider : DataAnnotationsModelMetadataProvider + public class AdditionalValuesMetadataProvider : IDisplayMetadataProvider { public static readonly string GroupNameKey = "__GroupName"; private static Guid _guid = new Guid("7d6d0de2-8d59-49ac-99cc-881423b75a76"); - protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype( - IEnumerable attributes, - Type containerType, - Type modelType, - string propertyName) + public void GetDisplayMetadata(DisplayMetadataProviderContext context) { - var metadata = base.CreateMetadataPrototype(attributes, containerType, modelType, propertyName); - if (modelType == typeof(LargeModelWithValidation)) + if (context.Key.ModelType == typeof(LargeModelWithValidation)) { - metadata.AdditionalValues.Add("key1", _guid); - metadata.AdditionalValues.Add("key2", "value2"); + context.DisplayMetadata.AdditionalValues.Add("key1", _guid); + context.DisplayMetadata.AdditionalValues.Add("key2", "value2"); } - var displayAttribute = attributes.OfType().FirstOrDefault(); + var displayAttribute = context.Attributes.OfType().FirstOrDefault(); var groupName = displayAttribute?.GroupName; if (!string.IsNullOrEmpty(groupName)) { - metadata.AdditionalValues[GroupNameKey] = groupName; + context.DisplayMetadata.AdditionalValues[GroupNameKey] = groupName; } - - return metadata; } } } \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Controllers/ModelMetadataController.cs b/test/WebSites/ModelBindingWebSite/Controllers/ModelMetadataController.cs index 9316020bfc..455573b764 100644 --- a/test/WebSites/ModelBindingWebSite/Controllers/ModelMetadataController.cs +++ b/test/WebSites/ModelBindingWebSite/Controllers/ModelMetadataController.cs @@ -12,7 +12,7 @@ namespace ModelBindingWebSite.Controllers public class ModelMetadataController { [HttpGet(template: "/AdditionalValues")] - public IDictionary GetAdditionalValues([FromServices] IModelMetadataProvider provider) + public IReadOnlyDictionary GetAdditionalValues([FromServices] IModelMetadataProvider provider) { var metadata = provider.GetMetadataForType(typeof(LargeModelWithValidation)); diff --git a/test/WebSites/ModelBindingWebSite/Startup.cs b/test/WebSites/ModelBindingWebSite/Startup.cs index 40626cde0f..8ff7059c4c 100644 --- a/test/WebSites/ModelBindingWebSite/Startup.cs +++ b/test/WebSites/ModelBindingWebSite/Startup.cs @@ -19,10 +19,6 @@ namespace ModelBindingWebSite // Set up application services app.UseServices(services => { - // Override the IModelMetadataProvider AddMvc would normally add. - // ModelMetadataController relies on additional values AdditionalValuesMetadataProvider provides. - services.AddSingleton(); - // Add MVC services to the services container services.AddMvc(configuration) .Configure(m => @@ -32,6 +28,9 @@ namespace ModelBindingWebSite m.AddXmlDataContractSerializerFormatter(); m.ValidationExcludeFilters.Add(typeof(Address)); + + // ModelMetadataController relies on additional values AdditionalValuesMetadataProvider provides. + m.ModelMetadataDetailsProviders.Add(new AdditionalValuesMetadataProvider()); }); services.AddSingleton(); diff --git a/test/WebSites/ModelBindingWebSite/TestBindingSourceModelBinder.cs b/test/WebSites/ModelBindingWebSite/TestBindingSourceModelBinder.cs index d5b8b3921a..71327521cb 100644 --- a/test/WebSites/ModelBindingWebSite/TestBindingSourceModelBinder.cs +++ b/test/WebSites/ModelBindingWebSite/TestBindingSourceModelBinder.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; namespace ModelBindingWebSite { @@ -17,7 +19,8 @@ namespace ModelBindingWebSite protected override Task BindModelCoreAsync(ModelBindingContext bindingContext) { - var metadata = (FromTestAttribute)bindingContext.ModelMetadata.BinderMetadata; + var attributes = ((DefaultModelMetadata)bindingContext.ModelMetadata).Attributes; + var metadata = attributes.OfType().First(); var model = metadata.Value; if (!IsSimpleType(bindingContext.ModelType)) {