Thursday 7 August 2008

Dynamic Data and Field Templates - An Advanced FieldTemplate with a GridView ***UPDATED 2008/09/24***

  1. The Anatomy of a FieldTemplate.
  2. Your First FieldTemplate.
  3. An Advanced FieldTemplate.
  4. A Second Advanced FieldTemplate.
  5. An Advanced FieldTemplate with a GridView.
  6. An Advanced FieldTemplate with a DetailsView.
  7. An Advanced FieldTemplate with a GridView/DetailsView Project.

The Idea for this FieldTemplate came from Nigel Basel post on the Dynamic Data forum where he said he needed to have a GridView emended in another data control i.e. FormView so he could use the AjaxToolkit Tab control. So here it is with some explanation.

Files Required for Project

In this project we are going to convert a PageTemplate into a FieldTemplate so in your project you will need to copy the List.aspx and it’s code behind List.aspx.cs to the FieldTemplate folder. When copied rename the file to GridView_Edit.ascx and change the class name to GridView_EditField see Listings 1 & 2.

<%@ Page 
    Language="C#" 
    MasterPageFile="~/Site.master" 
    CodeFile="List.aspx.cs" 
    Inherits="List" %>

to

<%@ Control 
    Language="C#" 
    CodeFile="GridView_Edit.ascx.cs" 
    Inherits="GridView_EditField" %>

Listing 1 – Changing the class name of the GridView_Edit.ascx file

public partial class List : System.Web.UI.Page
{
...
}

to

public partial class GridView_EditField : FieldTemplateUserControl
{
...
}

Listing 2 - Changing the class name of the GridView_Edit.ascx.cs code behind file

Now in the GridView_Edit.ascx file trim out all the page relavent code:

i.e. remove the following tags (and their closing tags where applicable)

<asp:Content
<asp:UpdatePanel
<ContentTemplate>
<%@ Register 
    src="~/DynamicData/Content/FilterUserControl.ascx" 
    tagname="DynamicFilter" 
    tagprefix="asp" %>

<asp:DynamicDataManager 
    runat="server" 
    ID="DynamicDataManager1" 
    AutoLoadForeignKeys="true" />

Also remove from the GridView the columns tags and everything in them, and then add the following properties to the GridView:

AutoGenerateColumns="true"
AutoGenerateDeleteButton="true"
AutoGenerateEditButton="true"

and you should end up with something like Listing 3.

<%@ Control 
    Language="C#" 
    CodeFile="GridView_Edit.ascx.cs" 
    Inherits="GridView_EditField" %>

<%@ Register 
    src="~/DynamicData/Content/GridViewPager.ascx" 
    tagname="GridViewPager" 
    tagprefix="asp" %>

<asp:DynamicDataManager 
    runat="server" 
    ID="DynamicDataManager1" 
    AutoLoadForeignKeys="true" />

<asp:ScriptManagerProxy 
    runat="server" 
    ID="ScriptManagerProxy1" />

<asp:ValidationSummary 
    runat="server" 
    ID="ValidationSummary1" 
    EnableClientScript="true"
    HeaderText="List of validation errors" />
    
<asp:DynamicValidator 
    runat="server" 
    ID="GridViewValidator" 
    ControlToValidate="GridView1" 
    Display="None" />

<asp:GridView 
    runat="server" 
    ID="GridView1" 
    DataSourceID="GridDataSource"
    AllowPaging="True" 
    AllowSorting="True" 
    AutoGenerateColumns="true"
    AutoGenerateDeleteButton="true"
    AutoGenerateEditButton="true"
    CssClass="gridview">

    <PagerStyle CssClass="footer"/> 
           
    <PagerTemplate>
        <asp:GridViewPager runat="server" />
    </PagerTemplate>
    
    <EmptyDataTemplate>
        There are currently no items in this table.
    </EmptyDataTemplate>
    
</asp:GridView>

<asp:LinqDataSource 
    runat="server" 
    ID="GridDataSource" 
    EnableDelete="true">
</asp:LinqDataSource>

Listing 3 – the finished GridView_Edit.ascx file

Now we’ll trim down the GridView_Edit.ascx.cs, the first things to remove are the following methods and event handlers:

protected void Page_Load(object sender, EventArgs e)
protected void OnFilterSelectedIndexChanged(object sender, EventArgs e)

This will leave us with just the Page_Init to fill in see Listing 4 for it.

