Dynamic Data and Field Templates - An Advanced FieldTemplate with a GridView UPDATED 2008/09/24 (original) (raw)
- The Anatomy of a FieldTemplate.
- Your First FieldTemplate.
- An Advanced FieldTemplate.
- A Second Advanced FieldTemplate.
- An Advanced FieldTemplate with a GridView.
- An Advanced FieldTemplate with a DetailsView.
- 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" %>
<%@ 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
<%@ 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:LinqDataSource runat="server" ID="GridDataSource" EnableDelete="true">
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().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
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