I was looking at the generated site which ASP.NET Dynamic Data had provided and thought, I wonder how I can choose which columns I want to display based on the model itself.  This is then dynamic, in the way that for one model I show one column, but for the others I may show five, or six etc…

image

I have been following the posts from Matt Berseth, where he exploits the attributes from the System.ComponentModel namespace.  He shows how you can create a nice nested menu based on Category and Display Name attributes. 

So, this got me thinking, along the lines of, it is very convenient to use attributes in order to determine outcomes when working with meta data, and in this case, which columns I want to display inside the List.aspx template which you get provided out of the box as a standard template.  Alteration more than anything but before that, I need to set a few things up.

The MetaDataModels

These are the classes which dictate what meta information are available for your entities at runtime.  You assign the relevant meta data model to the entity using an attribute like the following:

[MetadataType(typeof(ProductMetadata))]
 public partial class Product
 {
     public Product(){

     }
 }

I then create another class which is the actual ProductMetaData.

[Category("Products")]
public class ProductMetadata
{
    [ScaffoldColumn(false)]
    public int ProductID{get;set;}

    [ListVisible]
    public string ProductCode{get;set;}
}

Now in the second example above you will notice the ListVisible attribute, this is custom and is defined simply like this:

[AttributeUsage(AttributeTargets.Property)]
public class ListVisibleAttribute : Attribute
{

}

The Column Generators

Once this is done you can then apply it to properties inside your class.  I only want to use this for Properties so I limit its scope using the AttributeUsage attribute.  So the idea now is this, once we have the meta table we are going to bind with, we want to then extract all the columns which have the attribute ListVisible.  I don’t want to create a new template though, not sure how yet lol, so I just want to edit the current.  The GridView auto generates its columns and something which I rooted out today was the ColumnGenerator type, or rather the IAutoFieldGenerator interface.  When you provide a GridView with this, it then knows which columns should be auto generated, so inline with this example I want to make a column generator which gets all the columns with the ListVisible attribute.

public class GridViewMetaColumnGenerator :IAutoFieldGenerator
{
    protected MetaTable table;

    public GridViewMetaColumnGenerator(MetaTable table)
    {
        this.table = table;
    }

    #region IAutoFieldGenerator Members

    public System.Collections.ICollection GenerateFields(Control control)
    {
        DataControlFieldCollection columns = new DataControlFieldCollection();

        foreach (MetaColumn col in table.Columns)
        {
            if (col.GetListVisible())
            {
                BoundField column = new BoundField();
                column.DataField = col.Name;
                column.SortExpression = col.Name;
                column.HeaderText = col.Name;
                columns.Add(column);
            }
        }

        return columns;
    }

    #endregion
}

So, i create the class which takes one argument in the constructor which is the MetaTable.  The GenerateFields method will be called by the GridView, so inside here I loop through the columns inside the MetaTable and if the column is decorated with the ListVisible attribute, I add it to the DataControlFieldCollection.  You will notice some familiar properties of the BoundField above.  The method GetListVisible is an extension method which I made, and simply checks for the presence of the attribute on the MetaColumn:

public static bool GetListVisible(this MetaColumn column)
{
    return column.Attributes[typeof(ListVisibleAttribute)] != null;
}

All that is left to do now is to alter the List.aspx template to include the column generator.  Below is the updated Page_Load event:

protected void Page_Load(object sender, EventArgs e)
{
    table = GridDataSource.GetTable();
    Title = table.DisplayName;
    InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert);
    // Disable various options if the table is readonly

    GridViewMetaColumnGenerator metaColumnsGenerator = new GridViewMetaColumnGenerator(table);

    GridView1.ColumnsGenerator = metaColumnsGenerator;

    if (table.IsReadOnly)
    {
        GridView1.Columns[0].Visible = false;
        InsertHyperLink.Visible = false;
    }

}

And it works a treat, and if this is not the best way, I still think it is kind of clean.  Well I hope this helps some people as I appreciate this is new territory and I my self am constantly on the look out for helpful tutorials trying to open up the topic.

Cheers for now:

Andrew

UPDATE 27th March 2008

Although the above does work to an extent it does not work completely.  For one thing I have totally destroyed the functionality of the Dynamic Controls which are rendered based on the UIHint.  So, from reading about the internet I have found a few things including the fact that IAutoFieldGenerator has been used by the community in the ASP.NET Dynamic Data area for some time now.  The change required is the type which I am returning inside the GenerateFields method.  I currently instantiate BoundFields, where as I should be instantiating Dynamic Fields.

I also integrated a new field template which handles Many To Many relationships and that I got from this kind fellow at Micrsoft, David Ebbo.  Lots of Dynamic Data topics on here.

Another update I should also make the code above, is to provided further funationality on the custom attribute.  I am basically limiting its functionality to simply Lists.  Instead of that it would be better if you could state Flags for the type of Page Template the certain columns should be visible in.  Again this has already been done after searching round the internet.  So basically it provides a more robust approach than mine above.  Here is the example: http://stuartmanning.com/blogs/aspnet/archive/2009/01/26/dynamic-data-hiding-columns-in-selected-pagetemplates.aspx

So having not yet integrated that into my project I have still got the following as my GenerateFields method:

public System.Collections.ICollection GenerateFields(Control control)
{
    // Auto-generate fields from metadata.
    var fields = new List<DynamicField>();
    foreach (MetaColumn column in table.Columns)
    {
        // Skip column that shouldn't be scaffolded
        if (!column.Scaffold) continue;

        // Don't display long string in controls that show multiple items
        // REVIEW: we should avoid hard coding a list of control, but IDataBoundControl does not give us this info
        if ((column.IsLongString && control is GridView) || !column.GetListVisible())
            continue;


        // If it's a Many To Many column, use our special field template
        string uiHint = null;
        if (column.Provider.Association != null && column.Provider.Association.Direction == AssociationDirection.ManyToMany)
        {
            uiHint = "ManyToMany";
        }

        fields.Add(new DynamicField() { DataField = column.Name, UIHint = uiHint });
    }

    return fields;
}

Wednesday, March 25, 2009 9:09:39 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer