Wednesday, 3 September 2008

Dynamic Data and Field Templates - An Advanced FieldTemplate with a DetailsView ***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.

In this addition the FieldTemplates series I was asked to produce one that looked back up the relationship with the parent table to get some or all of the properties.

The basis for this FieldTemplate is the previous GridView_Edit FieldTemplate. This time I’m just going to post the files and then discuss the alterations, so here goes:

<%@ Control 
 Language="C#" 
 CodeFile="DetailsView_Edit.ascx.cs" 
 Inherits="DetailsView_EditField" %>
<asp:ValidationSummary ID="ValidationSummary1" 
 D="ValidationSummary1" 
 runat="server" 
 EnableClientScript="true"
 HeaderText="List of validation errors" />
 
<asp:DynamicValidator 
 runat="server" 
 ID="DetailsViewValidator" 
 ControlToValidate="DetailsView1"
 Display="None" />
 
<asp:DetailsView 
 ID="DetailsView1" 
 runat="server" 
 DataSourceID="DetailsDataSource"
 CssClass="detailstable"
 AutoGenerateDeleteButton="true"
 AutoGenerateEditButton="true"
 AutoGenerateInsertButton="true"
 FieldHeaderStyle-CssClass="bold">
 
</asp:DetailsView>
<asp:LinqDataSource 
 ID="DetailsDataSource" 
 runat="server" 
 EnableDelete="true">
</asp:LinqDataSource>

Listing 1 - DetailsView_Edit.ascx

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml.Linq;
using System.Web.DynamicData;
public partial class ParentDetails_EditField : FieldTemplateUserControl
{
 protected MetaTable parentTable;
 protected MetaTable childTable;
 public Boolean EnableDelete { get; set; }
 //public Boolean EnableInsert { get; set; }
 public Boolean EnableUpdate { get; set; }
 public String[] DisplayColumns { get; set; }
 public ParentDetails_EditField()
 {
 // set default values
 EnableDelete = true;
 EnableUpdate = true;
 //EnableInsert = false;
 }
 protected void Page_Init(object sender, EventArgs e)
 {
 var attribute = Column.Attributes.OfType<ShowColumnsAttribute>().SingleOrDefault();
 if (attribute != null)
 {
 if (!attribute.EnableDelete)
 EnableDelete = false;
 if (!attribute.EnableUpdate)
 EnableUpdate = false;
 //if (!attribute.EnableInsert)
 // EnableInsert = false;
 if (attribute.DisplayColumns.Length > 0)
 DisplayColumns = attribute.DisplayColumns;
 }
 var metaForeignKeyColumn = Column as MetaForeignKeyColumn;
 if (metaForeignKeyColumn != null)
 {
 childTable = metaForeignKeyColumn.Table;
 // setup data source
 DetailsDataSource.ContextTypeName = metaForeignKeyColumn.ParentTable.DataContextType.Name;
 DetailsDataSource.TableName = metaForeignKeyColumn.ParentTable.Name;
 // enable update, delete and insert
 DetailsDataSource.EnableDelete = EnableDelete;
 DetailsDataSource.EnableInsert = false; // EnableInsert;
 DetailsDataSource.EnableUpdate = EnableUpdate;
 DetailsView1.AutoGenerateDeleteButton = EnableDelete;
 DetailsView1.AutoGenerateInsertButton = false; // EnableInsert;
 DetailsView1.AutoGenerateEditButton = EnableUpdate;
 // get an instance of the MetaTable
 parentTable = DetailsDataSource.GetTable();
 // Generate the columns as we can't rely on 
 // DynamicDataManager to do it for us.
 DetailsView1.RowsGenerator = new FieldTemplateRowGenerator(parentTable, DisplayColumns);
 // setup the GridView's DataKeys
 String[] keys = new String[metaForeignKeyColumn.ParentTable.PrimaryKeyColumns.Count];
 int i = 0;
 foreach (var keyColumn in metaForeignKeyColumn.ParentTable.PrimaryKeyColumns)
 {
 keys[i] = keyColumn.Name;
 i++;
 }
 DetailsView1.DataKeyNames = keys;
 // enable AutoGenerateWhereClause so that the WHERE 
 // clause is generated from the parameters collection
 DetailsDataSource.AutoGenerateWhereClause = true;
 // doing the work of this above because we can't
 // set the DynamicDataManager table or where values
 //DynamicDataManager1.RegisterControl(DetailsView1, false);
 }
 else
 {
 // throw an error if set on column other than MetaChildrenColumns
 throw new InvalidOperationException("The GridView FieldTemplate can only be used with MetaChildrenColumns");
 }
 }
 protected override void OnDataBinding(EventArgs e)
 {
 base.OnDataBinding(e);
 // get the fk column
 var metaForeignKeyColumn = Column as MetaForeignKeyColumn;
 // get the association attributes associated with MetaChildrenColumns
 var association = metaForeignKeyColumn.Attributes.
 OfType<System.Data.Linq.Mapping.AssociationAttribute>().FirstOrDefault();
 if (metaForeignKeyColumn != null && association != null)
 {
 // get keys ThisKey and OtherKey into dictionary
 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(thisKeys[i], otherKeys[i]);
 }
 // setup the where clause 
 // support composite foreign keys
 foreach (String fkName in metaForeignKeyColumn.ForeignKeyNames)
 {
 // get the current FK column
 var fkColumn = metaForeignKeyColumn.Table.GetColumn(fkName);
 // get the current PK column
 var pkColumn = metaForeignKeyColumn.ParentTable.GetColumn(keys[fkName]);
 // setup parameter
 var param = new Parameter();
 param.Name = pkColumn.Name;
 param.Type = pkColumn.TypeCode;
 // get the value for this FK column
 param.DefaultValue = GetColumnValue(fkColumn).ToString();
 // add the where clause
 DetailsDataSource.WhereParameters.Add(param);
 }
 }
 }
}

