File: DynamicData\MetaColumn.cs
Project: ndp\fx\src\xsp\system\DynamicData\System.Web.DynamicData.csproj (System.Web.DynamicData)
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data.Spatial;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Web.DynamicData.ModelProviders;
using System.Web.DynamicData.Util;
 
namespace System.Web.DynamicData {
    /// <summary>
    /// Object that represents a database column used by dynamic data
    /// </summary>
    public class MetaColumn : IFieldFormattingOptions, IMetaColumn {
        private TypeCode _typeCode = TypeCode.Empty;
        private Type _type;
        private object _metadataCacheLock = new object();
 
        // text, ntext, varchar(max), nvarchar(max) all have different maximum lengths so this is a minimum value
        // that ensures that all of the abovementioned columns get treated as long strings.
        private static readonly int s_longStringLengthCutoff = ((Int32.MaxValue >> 1) - 4);
 
        // Metadata related members
        private IMetaColumnMetadata _metadata;
        private bool? _scaffoldValueManual;
        private bool? _scaffoldValueDefault;
 
        public MetaColumn(MetaTable table, ColumnProvider columnProvider) {
            Table = table;
            Provider = columnProvider;
        }
 
        /// <summary>
        /// The collection of metadata attributes that apply to this column
        /// </summary>
        public AttributeCollection Attributes {
            get {
                return Metadata.Attributes;
            }
        }
 
        /// <summary>
        /// The CLR type of the property/column
        /// </summary>
        public Type ColumnType {
            get {
                if (_type == null) {
                    // If it's an Nullable<T>, work with T instead
                    _type = Misc.RemoveNullableFromType(Provider.ColumnType);
                }
 
                return _type;
            }
        }
 
        /// <summary>
        ///  The DataTypeAttribute used for the column
        /// </summary>
        public DataTypeAttribute DataTypeAttribute {
            get {
                return Metadata.DataTypeAttribute;
            }
        }
 
        /// <summary>
        /// This column's defalut value. It is typically used to populate the field when creating a new entry.
        /// </summary>
        public object DefaultValue {
            get {
                return Metadata.DefaultValue;
            }
        }
 
        /// <summary>
        /// A description for this column
        /// </summary>
        public virtual string Description {
            get {
                // 
                return Metadata.Description;
            }
        }
 
        /// <summary>
        /// A friendly display name for this column
        /// </summary>
        public virtual string DisplayName {
            get {
                // Default to the Name if there is no DisplayName
                return Metadata.DisplayName ?? Name;
            }
        }
 
        /// <summary>
        /// The PropertyInfo of the property that represents this column on the entity type
        /// </summary>
        public PropertyInfo EntityTypeProperty { get { return Provider.EntityTypeProperty; } }
 
        /// <summary>
        /// The FilterUIHint used for the column
        /// </summary>
        public string FilterUIHint {
            get {
                return Metadata.FilterUIHint;
            }
        }
 
        /// <summary>
        /// Does this column contain binary data
        /// </summary>
        public bool IsBinaryData {
            get {
                return ColumnType == typeof(byte[]);
            }
        }
 
        /// <summary>
        /// meant to indicate that a member is an extra property that was declared in a partial class 
        /// </summary>
        public bool IsCustomProperty { get { return Provider.IsCustomProperty; } }
 
        /// <summary>
        /// Is this column a floating point type (float, double, decimal)
        /// </summary>
        public bool IsFloatingPoint {
            get {
                return ColumnType == typeof(float) || ColumnType == typeof(double) || ColumnType == typeof(decimal);
            }
        }
 
        /// <summary>
        /// This is set for columns that are part of a foreign key. Note that it is NOT set for
        /// the strongly typed entity ref columns (though those columns 'use' one or more columns
        /// where IsForeignKeyComponent is set).
        /// </summary>
        public bool IsForeignKeyComponent {
            get { return Provider.IsForeignKeyComponent; }
        }
 
        /// <summary>
        /// Is this column's value auto-generated in the database
        /// </summary>
        public bool IsGenerated { get { return Provider.IsGenerated; } }
 
        /// <summary>
        /// Is this column a integer
        /// </summary>
        public bool IsInteger {
            get {
                return ColumnType == typeof(byte) || ColumnType == typeof(short) || ColumnType == typeof(int) || ColumnType == typeof(long);
            }
        }
 
