Tuesday 20 January 2009

Writing Attributes and Extension Methods for Dynamic Data

This is just a short article on some useful tips I’ve found for writing Attributes for Dynamic Data.

I say Tips there really isn’t that much to this.

Let me show you an attribute I just created for a post on the Dynamic Data Forum:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DisableEditingAttribute : Attribute
{
    public Boolean Disable { get; set; }

    public static DisableEditingAttribute Default = new DisableEditingAttribute();

    public DisableEditingAttribute() : this(false) { }

    public DisableEditingAttribute(Boolean disable)
    {
        Disable = disable;
    }
}

Listing 1 – Disable Editing Attribute

And here’s it being used in the List page:

protected void Page_Load(object sender, EventArgs e)
{
    table = GridDataSource.GetTable();
    Title = table.DisplayName;

    //GridView1.Sort("ColumnName, ColumnName", SortDirection.Descending);

    InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert);

    // Disable various options if the table is readonly
    var editingDisabled = table.Attributes.OfType<DisableEditingAttribute>().DefaultIfEmpty(new DisableEditingAttribute()).FirstOrDefault();
    if (table.IsReadOnly || editingDisabled.Disable)
    {
        GridView1.Columns[0].Visible = false;
        InsertHyperLink.Visible = false;
    }
}

Listing 2 – DisableEditingAttribute in use BOLD ITALIC

The first ting to note is a Linq thing; the DefautIfEmpty extension method. This is used to specify a default value if the returned value is null (From the OfType FirstOrDefault combination) this means I don’t have to test for null like:

// Disable various options if the table is readonly
var editingDisabled = table.Attributes.OfType<DisableEditingAttribute>().FirstOrDefault();
if (table.IsReadOnly || (editingDisabled != null && editingDisabled.Disable))
{
    GridView1.Columns[0].Visible = false;
    InsertHyperLink.Visible = false;
}

Listing 3 – Having to test for null

Another way of doing this would be to use a Default static method to provide the default value.

// Disable various options if the table is readonly
var editingDisabled = table.Attributes.OfType<DisableEditingAttribute>().
    DefaultIfEmpty(DisableEditingAttribute.Default).FirstOrDefault();
if (table.IsReadOnly || editingDisabled.Disable)
{
    GridView1.Columns[0].Visible = false;
    InsertHyperLink.Visible = false;
}

Listing 4 – using the Default static method

Either method works but my preference is to have a default constructor Attribute() and I’ll explain why. But first let me show you an Extension method I found in the Dynamic Data Futures project extension methods:

public static T GetAttribute<T>(this MetaTable table) where T : Attribute
{
    return table.Attributes.OfType<T>().FirstOrDefault();
}

Listing 5 – the GetAttribute Generic extension method.

And it’s use:

// Disable various options if the table is readonly
var editingDisabled = table.GetAttribute<DisableEditingAttribute>();
if (table.IsReadOnly || (editingDisabled != null && editingDisabled.Disable))
{
    GridView1.Columns[0].Visible = false;
    InsertHyperLink.Visible = false;
}

Listing 6 – the GetAttribute extension method in use

This is better more readable code but the test for null makes it a bit messy. So here are my interpretations of the GetAttribute extension method:

public static T GetAttributeOrDefault<T>(this MetaTable table) where T : Attribute, new()
{
    return table.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

public static T GetAttributeOrDefault<T>(this MetaColumn column) where T : Attribute, new()
{
    return column.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

Listing 7 – My GetAttributeOrDefault extension methods

All I’ve done here is use the DefaultIfEmpty Linq extension method and added an extra constraint to the generic class the make the extension method only work with attributes that have an explicit default Constructor.

And just for completeness here are all the generic extension methods together:

public static T GetAttributeOrDefault<T>(this MetaTable table) where T : Attribute, new()
{
    return table.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

public static T GetAttribute<T>(this MetaTable table) where T : Attribute
{
    return table.Attributes.OfType<T>().FirstOrDefault();
}

public static T GetAttributeOrDefault<T>(this MetaColumn column) where T : Attribute, new()
{
    return column.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

public static T GetAttribute<T>(this MetaColumn column) where T : Attribute
{
    return column.Attributes.OfType<T>().FirstOrDefault();
}

Listing 8 – All Generic Attributes together

The one in BOLD ITALIC is the original from Dynamic Data Futures project.

 

I think C# 3.0 is real neat HappyWizard

9 comments:

Anonymous said...

where should i put function "GetAttribute"? thank you

Anonymous said...

where should i put function "GetAttribute"?

Stephen J. Naughton said...

You put them in a seperate public static class like all extension methods.

Steve :D

See C# Version 3.0 - Extension Methods

Anonymous said...

I dont care what your picture looks like. I think your beautiful.
Now tell me how to turn a column filter off.

Stephen J. Naughton said...

Hi Anonymous, and thank you for the compliment :)
you could have a look at this article here Limit the Filter Fields which shows how to hide filters based on a custom attribute.

Steve :D

Joe said...

I found this read only attribute to be extremely helpful for the List.aspx page. But you didn't mention the Details.aspx page that has the Edit and Delete at the bottom of the fields. I had to add the following code on the Page_Load of that page as well in order to hide that as well for read only tables.

// Disable various options if the table is readonly
var editingDisabled = table.Attributes.OfType().DefaultIfEmpty(new DisableEditingAttribute()).FirstOrDefault();
if (table.IsReadOnly || (editingDisabled != null && editingDisabled.Disable))
{
//GridView1.Columns[0].Visible = false;
//InsertHyperLink.Visible = false;
var totalFields = DetailsView1.Fields.Count;
DetailsView1.Fields[totalFields - 1].Visible = false;
}

Stephen J. Naughton said...

Thanks Joe,

Anonymous said...

Error 35 '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?)

How to resolve this error?

Stephen J. Naughton said...

Sounds like you are missing a using for System.Linq

Steve