Listing 2 - DetailsView_Edit.ascx.cs ***UPDATED 2008年09月24日***

UPDATED 2008年09月24日: The OnDataBinding event handler has been updated to handle multiple PK-FK relationships.

As you can see from examining the above file the main thing is the change of the GridView to DetailsView, however you will notice that part of the code has been remove from the Page_Init the OnDataBinding. This is because we are coming at this from the other end the value for the ForeignKey to link the DetailsView to the parent control is no longer in the Request.QueryString and so we have to extract it in the OnDataBinding event handler as access to column values is not valid untill OnDataBinding.

You will also I’ve added some properties to enable features of the DetailsView either declaratively or via attributes:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class DetailsViewTemplateAttribute : Attribute
{
 public DetailsViewTemplateAttribute(params String[] displayColumns)
 {
 DisplayColumns = displayColumns;
 }
 public String[] DisplayColumns { get; set; }
 public Boolean EnableDelete { get; set; }
 public Boolean EnableInsert { get; set; }
 public Boolean EnableUpdate { get; set; }
}

Listing 3 - DetailsViewTemplateAttribute.cs

[MetadataType(typeof(OrderMD))]
public partial class Order
{
 public class OrderMD
 {
 [UIHint("DetailsView")]
 [DetailsViewTemplate
 (
 "Title",
 "FirstName",
 "LastName", 
 "Region",
 "Extension", 
 EnableDelete=false, 
 EnableUpdate=false, 
 EnableInsert=false
 )]
 public object Employee { get; set; }
 }
}

Listing 4 – Northwind Partials and Metadata

As you can see from Listing 3 and Listing 4 you are able to enable or disable Update, Delete or Insert on the DetailsView FieldTemplate and also specify which columns you want to appear in the particular instance.

public class DetailsViewRowGenerator : IAutoFieldGenerator
{
 protected MetaTable _table;
 protected String[] _displayColumns;
 public DetailsViewRowGenerator(MetaTable table, String[] displayColumns)
 {
 _table = table;
 _displayColumns = displayColumns;
 }
 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;
 if (_displayColumns != null && !_displayColumns.Contains(column.Name))
 continue;
 DynamicField f = new DynamicField();
 f.DataField = column.Name;
 oFields.Add(f);
 }
 return oFields;
 }
}

Listing 5 – DetailsViewRowGenerator (tagged on to the end of the DetailsView_Edit.ascx.cs file)

And finally the DetailsViewRowGenerator wether to show some or all of the columns in the parent table, the most important line here is:

if (_displayColumns != null && !_displayColumns.Contains(column.Name))
 continue;

which test first to see of any columns have been specified and if so the check to see if the current column is not present in the list and then drops the column appropriately, otherwise the IAutoFieldGenerator implementation is pretty much the same as the GridView FieldTemplate.

See if working below:

[画像:DetailsView_Edit FieldTemplate at work]

Figure 1 - DetailsView_Edit FieldTemplate at work

No comments:

Post a Comment

Subscribe to: Post Comments (Atom)

AltStyle によって変換されたページ (->オリジナル) /