protected void Page_Init(object sender, EventArgs e)
{
    var metaChildColumn = Column as MetaChildrenColumn;
    var attribute = Column.Attributes.OfType<ShowColumnsAttribute>().SingleOrDefault();

    if (attribute != null)
    {
        if (!attribute.EnableDelete)
            EnableDelete = false;
        if (!attribute.EnableUpdate)
            EnableUpdate = false;
        if (attribute.DisplayColumns.Length > 0)
            DisplayColumns = attribute.DisplayColumns;
    }

    var metaForeignKeyColumn = metaChildColumn.ColumnInOtherTable as MetaForeignKeyColumn;

    if (metaChildColumn != null && metaForeignKeyColumn != null)
    {
        GridDataSource.ContextTypeName = metaChildColumn.ChildTable.DataContextType.Name;
        GridDataSource.TableName = metaChildColumn.ChildTable.Name;

        // enable update, delete and insert
        GridDataSource.EnableDelete = EnableDelete;
        GridDataSource.EnableInsert = EnableInsert;
        GridDataSource.EnableUpdate = EnableUpdate;
        GridView1.AutoGenerateDeleteButton = EnableDelete;
        GridView1.AutoGenerateEditButton = EnableUpdate;

        // get an instance of the MetaTable
        table = GridDataSource.GetTable();

        // Generate the columns as we can't rely on 
        // DynamicDataManager to do it for us.
        GridView1.ColumnsGenerator = new FieldTemplateRowGenerator(table, DisplayColumns);

        // setup the GridView's DataKeys
        String[] keys = new String[metaChildColumn.ChildTable.PrimaryKeyColumns.Count];
        int i = 0;
        foreach (var keyColumn in metaChildColumn.ChildTable.PrimaryKeyColumns)
        {
            keys[i] = keyColumn.Name;
            i++;
        }
        GridView1.DataKeyNames = keys;

        GridDataSource.AutoGenerateWhereClause = true;
    }
    else
    {
        // throw an error if set on column other than MetaChildrenColumns
        throw new InvalidOperationException("The GridView FieldTemplate can only be used with MetaChildrenColumns");
    }
}

Listing 4 – the Page_Init ***UPDATED 2008/09/24***

UPDATED 2008/09/24: Here I’ve completely remove the creation of the WHERE parameter into the OnDataBinding event handler see Listing 4a to handle multiple FK-PK relationships.
protected override void OnDataBinding(EventArgs e)
{
    base.OnDataBinding(e);

    var metaChildrenColumn = Column as MetaChildrenColumn;
    var metaForeignKeyColumn = metaChildrenColumn.ColumnInOtherTable as MetaForeignKeyColumn;

    // get the association attributes associated with MetaChildrenColumns
    var association = metaChildrenColumn.Attributes.
        OfType<System.Data.Linq.Mapping.AssociationAttribute>().FirstOrDefault();

    if (metaForeignKeyColumn != null && association != null)
    {

        // get keys ThisKey and OtherKey into Pairs
        var keys = new Dictionary<String, String>();
        var seperator = new char[] { ',' };
        var thisKeys = association.ThisKey.Split(seperator);
        var otherKeys = association.OtherKey.Split(seperator);
        for (int i = 0; i < thisKeys.Length; i++)
        {
            keys.Add(otherKeys[i], thisKeys[i]);
        }

        // setup the where clause 
        // support composite foreign keys
        foreach (String fkName in metaForeignKeyColumn.ForeignKeyNames)
        {
            // get the current pk column
            var fkColumn = metaChildrenColumn.ChildTable.GetColumn(fkName);

            // setup parameter
            var param = new Parameter();
            param.Name = fkColumn.Name;
            param.Type = fkColumn.TypeCode;

            // get the PK value for this FK column using the fk pk pairs
            param.DefaultValue = Request.QueryString[keys[fkName]];

            // add the where clause
            GridDataSource.WhereParameters.Add(param);
        }
    }
    // doing the work of this above because we can't
    // set the DynamicDataManager table or where values
    //DynamicDataManager1.RegisterControl(GridView1, false);
}

Listing 4a – OnDataBinding event handler ***ADDED 2008/09/24*** 

