// 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; }; } } } }