From 90cef3b9ca86f56bd49700f632474e7db068816a Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 27 Feb 2015 15:09:44 -0800 Subject: [PATCH] Refactor of the model metadata provider Separates the MMP into two phases: 1). Creation of the ModelMetadata, discovery of properties and attributes (reflection) is part of the MMP 2). Lookup of details based on attributes is now part of another phase, and has its results cached. Users can now implements and register an IFooMetadataProvider to customize a single aspect of metadata (see how data annotations does it). --- .../DefaultControllerActionArgumentBinder.cs | 28 +- .../DefaultApiDescriptionProvider.cs | 23 +- src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs | 18 +- .../Properties/Resources.Designer.cs | 10 +- .../BinderMetadata/BindingSource.cs | 6 - .../Binders/BindingSourceModelBinder.cs | 4 +- .../Binders/CompositeModelBinder.cs | 4 +- .../Binders/MutableObjectModelBinder.cs | 6 +- .../EmptyModelMetadataProvider.cs | 14 + .../{Metadata => }/IModelMetadataProvider.cs | 10 +- .../Metadata/AssociatedMetadataProvider.cs | 241 ----- .../Metadata/BindingMetadata.cs | 53 ++ .../BindingMetadataProviderContext.cs | 43 + ...CachedDataAnnotationsMetadataAttributes.cs | 102 -- .../CachedDataAnnotationsModelMetadata.cs | 412 --------- .../Metadata/CachedModelMetadata.cs | 727 --------------- .../DataAnnotationsMetadataDetailsProvider.cs | 171 ++++ .../DataAnnotationsModelMetadataProvider.cs | 42 - .../DefaultBindingMetadataProvider.cs | 99 ++ ...DefaultCompositeMetadataDetailsProvider.cs | 53 ++ .../Metadata/DefaultMetadataDetailsCache.cs | 65 ++ .../Metadata/DefaultModelMetadata.cs | 360 ++++++++ .../Metadata/DefaultModelMetadataProvider.cs | 198 ++++ .../Metadata/DisplayMetadata.cs | 112 +++ .../DisplayMetadataProviderContext.cs | 43 + .../Metadata/EmptyModelMetadataProvider.cs | 53 -- .../Metadata/IBindingMetadataProvider.cs | 19 + .../ICompositeMetadataDetailsProvider.cs | 15 + .../Metadata/IDisplayMetadataProvider.cs | 19 + .../Metadata/IMetadataDetailsProvider.cs | 14 + .../Metadata/IValidationMetadataProvider.cs | 19 + .../Metadata/ModelMetadata.cs | 285 ------ .../Metadata/ModelMetadataIdentity.cs | 113 +++ .../Metadata/ModelMetadataKind.cs | 26 + .../Metadata/ValidationMetadata.cs | 12 + .../ValidationMetadataProviderContext.cs | 43 + .../{Metadata => }/ModelExplorer.cs | 0 .../{Metadata => }/ModelExplorerExtensions.cs | 0 .../ModelMetadata.cs | 247 +++++ .../ModelMetadataProviderExtensions.cs | 0 .../{Metadata => }/ModelPropertyCollection.cs | 0 .../Validation/DefaultObjectValidator.cs | 3 +- .../project.json | 1 + .../OverloadActionConstraint.cs | 3 +- src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs | 5 + src/Microsoft.AspNet.Mvc/MvcServices.cs | 13 +- .../BodyModelBinderTests.cs | 67 +- .../ControllerTests.cs | 13 +- .../DefaultApiDescriptionProviderTest.cs | 2 +- .../ControllerActionArgumentBinderTests.cs | 34 +- .../ModelBindingHelperTest.cs | 75 +- .../Rendering/DefaultDisplayTemplatesTest.cs | 38 +- .../Rendering/DefaultEditorTemplatesTest.cs | 146 ++- .../Rendering/DefaultTemplatesUtilities.cs | 15 +- .../Rendering/HtmlHelperCheckboxTest.cs | 4 +- .../HtmlHelperDisplayNameExtensionsTest.cs | 4 +- .../Rendering/HtmlHelperDisplayTextTest.cs | 47 +- .../HtmlHelperLabelExtensionsTest.cs | 52 +- .../Rendering/HtmlHelperNameExtensionsTest.cs | 22 +- .../Rendering/ViewDataOfTTest.cs | 10 +- .../TestModelMetadataProvider.cs | 182 ++-- .../RazorInstrumentationTests.cs | 2 +- .../Binders/ArrayModelBinderTest.cs | 17 +- ...nderTypeBasedModelBinderModelBinderTest.cs | 18 +- .../Binders/BindingSourceModelBinderTest.cs | 20 +- .../Binders/CollectionModelBinderTest.cs | 2 +- .../Binders/ComplexModelDtoTest.cs | 2 +- .../Binders/CompositeModelBinderTest.cs | 2 +- .../Binders/HeaderModelBinderTests.cs | 12 +- .../Binders/MutableObjectModelBinderTest.cs | 56 +- .../AssociatedMetadataProviderTest.cs | 260 ------ ...edDataAnnotationsMetadataAttributesTest.cs | 166 ---- ...ataAnnotationsModelMetadataProviderTest.cs | 375 -------- .../CachedDataAnnotationsModelMetadataTest.cs | 614 ------------- ...aAnnotationsMetadataDetailsProviderTest.cs | 197 ++++ .../DefaultBindingMetadataProviderTest.cs | 151 +++ .../DefaultModelMetadataProviderTest.cs | 213 +++++ .../Metadata/DefaultModelMetadataTest.cs | 438 +++++++++ .../EmptyCompositeMetadataDetailsProvider.cs | 13 + .../Metadata/ModelMetadataProviderTest.cs | 868 ++++++++++++++++++ .../Metadata/ModelMetadataTest.cs | 745 ++++----------- .../Properties/Resources.Designer.cs | 32 + .../Resources.resx | 60 +- .../TestModelMetadataProvider.cs | 183 ++++ .../AssociatedValidatorProviderTest.cs | 2 +- .../Validation/CompareAttributeAdapterTest.cs | 8 +- ...taAnnotationsModelValidatorProviderTest.cs | 2 +- .../DataAnnotationsModelValidatorTest.cs | 3 +- .../DataMemberModelValidatorProviderTest.cs | 2 +- .../Validation/DefaultObjectValidatorTests.cs | 2 +- .../MaxLengthAttributeAdapterTest.cs | 4 +- .../MinLengthAttributeAdapterTest.cs | 4 +- .../Validation/RangeAttributeAdapterTest.cs | 2 +- .../RequiredAttributeAdapterTest.cs | 2 +- .../StringLengthAttributeAdapterTest.cs | 4 +- .../RazorPageCreateModelExpressionTest.cs | 2 +- .../TestModelMetadataProvider.cs | 182 ++++ .../AnchorTagHelperTest.cs | 2 +- .../FormTagHelperTest.cs | 5 +- .../InputTagHelperTest.cs | 52 +- .../LabelTagHelperTest.cs | 4 +- .../SelectTagHelperTest.cs | 6 +- .../TestModelMetadataProvider.cs | 184 ++++ .../TextAreaTagHelperTest.cs | 4 +- .../ValidationMessageTagHelperTest.cs | 15 +- .../ValidationSummaryTagHelperTest.cs | 2 +- .../AdditionalValuesMetadataProvider.cs | 24 +- .../Controllers/ModelMetadataController.cs | 2 +- test/WebSites/ModelBindingWebSite/Startup.cs | 7 +- .../TestBindingSourceModelBinder.cs | 5 +- 110 files changed, 5074 insertions(+), 4348 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/EmptyModelMetadataProvider.cs rename src/Microsoft.AspNet.Mvc.ModelBinding/{Metadata => }/IModelMetadataProvider.cs (68%) delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadata.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadataProviderContext.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsMetadataDetailsProvider.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsModelMetadataProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultCompositeMetadataDetailsProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultMetadataDetailsCache.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadataProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DisplayMetadata.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DisplayMetadataProviderContext.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/EmptyModelMetadataProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IBindingMetadataProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IDisplayMetadataProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IMetadataDetailsProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IValidationMetadataProvider.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataIdentity.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataKind.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadata.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadataProviderContext.cs rename src/Microsoft.AspNet.Mvc.ModelBinding/{Metadata => }/ModelExplorer.cs (100%) rename src/Microsoft.AspNet.Mvc.ModelBinding/{Metadata => }/ModelExplorerExtensions.cs (100%) create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadata.cs rename src/Microsoft.AspNet.Mvc.ModelBinding/{Metadata => }/ModelMetadataProviderExtensions.cs (100%) rename src/Microsoft.AspNet.Mvc.ModelBinding/{Metadata => }/ModelPropertyCollection.cs (100%) delete mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs delete mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs delete mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs delete mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DataAnnotationsMetadataDetailsProviderTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataProviderTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/EmptyCompositeMetadataDetailsProvider.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/TestModelMetadataProvider.cs create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Test/TestModelMetadataProvider.cs create mode 100644 test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestModelMetadataProvider.cs 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)) {