UPDATED 2008/08/11: This update removes the need for the Attribute defined in Listing 6 the GridView_Edit.ascx FieldTemplate now supports getting foreign key(s) automatically and support Clustered/Composite Keys. :D See this post from Zwitterion here Re: Dynamic child details on a dynymic details or edit page? where her mentions the Column.ColumnInOtherTable which gave me the idea for the above changes :D
UPDATED 2008/08/08: I’ve updated the above code in reply to a post where Zwitterion points out that I make the assumption that the child’s FK column is the same name as the parents PK column. so I’ve added an Attribute to fix this. And now I’ve added some more error handling to cover misuse of FieldTemplate

And now we will need to implement the GridViewColumnGenerator to fill in the rows as the DynamicDataManager would have done.

public class GridViewColumnGenerator : IAutoFieldGenerator
{
    protected MetaTable _table;

    public GridViewColumnGenerator(MetaTable table)
    {
        _table = table;
    }

    public ICollection GenerateFields(Control control)
    {
        List<DynamicField> oFields = new List<DynamicField>();

            foreach (var column in _table.Columns)
            {
                // carry on the loop at the next column  
                // if scaffold table is set to false or DenyRead
                if (!column.Scaffold)
                    continue;

                DynamicField field = new DynamicField();

                field.DataField = column.Name;
                oFields.Add(field);
            }
        return oFields;
    }
}
Listing 5 – the GridViewColumnGenerator class

In Listing 5 we have the GridViewColumnGenerator class which you can just tag onto the end the GridView_Edit.ascx.cs file as it is only used here.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class GridViewTemplateAttribute : Attribute
{
    public String ForeignKeyColumn { get; private set; }

    public GridViewTemplateAttribute(string foreignKeyColumn)
    {
        ForeignKeyColumn = foreignKeyColumn;
    }
}

Listing 6 – GridViewTemplateAttribute for the above ***UPDATE 2008/08/08 *** :D

Note: This GridViewColumnGenerator class would need to be changed to the FilteredFieldsManager of the my permissions add on for Dynamic Data that I did on my blog here and here

GridView_Edit FieldTemplate in action

Figure 1 GridView_Edit FieldTemplate in action

!Important: Because you will need to apply UIHint metadata to the ChildrenColumn you wish to use this with you will also need to copy the FieldTemplate Children.ascx to GridView.ascx as in the previous post in this series, to have the column show in non edit/insert modes.

[UIHint("GridView")]
public object Order_Details { get; set; }

Listing 7 – Metadata

Until next time smile_teeth

27 comments:

Angry Tech Guy said...

Thanks for all the great information. The grid view sample helps expose even more of the capabilities of Dynamic Data and your work with the security model is right where a lot of people are thinking.

Keep up the excellent work!

Stephen J. Naughton said...

Thanks Angry Tech Guy glad you like it, where are you from?

Angry Tech Guy said...

From Maine, USA

Sadly I've moved to more over IT Management from the strict programming and design side, but DynamicData is allowing me easily work through some things I ordinarily wouldn't have had the time to pull off. It was missing a few things but the gridview article, security, and the dynamic filters are helping close the gaps.

Jessica Smith said...

I'm interested in using a gridview in place of a dropdownlist for a foreign key field. The reason being is two-fold: the parent table has too many rows for the dropdownlist control as far as performance is concerned, and selection of the parent row would be made easier with the ability to see other column data and the use of filters.

Nicolas R said...

Hello,

That is just excellent but ..

is this possible to do the same thing using Entities instead of LINQ to SQL ?

Stephen J. Naughton said...

Yes it is possible I will get around to doing a post on that when we get to Beta 2 of VS2010/.Net 4.0

Steve :D

Anonymous said...

No offence, but the above is almost impossible to read, what with all the edits, deletes, etc.

As someone who manages people and code, I would never post anything like this w/o a downloadable file or set of files. What's the point of trying to read betwixt all the early mistakes you made? I see none, and irregardless, a clean solution would be many times more valuable than reading how a flawed design gets worked on.

Stephen J. Naughton said...

if you go to the final article in the series you will find the source code

Steve :D

P.S. the article is old and had many changes and I wanted readers to see my errors.

Shaheen said...

hey steve!! got my job done BUT i have an issue, results are not being filtered out i mean say i have customers and shipping information table, instead of showing me only that particular customer ID shipping info , child grid displays the whole shipping info table. can you help me with this?

Anonymous said...

Any chance of post LINQ to SQL?
Cheers


"is this possible to do the same thing using Entities instead of LINQ to SQL ?

1 June 2009 16:58
Steve said...
Yes it is possible I will get around to doing a post on that when we get to Beta 2 of VS2010/.Net 4.0"

