jump to navigation

Custom Attributes with Extension Methods: Resource Key July 4, 2011

Posted by codinglifestyle in C#, CodeProject.
Tags: , , , , ,
add a comment

I picked up this technique in my last job to use a custom attribute to contain a resource key. The biggest benefit was all the enums in the system used this attribute which provided a way to translate that enum to text. Take a look at a sample enum:

public enum Mode
{
    [AttributeResourceKey("lblInvalid")]
    Invalid,
    [AttributeResourceKey("lblReview")]
    Review,
    [AttributeResourceKey("lblCheckout")]
    Checkout,
    [AttributeResourceKey("lblOrdered")]
    Ordered
}

Each enum uses the AttributeResourceKey to specify the resource key defined in the resx file. Combined with an extension method we can extend the enum itself to allow us to execute the following:

public void DoOperation(Mode mode)
{
    Log.Info(GetResourceString(mode.ResourceKey()));
    ...
}

The C++ head in me thinks, “why are we using reflection when a static function in a helper class could contain a switch statement to convert the enum to the resource key?”.  Technically this is sufficient and faster.  However, the C# head in me loves the idea that the enum and the resource key are intimately tied together in the same file. There is no helper function to forget to update.  The penalty of reading an attribute is a small price to pay to keep the enum and resource key together in order to increase overall maintainability.

So the first thing I am going to do is define a simple interface for my custom attributes.

public interface IAttributeValue<T>
{
    T Value { get; }
}

All this interface does is define that the custom attribute class itself will define a property called Value of type T. This will be useful when using the generic method, below, for pulling the attribute. Next we define the custom attribute class itself.

    public sealed class AttributeResourceKey : Attribute, IAttributeValue<string>
    {
        private string _resourceKey;
        public AttributeResourceKey(string resourceKey)
        {
            _resourceKey = resourceKey;
        }

        #region IAttributeValue<string> Members
        public string Value
        {
            get { return _resourceKey; }
        }
        #endregion
    }

Notice how simple the above class is. We have a constructor taking a string and a property called Value which returns said string. Now let’s look at the generic method for pulling the attribute.

    public static class AttributeHelper
    {
        /// <summary>
        /// Given an enum, pull out its attribute (if present)
        /// </summary>
        public static TReturn GetValue<TAttribute, TReturn>(object value)
        where TAttribute: IAttributeValue<TReturn>
        {
            FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
            object[] attribs    = fieldInfo.GetCustomAttributes(typeof(TAttribute), false);
            TReturn returnValue = default(TReturn);

            if (attribs != null && attribs.Length > 0)
                returnValue = ((TAttribute)attribs[0]).Value;

            return returnValue;
        }
    }

The code above is the heart of code. It uses generics so you need only define this code once in a static class. By passing the attribute and return type we can extract our Value defined by IAttributeValue<TReturn>.  Using the where constraint on TAttribute allows the generic method to know this type defines a property called Value of type TReturn.  This exposes the true power of generics as without this constraint the method could only presume TAttribute is nothing more than an object.  This might tempt you to wrongly cast TAttribute in order to access it’s properties inviting an exception only seen at runtime.

Now to define our extension method, to be placed in a common namespace, to extend all enums with the ResourceKey() method.

    public static class EnumerationExtensions
    {
        /// <summary>
        /// Given an enum, pull out its resource key (if present)
        /// </summary>
        public static string ResourceKey(this Enum value)
        {
            return AttributeHelper.GetValue<AttributeResourceKey, string>(value);
        }
    }

Thanks to the generic attribute helper the above extension method looks trivial. We simply use the helper to return the resource key and now we’ve extended all our enums to have this useful property.

Advertisements

Change Background Color of Invalid Controls (ASP.NET Validator) September 16, 2009

Posted by codinglifestyle in ASP.NET, CodeProject.
Tags: , , , ,
11 comments

I was working with a customer who has invested a lot in redoing the validation in their web application.  I accedentially suggested wouldn’t it be nice if we could change the background or border of the field in question.  The customer loved this idea which meant I’d just created more work for myself.  After searching about I wasn’t finding this done for me, so I actually had to write some code instead of cutting and pasting.

