Generic Access to ASP.NET Dynamic Data UIHint Attribute Values

Yesterday I published a post titled, Passing Arguments to a Dynamic Data Field Template from a UIHint Attribute. The purpose of that post is to show you how you can access the UIHint argument values in a dynamic data field template. Today I want to show you how to take the next step.

The Wrap Up

After spending a little more time with my site I realized there is a better way to access the UIHint argument values. I don’t like writing code over and over again that checks for the existence of items. A simple method call that takes care of all the implementation details was in order.

First I started with the following method that looks for string-based UIHint arguments:

using System.ComponentModel.DataAnnotations;

protected string GetUIHintArg(string key)

{

    UIHintAttribute hint = null;

    string returnValue = string.Empty;

    hint = (UIHintAttribute)this.Column.Attributes[typeof(UIHintAttribute)];

    if (hint != null)

    {

        if (hint.ControlParameters.ContainsKey(key))

        {

            returnValue = hint.ControlParameters[key].ToString();

        }

    }

    return returnValue;

}

This is basically the same code as described in my last post, but the key difference here is that I consolidated two “if” statements down to one. I was able to tighten the code up by using the ContainsKey method to test for the existence for the desired key/value pair. This approach is not only more succinct, but also will not throw an exception if there are no arguments defined in the dictionary. Having this method is convenient because you may only define hints as string in the attribute, so you will probably use this method a lot.

Going Beyond Strings

Strings are great, but I also want to pass in other types like booleans and enumerations. In my control I am using a boolean to decide if I should overwrite existing data. The control can also emit the user name or the date and time – so I wanted an enumeration to help the control know how to format itself.

To handle native types and enumerations, I implemented the same method while capitalizing on the use of generics.

As an example, consider the following UIHint attribute:

[UIHint("AuditField", null, "Overwrite", "true")]

Knowing that the “Overwrite” argument is a boolean type, there should have an easy way to get the value back.

Here’s how:

this.overwrite = this.GetUIHintArg<bool>("Overwrite");

By passing in the expected type of the object, the returned value is pre-converted to the appropriate data type.

Here’s the new generic method:

Update: Scott Hanselman suggested in the comments a way to get rid of a very ugly switch statement I was previously using. The code is now updated per his suggestion. Thanks, Scott!

protected T GetUIHintArg<T>(string key)

{

    UIHintAttribute hint = null;

    T returnValue = default(T);

    string value = string.Empty;

 
    hint = (UIHintAttribute)this.Column.Attributes[typeof(UIHintAttribute)];

 
    if (hint != null)

    {

        if (hint.ControlParameters.ContainsKey(key))

        {

            value = hint.ControlParameters[key].ToString();

            var converter = TypeDescriptor.GetConverter(typeof(T));

            returnValue = (T)converter.ConvertFromInvariantString(value);

        }

    }

 
    return returnValue;

}

The only real change here is that the method is asking what type to expect to find the argument and then casting the value to that type.

This method will work with any custom enumeration as well. Consider the following enumeration and UIHint:

public enum DisplayTypes

{

    Undefined,

    UserName,

    DateTime

}

[UIHint("AuditField", null, "DisplayType","UserName")]

The above arguments tell the template to display the user name when the control is rendered. Here is the code used to access this data:

this.displayType = this.GetUIHintArg<DisplayTypes>("DisplayType");

Placement is Everything

To get the most out of this method, the best place to keep it is in a base class that wraps up System.Web.DynamicData.FieldTemplateUserControl. Once all your field templates inherit from your new base class then this feature is available at all times!

 
Thanks to George Durzi who gave some help thinking though some of the code on this post. 