Stephen J. Naughton said...

I'm sure it should but I havent tried as all my projects have been Linq to SQL.

Steve :D

P.S. with the advent of VS2010 I think all my projects will go EF :)

Anonymous said...

Where is ShowColumnsAttribute? I'm using 2010 RC, is that class no longer part of the dynamic data libraries?

Stephen J. Naughton said...

Hi there, ShowColumnsAttribute was one of my custom attributes see my latest blog post for stuff on VS2010 RC and newer.

Steve :D

@paulovila said...

Instead of:
[code]
GridDataSource.ContextTypeName = metaChildColumn.ChildTable.DataContextType.Name;
[/code]
you should use:
[code]
GridDataSource.ContextTypeName = metaChildColumn.ChildTable.DataContextType.FullName;
[/code]
And in C# 4 instead of :
[code]
if (!attribute.EnableDelete)
EnableDelete = false;
if (!attribute.EnableUpdate)
EnableUpdate = false;
[/code]

you should use:
[code]
EnableDelete = attribute.EnableDelete;
EnableUpdate = attribute.EnableUpdate;
[/code]

Stephen J. Naughton said...

Hi paulovila, this article was for DD1 in 2008 and is now out of date but thanks for the update I will try and fix it sometime.

Steve

Anonymous said...

Hi Steve,

Is there updated version of of this? I am having issues porting this to VS2010 EF.

Thanks,

San

PS: Love ur work, I have learned a lot Thanks

Stephen J. Naughton said...

I will be doing an article for the child grid for VS2010 and EF soon

Steve.

Lener said...

Hi,
Thanks for all the publishing on DynamicData. Appreciate it.

When is the new child gridview article coming? Waiting for it!

I still have at least one problem with this approach, the ValidationException(s) are not caught somehow (for db errors) and the "yellow screen of death" as you intimately call it, is still disturbing me!

You tell in this article that we can't rely on the DynamicDataManager, why not? Why is it not as simple as putting another, nested, dynamicDataManager and BOUM! It should be! Or why is it not possible to reference nested controls in the "root" dynamicDataManager.
If only I could tweak it...

A bit frustrating!

Stephen J. Naughton said...

Hi Lener, I am working on a new version of the ChildrenList as I've now called it :) for .Net 3.5 and .Net 4 and Linq to SQL and Entity Framework. I will do a new article when work schedule allows.

I'm not sure of the reason why the DynamicDataManager is of no use here. BTW the DynamicDataManager does not contain the nested controls the GridViews/DetailsViews/FormViews do, they are just wrapped in field templates and DynamicControls.

Steve :)

Faizal Shaji said...

Hello sir,
Can you say,how can we create a sales page in asp.net.Either in Dynamic Data or Web forms.What i mean is that,i want to enter the date customer name etc and the order details below it in a gridview. Such a thing can easily be achieved in deskop applications.Please help

Stephen J. Naughton said...

Hi Faizal, I do have somthing like that but I have made it puclic yet sorry, I hope to get it on NuGet when I get the time but stuck doing paid work at the moment.

Got to eat :D

Steve

Anonymous said...

Hi,

Thanks for all of the work, it has been very helpful. I am working on modifying this code to create a user control that displays a gridview for the parent table of each foreign key of a given table. The problem I am having is that I am using a LINQ to Entities project, and I am getting the error:
"The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'."
Some other resources say this has to do with using the wrong type of dynamic data project. Your example is in LINQ to SQL. How can I modify your code to work with LINQ to Entities (or am I missing something and that is not the problem)?

Thanks,

Jonathan

Stephen J. Naughton said...

Hi Jonathan, yes I already did that just not published it I can send you the code if you direct e-mail me

Steve

Anonymous said...

Hi,

Great post. Any idea if it's possible to insert one or more new "order_details" in the gridview before sending the whole bunch into the database via the FormView's "update" button?

Thanks,
red

Stephen J. Naughton said...

it is possible but I don't have a sample for it. the editable Grid I have does the inserts one at a time. What you want is there in Visual Studio LightSwitch you can create a new record and add child records and then click save all in one.

Steve

Anonymous said...

Hi,
Thanks for this great work. Do you have already published somewhere the version with Linq to Entity Framework?

Thanks,
Marco

Stephen J. Naughton said...

I have but you will need to e-mail me if you want just the bare minimum sample else you can look on my open source project here

Steve