If you set a breakpoint on WebForm_OnSubmit and step in you can check out the .NET validator script.  Key thing here is a global array called Page_Validators.  From here it is fairly trivial to test and change something about the control in question.

function fnOnUpdateValidators()

{

for (var i = 0; i < Page_Validators.length; i++)

{

var val = Page_Validators[i];

var ctrl = document.getElementById(val.controltovalidate);

if (ctrl != null && ctrl.style != null)

{

if (!val.isvalid)

ctrl.style.background=“#FFAAAA”;

else

ctrl.style.backgroundColor = “”;

}

}

}

 

Of course, one problem is if the control already had a background color, it would be lost.  This can be circumvented by storing the old value in an attribute or elsewhere.  Also, custom validators are a special case so you will need to add the appropriate changes for fields validated in a custom way.

To make sure my function is called I use the deprecated Page.RegisterOnSubmitStatement(“val”, “fnOnUpdateValidators();”).  This will call my function after the validators have fired so the isvalid is up to date.

Composite Controls: Dude, where’s my data? June 25, 2009

Posted by codinglifestyle in ASP.NET, C#, CodeProject.
Tags: , , , , , , , , ,
3 comments

I started my .NET career writing WebParts for SharePoint 2003.  You would think this would make me a bit of a server control expert, as WebParts are essentially gussied up server controls.  Yet, I’ve officially wasted 2 days on an age old composite control problem which typically involves these kinds of google searches:

  • server control postback missing data
  • postback server control child control missing
  • composite control event not firing
  • webcontrol createchildcontrol postback control
  • viewstate webcontrol data missing empty blank

Sound familiar?  I understand your pain and hope I can help.  Composite controls can be tricky, if they aren’t set up just right even the most basic example won’t work properly.

First, keep in mind there are two types of custom controls developers typically write.  There are user controls (.ascx) and server controls (.cs).   We will focus on a server control.  One gotcha for a composite control is using the right base class*.  We want to use the CompositeControl base class, not WebControl or Control.  This will tell ASP.NET to ensure the IDs of our child controls are unique (via INamingContainer).  This is simple but very important, in order for ASP.NET to wire up events and serialize ViewState the ID of a control needs to be identical at all times.  So only assign a literal string to your child control and let .NET worry about making it unique.

Now there are two cases to consider, the first is where our composite server control creates a static set of controls.  This is a straightforward case, because we can create the controls at any time.  The most important function in a server control is CreateChildControls().   This is where you can create and assign your controls to member variables.  We can have properties and get and set values straight to our member controls.   In every property just call EnsureChildControls() in each get and set.

private TextBox _TextBox;

[Bindable(true), Category(“TextBoxEx”), DefaultValue(“”), Localizable(true)]

public string Text

{

get

{

EnsureChildControls();

return _TextBox.Text;

}

set

{

EnsureChildControls();

_TextBox.Text = value;

}

}

 

protected override void CreateChildControls()

{

_TextBox = new TextBox();

_TextBox.ID = “_Text”;

 

Controls.Add(_TextBox);

}

 

In reality we may have properties or logic which determine which controls are created or how our control behaves.  This is a complicated scenario, because we cannot create the controls before the logic has been initialized (otherwise our logic will not know which controls to dynamically create)!  In this case, we want our control’s own properties independent of the child controls and we’ll store this information in ViewState, not a control.  We want to avoid calling EnsureChildControls() and delay calling CreateChildControls() prematurely.  This allows the control to be initialized first so that when CreateChildControls() is called our logic will know which controls to create.  First let’s see how to store a property in ViewState.

 

private const string STR_CreateTextBox       = “CreateTextBox”;

public bool CreateTextBox

{

get

{

if (ViewState[STR_CreateTextBox] == null)

return false;

else return (bool)ViewState[STR_CreateTextBox];

}

set

{

ViewState[STR_CreateTextBox] = value;

}

}