9 Comments

  • Hi Craig,

    I am building support for Dynamic Data in the next upgrade to Peter's Data Entry Suite. I've created a new FieldTemplateUserControl base class to use (subclassed from the original with a bunch of enhancements.) One of these enhancements is your request, to add it in the base class.

    Here's my current code. (While its from a commercial application, I agree to allow it used freely by all.) The ApplyUIHintControlParms() method is called from OnPreRender or OnInit, depending on if the user wants the UIHintAttribute to override their own settings (including those from the properties assigned to the DynamicControl that have the same capabilities.)

    ///
    /// UIHintAttribute lets the user deliver name/value pairs to be assigned to
    /// properties on the FieldTemplate. This applies them.
    ///
    ///
    /// Requires a UIHintAttribute assigned. (The UIHint can omit the actual hint name so
    /// it uses the default template.)

    /// Gets the name/value pairs from UIHintAttribute.ControlParameter.
    /// Each ControlParameter.name is used as a property name on the FTUC.
    /// If the name doesn't match (case insensitive), it is ignored instead of throwing an exception.
    /// The trace will report it though.

    ///
    public virtual void ApplyUIHintControlParms()
    {
    UIHintAttribute vUIHintAtt = Column.Attributes.OfType().FirstOrDefault();
    if ((vUIHintAtt != null) && (vUIHintAtt.ControlParameters != null))
    foreach (string vName in vUIHintAtt.ControlParameters.Keys)
    {
    object vValue = vUIHintAtt.ControlParameters[vName]; // may be null
    try
    {
    PropertyInfo vPropertyInfo = this.GetType().GetProperty(vName, BindingFlags.Public | BindingFlags.Instance);
    if (vPropertyInfo != null)
    {
    // if PropertyType is System.Type, no type converter is offered. Instead, translate it manually
    if (vPropertyInfo.PropertyType == typeof(Type))
    {
    if (vValue is string)
    {
    Type vType = Type.GetType((string) vValue, false, true);
    vPropertyInfo.SetValue(this, vType, null);
    }
    // ignored if not a string type
    }
    else
    {
    TypeConverter vTypeConverter = TypeDescriptor.GetConverter(vPropertyInfo.PropertyType);
    if (vTypeConverter == null)
    {
    if (Page.Trace.IsEnabled)
    Page.Trace.Write("UIHint on column " + ColumnName + " of table " + Table.Name + " is specifies the property name " + vName + " on FieldTemplate type " + this.GetType().FullName + ". The property VALUE is not legal for the property whose type is " + vPropertyInfo.PropertyType.FullName + ".");
    continue;
    }
    // convert and set it
    object vConvertedValue = vTypeConverter.ConvertFrom(vValue);
    vPropertyInfo.SetValue(this, vConvertedValue, null); // may throw a TargetException in design mode when in VS2005/8
    }
    }
    else if (Page.Trace.IsEnabled)
    Page.Trace.Write("UIHint on column " + ColumnName + " of table " + Table.Name + " is specifies the property name " + vName + " on FieldTemplate type " + this.GetType().FullName + ". This property does not exist and has been ignored. The property name is case sensitive.");
    }
    catch (Exception pE)
    {
    if (Page.Trace.IsEnabled)
    Page.Trace.Warn("UIHint on column " + ColumnName + " of table " + Table.Name + " is incorrectly setup and caused an exception (which has been ignored.). The ControlParameter with the problem is named " + vName + " and its value is " + (vValue != null ? vValue.ToString() : "null") + ".");
    }
    }
    } // ApplyUIHintControlParameters

  • Rather than that switch statement, can't you do something like:

    var foo = TypeDescriptor.GetConverter(T)
    foo.ConvertFromInvariantString(bar);

  • Peter: Thanks for sharing your code! I will add it to my solution and play around with it.

    Scott: Thanks for the tip. I knew that switch was really nasty :) I updated the post with your suggestion.

    - Craig

  • @Jacob : so are you saying it would keep looking for another argument of the same type if it doesn't find the one specified?

  • Not exactly, here is what I mean:
    ////
    protected T GetUIHintArg(string key, T defaultValue)
    {
    UIHintAttribute hint = null;
    T returnValue = defaultValue;
    string value = string.Empty;

    hint = (UIHintAttribute)this.Column.Attributes[typeof(UIHintAttribute)];
    if (hint != null)
    {
    if (hint.ControlParameters.ContainsKey(key))
    {
    value = hint.ControlParameters[key].ToString();
    var converter = TypeDescriptor.GetConverter(typeof(T));
    returnValue = (T)converter.ConvertFromInvariantString(value);
    }
    }

    return returnValue;
    }
    ////

    This will simply make the usage of the function more convinient. You shouldn't check for null in the return value after every call.
    Examples:
    TextBox1.Text = base.GetUIHintArg("DefaultValue", null);
    or
    TextBox1.Text = base.GetUIHintArg("DefaultValue", DateTime.Now);
    or
    if (base.GetUIHintArg("AutoFill", false))
    TextBox1.Text = DateTime.Now;

  • Jacob:

    Ahh I see... I like it!

    Craig

  • How would you get at the UIHint from your MetadataType without going through a dynamic data field?

  • I implemented this as an extension method instead of replacing the FieldTemplateUserControl class. Works beautifully.

    using System;
    using System.Web.DynamicData;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel;

    public static class Extensions
    {
    public static T GetUIHintArg(this FieldTemplateUserControl c, string key)
    {
    UIHintAttribute hint = null;
    T returnValue = default(T);
    string value = string.Empty;

    hint = (UIHintAttribute)c.Column.Attributes[typeof(UIHintAttribute)];

    if (hint != null)
    {
    if (hint.ControlParameters.ContainsKey(key))
    {
    value = hint.ControlParameters[key].ToString();
    var converter = TypeDescriptor.GetConverter(typeof(T));
    returnValue = (T)converter.ConvertFromInvariantString(value);
    }
    }

    return returnValue;
    }
    }

  • thanks for your post. good.

Comments have been disabled for this content.