        /// <summary>
        /// Is this column a 'long' string. This is used to determine whether a textbox or textarea should be used.
        /// </summary>
        public bool IsLongString {
            get {
                return IsString && Provider.MaxLength >= s_longStringLengthCutoff;
            }
        }
 
        /// <summary>
        /// Is this column part if the table's primary key
        /// </summary>
        public bool IsPrimaryKey { get { return Provider.IsPrimaryKey; } }
 
        /// <summary>
        /// Is this a readonly column
        /// </summary>
        [SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces",
            Justification = "Interface denotes existence of property, not used for security.")]
        public virtual bool IsReadOnly {
            get {
                return Provider.IsReadOnly || Metadata.IsReadOnly ||
                    (Metadata.EditableAttribute != null && !Metadata.EditableAttribute.AllowEdit);
            }
        }
 
        /// <summary>
        /// Specifies if a read-only column (see IsReadOnly) allows for a value to be set on insert.
        /// The default value is false when the column is read-only; true when the column is not read-only.
        /// The default value can be override by using EditableAttribute (note that this will indicate that
        /// the column is meant to be read only).
        /// </summary>
        public bool AllowInitialValue {
            get {
                if (IsGenerated) {
                    // always return false for generated columns, since that is a stronger statement.
                    return false;
                }
 
                return Metadata.EditableAttribute.GetPropertyValue(a => a.AllowInitialValue, !IsReadOnly);
            }
        }
 
        /// <summary>
        /// Does this column require a value
        /// </summary>
        public bool IsRequired {
            get {
                return Metadata.RequiredAttribute != null;
            }
        }
 
        /// <summary>
        /// Is this column a string
        /// </summary>
        public bool IsString {
            get {
                return ColumnType == typeof(string);
            }
        }
 
        /// <summary>
        /// The maximun length allowed for this column (applies to string columns)
        /// </summary>
        public int MaxLength {
            get {
                var stringLengthAttribute = Metadata.StringLengthAttribute;
                return stringLengthAttribute != null ? stringLengthAttribute.MaximumLength : Provider.MaxLength;
            }
        }
 
        /// <summary>
        /// The MetaModel that this column belongs to
        /// </summary>
        public MetaModel Model { get { return Table.Model; } }
 
        /// <summary>
        /// The column's name
        /// </summary>
        public string Name { get { return Provider.Name; } }
 
        /// <summary>
        /// A value that can be used as a watermark in UI bound to value represented by this column.
        /// </summary>
        public virtual string Prompt { get { return Metadata.Prompt; } }
 
        /// <summary>
        /// the abstraction provider object that was used to construct this metacolumn.
        /// </summary>
        public ColumnProvider Provider { get; private set; }
 
        /// <summary>
        /// The error message used if this column is required and it is set to empty
        /// </summary>
        public string RequiredErrorMessage {
            get {
                var requiredAttribute = Metadata.RequiredAttribute;
                return requiredAttribute != null ? 
                    StringLocalizerUtil.GetLocalizedString(requiredAttribute, DisplayName) : String.Empty;
            }
        }
 