If you were wondering if ViewState was the right place to store properties the answer is maybe.  For a bound property it is overkill as it will be set back to its original value every postback.  In that case we simply need a property with a backing store (or in C# 3.0+ a simple get; set; will do).  But when our logic needs to be stored ViewState is the place to persist it.  Just remember that the dynamically created controls don’t need ViewState of their own so we’ll be sure to turn that off when we create them.  The right place to create and add our dynamic controls is in CreateChildControls().  Let’s create a TextBox based on some logic stored in the composite control’s ViewState.

protected override void CreateChildControls()

{

if (CreateTextBox)

{

_TextBox = new TextBox();

_TextBox.ID = “_Text”;

_TextBox.EnableViewState = false;

_TextBox.TextChanged += new EventHandler(Text_TextChanged);

Controls.Add(_TextBox);

}

}

void Text_TextChanged(object sender, EventArgs e)

{

string sText = ((TextBox)sender).Text;

}

Lets take a look at what we’re doing here.  We are dynamically creating our TextBox control in CreateChildControls().  We are setting the ID to a literal string, ASP.NET will make sure our name is unique because we inherit from CompositeControl.  We are setting EnableViewState to false because, as discussed, our composite control is already taking care of ViewState.  We are adding an event as an example as this is the right place to setup any events you might need.

Now here is the interesting bit:  How do we get the user’s value back from a dynamic TextBox?  Take another look at the property Text above and note that the getter calls CreateChildControls().  This will ensure our textbox is recreated and the form will syncronize the user’s form data back in to the textbox.  We could also capture the value using the TextBox’s text changed event to do some processing or whatever.  With this event, when we postback, our Text_TextChanged event will fire before a OK button’s click event on the page due its place in the control hierarchy.  This allows our event to manipulate the TextBox’s text value before our OK button’s click event occurs.

I’ll just note that you may be reading some advice on forum’s to override OnInit.  Contrary to that advice, CreateChildControls() is the right place to dynamically create your controls and events.  OnPreRender() is a great place for initialization as it is called after all properties are set.  And, of course, Render() gives you complete control on how your control will be drawn.

* Now I have to mention Repeater.  Repeater may cause a lot of pain with your server controls.  You may see your control working properly outside a repeater and suddenly all go pearshaped when used with one.  After a lot of trial and error I discovered this had to do with the ID assigned to the dynamic controls.  We know ASP.NET depends on the ID being identical at all points for events and serialization with the form and viewstate to take place.    Sad to say when our composite control is inside a Repeater we can not trust our IDs to .NET.  So we do not want to inherit from CompositeControl or INamingContainer.  Instead, assign a unique id yourself.  Being that it is a repeater, this can not be a literal string because no 2 controls can have the same ID.  Instead try _TextBox.ID = this.ID + “_Text”;

So although there are several points and gotchas to consider, think about using a custom control to lend some OO design to your UI.  Be sure to unit test on a simple website to make development of the control easier.  Good luck!

DataGrid ITemplate TemplateColumn HyperLinkColumn implmenting dynamic URL May 3, 2006

Posted by codinglifestyle in ASP.NET, C#.
Tags: , , , ,
add a comment

Recently I had to replace my HyperLinkColumn on a DataGrid as I was merging data from two datasources and needed to handle a click on the column differently depending on the datasource.  By using a TemplateColumn I reproduced the functionality of a HyperLinkColumn while being able to change the URL depending on the datasource.

TemplateColumn tCol=new TemplateColumn();
tCol.ItemTemplate=new TColumn(sColumn);

dgColumns.Add(tCol);

  public class TColumn: ITemplate
  {
   private string m_sCol;
   public TColumn(string sCol)
   {
    m_sCol   = sCol;
   }

   public void InstantiateIn(Control container)
   {
    HyperLink h = new HyperLink();
    h.DataBinding +=
     new EventHandler(this.OnDataBinding);
    container.Controls.Add(h);
   }

   public void OnDataBinding(object sender, EventArgs e)
   {
    HyperLink h = (HyperLink) sender;
    DataGridItem container = (DataGridItem) h.NamingContainer;
    h.Text = ((DataRowView)container.DataItem)[m_sCol].ToString();

    string sUrl;
    bool bTest;
    //Perform test
    if (bTest)
    {
        sUrl = “http://news.yahoo.com“;
    } else
    {
        sUrl = http://www.cnn.com;
    }
    h.NavigateUrl = sUrl;
    h.Target = “_blank”;
   }
  }