Sunday, 12 October 2008

Dynamic Data – Custom Metadata Providers *** UPDATED 20081110 ***

This is really just an addition to Matt Berseth's article Dynamic Data And Custom Metadata Providers from August 24, 2008, all I wanted to do was add the same features to the Table/Class not just the Columns/Properties. So you can see the full explanation over at Matt Berseth's blog. So here are the listings:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web;

public class DefaultAttributesTypeDescriptionProvider : System.ComponentModel.TypeDescriptionProvider
{
    private Type Type { get; set; }

    public DefaultAttributesTypeDescriptionProvider(Type type)
        : this(type, TypeDescriptor.GetProvider(type))
    {
        this.Type = type;
    }

    public DefaultAttributesTypeDescriptionProvider(Type type, TypeDescriptionProvider parentProvider)
        : base(parentProvider)
    {
        this.Type = type;
    }

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        return new DefaultAttributesTypeDescriptor(base.GetTypeDescriptor(objectType, instance), this.Type);
    }
}

Listing 1 – TypeDescriptorProvider

No change in Listing 1 from what Matt has already done just some renaming to make it a bit clearer to me what's going on.

using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Reflection;

public class DefaultAttributesTypeDescriptor : CustomTypeDescriptor
{
    private Type Type { get; set; }

    public DefaultAttributesTypeDescriptor(ICustomTypeDescriptor parent, Type type)
        : base(parent)
    {
        this.Type = type;
    }

    /// <summary>
    /// Returns the collection of attributes for a given table
    /// </summary>
    /// <returns>AttributeCollection</returns>
    public override AttributeCollection GetAttributes()
    {
        AttributeCollection baseAttributes = base.GetAttributes();

        if (baseAttributes.OfType<DisplayNameAttribute>().FirstOrDefault() == null)
        {
            List<Attribute> extraAttributes = new List<Attribute>();

            // generate the display name
            String friendlyDisplayName = base.GetClassName().ToTitleFromPascal();

            // add it to the list
            extraAttributes.Add(new DisplayNameAttribute(friendlyDisplayName));

            // only create a new collection if it is necessary
            return AttributeCollection.FromExisting(baseAttributes, extraAttributes.ToArray());
        }
        else
        {
            return baseAttributes;
        }
    }

    /// <summary>
    /// Returns a collection of properties (columns) for the type,
    /// each with attributes for that table
    /// </summary>
    /// <returns>PropertyDescriptorCollection</returns>
    public override PropertyDescriptorCollection GetProperties()
    {
        List<PropertyDescriptor> propertyDescriptors = new List<PropertyDescriptor>();

        foreach (PropertyDescriptor propDescriptor in base.GetProperties())
        {
            List<Attribute> newAttributes = new List<Attribute>();

            // Display Name Rules ...
            // If the property doesn't already have a DisplayNameAttribute defined
            // go ahead and auto-generate one based on the property name
            if (!propDescriptor.HasAttribute<DisplayNameAttribute>())
            {
                // generate the display name
                String friendlyDisplayName = propDescriptor.Name.ToTitleFromPascal();

                // add it to the list
                newAttributes.Add(new DisplayNameAttribute(friendlyDisplayName));
            }

            // Display Format Rules ...
            // If the property doesn't already have a DisplayFormatAttribute defined
            // go ahead and auto-generate one based on the property type
            if (!propDescriptor.HasAttribute<DisplayFormatAttribute>())
            {
                // get the default format for the property type
                String displayFormat = propDescriptor.PropertyType.GetDisplayFormat();

                // add it to the list
                newAttributes.Add(new DisplayFormatAttribute() { DataFormatString = displayFormat });
            }

            propertyDescriptors.Add(new WrappedPropertyDescriptor(propDescriptor, newAttributes.ToArray()));
        }

        // return the descriptor collection
        return new PropertyDescriptorCollection(propertyDescriptors.ToArray(), true);
    }

    private class WrappedPropertyDescriptor : PropertyDescriptor
    {
        private PropertyDescriptor _wrappedPropertyDescriptor;

        public WrappedPropertyDescriptor(PropertyDescriptor wrappedPropertyDescriptor, Attribute[] attributes)
            : base(wrappedPropertyDescriptor, attributes)
        {
            _wrappedPropertyDescriptor = wrappedPropertyDescriptor;
        }

        public override bool CanResetValue(object component)
        {
            return _wrappedPropertyDescriptor.CanResetValue(component);
        }

        public override Type ComponentType
        {
            get { return _wrappedPropertyDescriptor.ComponentType; }
        }

        public override object GetValue(object component)
        {
            return _wrappedPropertyDescriptor.GetValue(component);
        }

        public override bool IsReadOnly
        {
            get { return _wrappedPropertyDescriptor.IsReadOnly; }
        }

        public override Type PropertyType
        {
            get { return _wrappedPropertyDescriptor.PropertyType; }
        }