        /// <summary>
        /// Is it a column that should be displayed (e.g. in a GridView's auto generate mode) 
        /// </summary>
        [SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces",
            Justification = "Interface denotes existence of property, not used for security.")]
        public virtual bool Scaffold {
            get {
                // If the value was explicitely set, that always takes precedence
                if (_scaffoldValueManual != null) {
                    return _scaffoldValueManual.Value;
                }
 
                // If there is a DisplayAttribute with an explicit value, always honor it
                var displayAttribute = Metadata.DisplayAttribute;
                if (displayAttribute != null && displayAttribute.GetAutoGenerateField().HasValue) {
                    return displayAttribute.GetAutoGenerateField().Value;
                }
 
                // If there is an explicit Scaffold attribute, always honor it
                var scaffoldAttribute = Metadata.ScaffoldColumnAttribute;
                if (scaffoldAttribute != null) {
                    return scaffoldAttribute.Scaffold;
                }
 
                if (_scaffoldValueDefault == null) {
                    _scaffoldValueDefault = ScaffoldNoCache;
                }
 
                return _scaffoldValueDefault.Value;
            }
            set {
                _scaffoldValueManual = value;
            }
        }
 
        /// <summary>
        /// Look at various pieces of data on the column to determine whether it's
        /// Scaffold mode should be on.  This only gets called once per column and the result
        /// is cached
        /// </summary>
        internal virtual bool ScaffoldNoCache {
            get {
                // Any field with a UIHint should be included
                if (!String.IsNullOrEmpty(UIHint)) return true;
 
                // Skip columns that are part of a foreign key, since they are already 'covered' in the
                // strongly typed foreign key column
                if (IsForeignKeyComponent) return false;
 
                // Skip generated columns, which are not typically interesting
                if (IsGenerated) return false;
 
                // Always include non-generated primary keys
                if (IsPrimaryKey) return true;
 
                // Skip custom properties
                if (IsCustomProperty) return false;
 
                // Include strings and characters
                if (IsString) return true;
                if (ColumnType == typeof(char)) return true;
 
                // Include numbers
                if (IsInteger) return true;
                if (IsFloatingPoint) return true;
 
                // Include date related columns
                if (ColumnType == typeof(DateTime)) return true;
                if (ColumnType == typeof(TimeSpan)) return true;
                if (ColumnType == typeof(DateTimeOffset)) return true;
 
 
                // Include bools
                if (ColumnType == typeof(bool)) return true;
 
                // Include enums
                Type enumType;
                if (this.IsEnumType(out enumType)) return true;
 
                //Include spatial types
                if (ColumnType == typeof(DbGeography)) return true;
                if (ColumnType == typeof(DbGeometry)) return true;
 
                return false;
            }
        }
 
        /// <summary>
        /// A friendly short display name for this column. Meant to be used in GridView and similar controls where there might be
        /// limited column header space
        /// </summary>
        public virtual string ShortDisplayName {
            get {
                // Default to the DisplayName if there is no ShortDisplayName
                return Metadata.ShortDisplayName ?? DisplayName;
            }
        }
 
        /// <summary>
        /// The expression used to determine the sort order for this column
        /// </summary>
        public string SortExpression {
            get {
                return SortExpressionInternal;
            }
        }
 
        internal virtual string SortExpressionInternal {
            get {
                return Provider.IsSortable ? Name : string.Empty;
            }
        }
 
        /// <summary>
        /// The MetaTable that this column belongs to
        /// </summary>
        public MetaTable Table { get; private set; }
 
        /// <summary>
        /// The TypeCode of this column. It is derived from the ColumnType
        /// </summary>
        public TypeCode TypeCode {
            get {
                if (_typeCode == TypeCode.Empty) {
                    _typeCode = DataSourceUtil.TypeCodeFromType(ColumnType);
                }
 
                return _typeCode;
            }
        }
 
        /// <summary>
        ///  The UIHint used for the column
        /// </summary>
        [SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces",
            Justification = "Interface denotes existence of property, not used for security.")]
        public virtual string UIHint {
            get {
                return Metadata.UIHint;
            }
        }
 
        #region IFieldFormattingOptions Members
 
        /// <summary>
        /// Same semantic as the same property on System.Web.UI.WebControls.BoundField
        /// </summary>
        public bool ApplyFormatInEditMode {
            get {
                return Metadata.ApplyFormatInEditMode;
            }
        }
 
        /// <summary>
        /// Same semantic as the same property on System.Web.UI.WebControls.BoundField
        /// </summary>
        public bool ConvertEmptyStringToNull {
            get {
                return Metadata.ConvertEmptyStringToNull;
            }
        }
 
        /// <summary>
        /// Same semantic as the same property on System.Web.UI.WebControls.BoundField
        /// </summary>
        public string DataFormatString {
            get {
                return Metadata.DataFormatString;
            }
        }
 
        /// <summary>
        /// Same semantic as the same property on System.Web.UI.WebControls.BoundField
        /// </summary>
        public bool HtmlEncode {
            get { 
                return Metadata.HtmlEncode; 
            }
        }
 
        /// <summary>
        /// Same semantic as the same property on System.Web.UI.WebControls.BoundField
        /// </summary>
        public string NullDisplayText {
            get {
                return Metadata.NullDisplayText;
            }
        }
 
        #endregion
 
        /// <summary>
        /// Build the attribute collection, later made available through the Attributes property
        /// </summary>
        protected virtual AttributeCollection BuildAttributeCollection() {
            return Provider.Attributes;
        }
 
        /// <summary>
        /// Perform initialization logic for this column
        /// </summary>
        internal protected virtual void Initialize() { }
 
        /// <summary>
        /// Resets cached column metadata (i.e. information coming from attributes). The metadata cache will be rebuilt
        /// the next time any metadata-derived information gets requested.
        /// </summary>
        public void ResetMetadata() {
            _metadata = null;
        }
 
        /// <summary>
        /// Shows the column name. Mostly for debugging purpose.
        /// </summary>
        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public override string ToString() {
            return GetType().Name + " " + Name;
        }
 
        internal IMetaColumnMetadata Metadata {
            get {
                // Use a local to avoid returning null if ResetMetadata is called
                IMetaColumnMetadata metadata = _metadata;
                if (metadata == null) {
                    metadata = new MetaColumnMetadata(this);
                    _metadata = metadata;
                }
                return metadata;
            }
            set {
                // settable for unit testing
                _metadata = value;
            }
        }
 
        #region Metadata abstraction
 
        internal interface IMetaColumnMetadata {
 
            AttributeCollection Attributes { get; }
 
            DisplayAttribute DisplayAttribute { get; }
 
            bool ApplyFormatInEditMode { get; }
 
            bool ConvertEmptyStringToNull { get; }
 
            bool HtmlEncode { get; }
 
            string DataFormatString { get; }
 
            DataTypeAttribute DataTypeAttribute { get; }
 
            object DefaultValue { get; }
 
            string Description { get; }
 
            string DisplayName { get; }
 
            string FilterUIHint { get; }
 
            string ShortDisplayName { get; }
 
            string NullDisplayText { get; }
 
            string Prompt { get; }
 
            RequiredAttribute RequiredAttribute { get; }
 
            ScaffoldColumnAttribute ScaffoldColumnAttribute { get; }
 
            StringLengthAttribute StringLengthAttribute { get; }
 
            string UIHint { get; }
 
            bool IsReadOnly { get; }
 
            EditableAttribute EditableAttribute { get; }
        }
 
        internal class MetaColumnMetadata : IMetaColumnMetadata {
            private MetaColumn Column { get; set; }
 
            public AttributeCollection Attributes { get; private set; }
 
            public MetaColumnMetadata(MetaColumn column) {
                Debug.Assert(column != null);
                Column = column;
 
                Attributes = Column.BuildAttributeCollection();
 
                DisplayAttribute = Attributes.FirstOrDefault<DisplayAttribute>();
                DataTypeAttribute = Attributes.FirstOrDefault<DataTypeAttribute>() ?? GetDefaultDataTypeAttribute();
                DescriptionAttribute = Attributes.FirstOrDefault<DescriptionAttribute>();
                DefaultValueAttribute = Attributes.FirstOrDefault<DefaultValueAttribute>();
                DisplayNameAttribute = Attributes.FirstOrDefault<DisplayNameAttribute>();
                RequiredAttribute = Attributes.FirstOrDefault<RequiredAttribute>();
                ScaffoldColumnAttribute = Attributes.FirstOrDefault<ScaffoldColumnAttribute>();
                StringLengthAttribute = Attributes.FirstOrDefault<StringLengthAttribute>();
 
                UIHint = GetHint<UIHintAttribute>(a => a.PresentationLayer, a => a.UIHint);
                FilterUIHint = GetHint<FilterUIHintAttribute>(a => a.PresentationLayer, a => a.FilterUIHint);
 
                EditableAttribute = Attributes.FirstOrDefault<EditableAttribute>();
                IsReadOnly = Attributes.GetAttributePropertyValue<ReadOnlyAttribute, bool>(a => a.IsReadOnly, false);
 
                var displayFormatAttribute = Attributes.FirstOrDefault<DisplayFormatAttribute>() ??
                    (DataTypeAttribute != null ? DataTypeAttribute.DisplayFormat : null);
 
                ApplyFormatInEditMode = displayFormatAttribute.GetPropertyValue(a => a.ApplyFormatInEditMode, false);
                ConvertEmptyStringToNull = displayFormatAttribute.GetPropertyValue(a => a.ConvertEmptyStringToNull, true);
                DataFormatString = displayFormatAttribute.GetPropertyValue(a => a.DataFormatString, String.Empty);
                NullDisplayText = displayFormatAttribute.GetPropertyValue(a => a.NullDisplayText, String.Empty);
                HtmlEncode = displayFormatAttribute.GetPropertyValue(a => a.HtmlEncode, true);
            }
 
            public DisplayAttribute DisplayAttribute { get; private set; }
 
            public bool ApplyFormatInEditMode { get; private set; }
 
            public bool ConvertEmptyStringToNull { get; private set; }
 
            public string DataFormatString { get; private set; }
 
            public DataTypeAttribute DataTypeAttribute { get; private set; }
 
            public object DefaultValue {
                get {
                    return DefaultValueAttribute.GetPropertyValue(a => a.Value, null);
                }
            }
 
            private DefaultValueAttribute DefaultValueAttribute { get; set; }
 
            public string Description {
                get {
                    return DisplayAttribute.GetLocalizedDescription() ??
                        DescriptionAttribute.GetPropertyValue(a => a.Description, null);
                }
            }
 
            private DescriptionAttribute DescriptionAttribute { get; set; }
 
            public string DisplayName {
                get {
                    return DisplayAttribute.GetLocalizedName() ??
                        DisplayNameAttribute.GetPropertyValue(a => a.DisplayName, null);
                }
            }
 
            public string ShortDisplayName {
                get {
                    return DisplayAttribute.GetLocalizedShortName();
                }
            }
 
            private DisplayNameAttribute DisplayNameAttribute { get; set; }
 
            public string FilterUIHint { get; private set; }
 
            public EditableAttribute EditableAttribute { get; private set; }
 
            public bool IsReadOnly { get; private set; }
 
            public string NullDisplayText { get; private set; }
 
            public string Prompt {
                get {
                    return DisplayAttribute.GetLocalizedPrompt();
                }
            }
 
            public RequiredAttribute RequiredAttribute { get; private set; }
 
            public ScaffoldColumnAttribute ScaffoldColumnAttribute { get; private set; }
 
            public StringLengthAttribute StringLengthAttribute { get; private set; }
 
            public string UIHint { get; private set; }
 
            private DataTypeAttribute GetDefaultDataTypeAttribute() {
                if (Column.IsString) {
                    if (Column.IsLongString) {
                        return new DataTypeAttribute(DataType.MultilineText);
                    }
                    else {
                        return new DataTypeAttribute(DataType.Text);
                    }
                }
 
                return null;
            }
 
            private string GetHint<T>(Func<T, string> presentationLayerPropertyAccessor, Func<T, string> hintPropertyAccessor) where T : Attribute {
                var uiHints = Attributes.OfType<T>();
                var presentationLayerNotSpecified = uiHints.Where(a => String.IsNullOrEmpty(presentationLayerPropertyAccessor(a)));
                var presentationLayerSpecified = uiHints.Where(a => !String.IsNullOrEmpty(presentationLayerPropertyAccessor(a)));
 
                T uiHintAttribute = presentationLayerSpecified.FirstOrDefault(a => presentationLayerPropertyAccessor(a).ToLower(CultureInfo.InvariantCulture) == "webforms" ||
                                                                                   presentationLayerPropertyAccessor(a).ToLower(CultureInfo.InvariantCulture) == "mvc") ??
                                                  presentationLayerNotSpecified.FirstOrDefault();
 
                return uiHintAttribute.GetPropertyValue(hintPropertyAccessor);
            }
 
 
            public bool HtmlEncode {
                get; set;
            }
        }
 
        #endregion
 
        string IMetaColumn.Description {
            get {
                return Description;
            }
        }
 
        string IMetaColumn.DisplayName {
            get {
                return DisplayName;
            }
        }
 
        string IMetaColumn.Prompt {
            get {
                return Prompt;
            }
        }
 
        string IMetaColumn.ShortDisplayName {
            get {
                return ShortDisplayName;
            }
        }
 
        IMetaTable IMetaColumn.Table {
            get {
                return Table;
            }
        }
 
        IMetaModel IMetaColumn.Model {
            get {
                return Model;
            }
        }
    }
}