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");Listing 3 – Helper extension methods
// 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; } }
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.
15 comments:
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
Hi Mark I think you will need a using System.Linq; in there :D
Steve
That took care of it. Thanks!
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.)?
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
Thx.
I follow your tutorial but I have this error :
The call is ambiguous between the following methods or properties:
'ExtensionMethods.HasAttribute[System.ComponentModel.DisplayNameAttribute]System.ComponentModel.PropertyDescriptor)'
and
'ExtensionMethods.HasAttribute[System.ComponentModel.DisplayNameAttribute](System.ComponentModel.PropertyDescriptor)'
As you can see, there are the same ! I red something here which help me a little but I still have the problem :
http://stackoverflow.com/questions/398037/asp-net-web-site-or-web-application
(ps : I replace '<' by '[' in the error)
Thx Steve.
Ok, sorry for asking too fast.
I solved the problem by moving each extended method into the coresponding file.
I just need to declare the ExtensionMethods class as partial.
Thx again.
I'm a bit confused what the advantage of the new GetAttributes method is? Sorry, new to Dynamic Data.
Hi, I find that it makes my code more readable.
Steve :D
Is there any way to get access to the entity key in these methods? In particular, in the GetProperties method, if I can access the entity key, I have a lookup table that will be used to provide the descriptions, as a simple regex replacement won't always fit the bill. Thanks!
Hi Xaeryan, do you want to send me an e-mail and I can find out in more detail what it is you want to do please.
Steve :D
Steve,
I started with matt's version and had it working. trying to get your version working, but there's no Microsoft.Web.DynamicData; namespace available in my environment.
Am I missing something?
I suspect that is just a hold over on some class file I was reusing or I copied it from the usings of a similar class. I don't think it should be required but it may if you want it download the Dynamic Data Futures from Codeplex.com and use the futures project dll, but you will find if I am using it it will just be some of the extension methods.
Steve :D
Steve,
The TypeDescriptionProvider seems to ignore any metadata added to the Globals.asax.cs file. Is this the intended functionality?
- Kevin
Hi Kevin, I'm not sure what you mean you don't add Metadata in Glabal.asax.cs using this?
Steve
Post a Comment