        public override void ResetValue(object component)
        {
            _wrappedPropertyDescriptor.ResetValue(component);
        }

        public override void SetValue(object component, object value)
        {
            _wrappedPropertyDescriptor.SetValue(component, value);
        }

        public override bool ShouldSerializeValue(object component)
        {
            return _wrappedPropertyDescriptor.ShouldSerializeValue(component);
        }
    }
}

Listing 2 – CustomTypeDescriptor

Here all I’ve done is again some renaming and add a GetAttributes method which returns the attributes on the class/table. The section is marked in BOLD ITALIC for emphasis. What it does is check to see if the DisplayNameAttriburte is set and if not then auto generates it.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.DynamicData;
using Microsoft.Web.DynamicData;

public static class HelperExtansionMethods
{
    public static ColumnOrderAttribute ColumnOrdering(this MetaColumn column)
    {
        return column.Attributes.OfType<ColumnOrderAttribute>().DefaultIfEmpty(ColumnOrderAttribute.Default).First();
    }

    public static Boolean IsHidden(this MetaColumn column, PageTemplate currentPage)
    {
        var hideIn = column.Attributes.OfType<HideColumnInAttribute>().DefaultIfEmpty(new HideColumnInAttribute()).First() as HideColumnInAttribute;
        return hideIn.PageTemplates.Contains(currentPage);
    }

    /// <summary>
    /// Converts a Pascal type Name to Title
    /// i.e. MyEmployees becomes My Employees
    ///      OrderID becomes Order ID etc
    /// </summary>
    /// <param name="s">String to convert</param>
    /// <returns>Title String</returns>
    public static String ToTitleFromPascal(this String s)
    {
        // remove name space
        String s0 = Regex.Replace(s, "(.*\\.)(.*)", "$2");
// add space before Capital letter String s1 = Regex.Replace(s0, "[A-Z]", " $&"); // replace '_' with space String s2 = Regex.Replace(s1, "[_]", " "); // replace double space with single space String s3 = Regex.Replace(s2, " ", " "); // remove and double capitals with inserted space String s4 = Regex.Replace(s3, "(?<before>[A-Z])\\s(?<after>[A-Z])", "${before}${after}"); // remove and double capitals with inserted space String sf = Regex.Replace(s4, "^\\s", ""); // force first character to upper case return sf.ToTitleCase(); } public static String ToTitleCase(this String text) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < text.Length; i++) { if (i > 0) { if (text.Substring(i - 1, 1) == " " || text.Substring(i - 1, 1) == "\t" || text.Substring(i - 1, 1) == "/") sb.Append(text.Substring(i, 1).ToString().ToUpper()); else sb.Append(text.Substring(i, 1).ToString().ToLower()); } else sb.Append(text.Substring(i, 1).ToString().ToUpper()); } return sb.ToString(); } public static Boolean HasAttribute<T>(this PropertyDescriptor descriptor) where T : Attribute { Boolean value = false; for (int i = 0; i < descriptor.Attributes.Count && !value; i++) { value = (descriptor.Attributes[i] is T); } return value; } public static String GetDisplayFormat(this Type type) { string defaultFormat = "{0}"; if (type == typeof(DateTime) || type == typeof(Nullable<DateTime>)) { defaultFormat = "{0:d}"; } else if (type == typeof(decimal) || type == typeof(Nullable<decimal>)) { defaultFormat = "{0:c}"; } return defaultFormat; } }
Listing 3 – Helper extension methods 
UPDATED: 2008/11/10 altered ToTitleFromPascal extension method to remove namespace and to force to title case in cases where Table or column name is:
EntitiesModel.MyEntity
or
tbl_name_of_table

I’ve moved my helper extension methods out of the class and rewritten the ToHumanFromPascal as ToTitleFromPascal and used Regex to do my converting.

I hope Matt doesn't mind. smile_teeth

5 comments:

Mark said...

I get an error on the line:

if (baseAttributes.OfType<DisplayNameAttribute>().FirstOrDefault() == null)

'System.ComponentModel.AttributeCollection' does not contain a definition for 'OfType' and no extension method 'OfType' accepting a first argument of type 'System.ComponentModel.AttributeCollection' could be found (are you missing a using directive or an assembly reference?)

What am I missing?

- Mark

Steve said...

Hi Mark I think you will need a using System.Linq; in there :D

Steve

Mark said...

That took care of it. Thanks!

Jason Kergosien said...

Steve,

I like where you're going on this.

How would one go about creating providers for the other attribute types (ex. Range, UIHint, Filter, etc.)?

Steve said...

Hi Jason, I'm not sure what you mean? do you mean automating e.g.Range, UIHint, Filter, etc if so how would you go about deciding what UIHine to do on the field?

Again you could do what your asking, and the only issue is deciding how to detect which attribute to add. :D

Steve

Post a Comment