Dynamic Data and Field Templates - A Second Advanced FieldTemplate UPDATED (original) (raw)

  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.

For this article we are going to convert the CascadingFilter from Dynamic Data Futures project this was thought of by Noimed in this thread.

Files Required for this Project

Here are all of the files we will need to copy to our project from the Dynamic Data Futures project:

From the sample website DynamicDataFuturesSample\DynamicData\Filters folder to the our projects DynamicData\FieldTemplates folder

From the sample website DynamicDataFuturesSample root to our projects App_Code folder

Plus we will need to add a reference to the DynamicDataFutures project or just copy the Microsoft.Web.DynamicData.dll to the bin folder of our project (you will need to create a bin folder manually if you just copy the dll).

What Cascading Filter does

Cascading filter in Edit/Insert modes, I pointed him to the previous post in this series and we eventually sorted it so it worked as a FieldTemplate. This returns the Primary Key of the parent table see Figure 1.

Order_Details relationships

Figure 1 - Order_Details relationships

In this diagram you can see that Product is grouped Category so the CascadingFilter user control would be ideal for picking the product on the Order_Detail Insert page.

Note: You can’t edit the Product on the Order_Detail form because the Primary Key of Order_Detail is OrderID combined with ProductID :D

So in our sample we will be filtering the Product by the Category.

Creating the Website Project and Adding the Files

The first thing to do will be to create a file based website and add the Northwind database to it. This can be done simply (if you have SQL Server Express 2005/2008 installed) by creating a App_Data folder and copying the Northwind.mdb file to it (the Northwind database can be downloaded from here).

Then add an App_Code folder to the website and add a new Linq to SQL classes item it call it NW.dbml and add at lease the above table to it.

Now copy the files listed in the “Files Required for this Project” and add the reference to Dynamic Data Futures project.

Lets add a reference to the Dynamic Data Futures project; I do this by first adding an existing project, you do this by clicking File->Add->Existing Project...

Adding an Existing project

Figure 2 - Adding an Existing project

Browse to the location you have you Dynamic Data Futures project and select the project file.

Now right click the website and choose Add Reference when the dialogue box pops up select the Projects tab and choose the Dynamic Data Futures project and click the OK button.

Your project should now look like Figure 3.

How the project should look after adding the files and references

Figure 3 – How the project should look after adding the files and references

Note: Don’t forget the add your data context to the Global.asax file and set ScaffoldAllTables to true

Modifying the added files

Remove the namespace for the CascadeAttribute.cs file and save that’s done.

Note: Removing the namespace is for file based website only in a Web Application Project you would need to change the namespace to match your applications.

And now lets sort out the Cascade filter. We start by renaming the Cascade.ascx to Cascade_Edit.ascx and then edit both files:

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="**Cascade_Edit**.ascx.cs" Inherits="**Cascade_EditField**" %> <%-- Controls will be added dynamically. See code file. --%>

Listing 1 – Cascade_Edit.ascx

Remove the DynamicDataFuturesSample. from the beginning of the Inherits Control property.

Then edit the Cascade_Edit.ascx.cs file:

namespace DynamicDataFuturesSample { public partial class Cascade_Filter : FilterUserControlBase, ISelectionChangedAware {

Listing 2 – Cascade_Edit.ascx.cs

Remove the namespace from around the control class and change the inheritance from FilterUserControlBase, ISelectionChangedAware to FieldTemplateUserControl as in Listing 3.

public partial class Cascade_Filter : FieldTemplateUserControl {

Listing 3 – Altered Cascade_Edit.ascx.cs

Remove the following section as this is for Filters

public override string SelectedValue { get { return filterDropDown.SelectedValue; } }

Listing 4 – Remove SelectedValue method

Open ForeignKey_Edit.ascx.cs and copy the following sections to the Cascading_Edit.ascx.cs

protected override void ExtractValues(IOrderedDictionary dictionary) { //...
}

public override Control DataControl { //...
}

Listing 5 – Methods to copy from ForeignKey_Edit.ascx.cs

Now Edit the ExtractValues and DataControl methods so they look like Listing 6.

protected override void ExtractValues(IOrderedDictionary dictionary) { // If it's an empty string, change it to null string val = filterDropDown.SelectedValue; if (val == String.Empty) val = null;

ExtractForeignKey(dictionary, val);

}

public override Control DataControl { get { return filterDropDown; } }

Listing 6 – Finished ExtractValues and DataControl methods

Note: You will also probably need to add the following using: using System.Collections.Specialized; for the IOrderedDictionary and using System.Web.UI; for the Control.

Adding the Metadata

We have to add the following telling the Cascade FieldTemplate what it needs, it need to know what table to use to filter the main parent table by, in this case the Category table. And we need the UIHint to tell Dynamic Data to use the Cascade FieldTemplate.

using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using Microsoft.Web.DynamicData;

[MetadataType(typeof(Order_DetailMD))] public partial class Order_Detail { public class Order_DetailMD { [Cascade("Category")] [UIHint("Cascade")] public object Product { get; set; } } }

Listing 7 – the metadata

One last thing we need to add a Cascade.ascx FieldTemplate to the FieldTemplates folder as there is no way of Dynamic Data knowing what FieldTemplate to use in Read-Only mode. For this we will just copy ForeignKey.ascx as Cascade.ascx and change the class name from ForeignKeyField to CascadeField.

Add some Business Logic/Validation

Because we are using Order_Details table which has a composite primary key see below:

Order Details table

Figure 4 - Order Details table

So we need to add some business logic to validate this before insert.

public partial class NWDataContext { partial void InsertOrder_Detail(Order_Detail instance) { var DC = new NWDataContext(); var dk = DC.Order_Details.SingleOrDefault( od => od.OrderID == instance.OrderID && od.ProductID == instance.ProductID );

    if (dk != null)
    {
        // if a record is found throw an exception
        String error = "Duplicate Primary keys not allowed (OrderID={0} ProductID={1})";
        throw new ValidationException(String.Format(error, instance.OrderID, instance.ProductID));
    }
    else
    {
        // finnaly send to the database
        this.ExecuteDynamicInsert(instance);
    }
}

}

Listing 8 – InsertOrder_Details partial method

This just checks the database to see if this is a duplicate primary key and if so generates a validation error.

Cascade FieldTemplate in Action

Figure 5 – Cascade FieldTemplate in Action

Business Logic in action

Figure 6 – Business Logic in action

Adding Sorting to the Filters DropDownList ***UPDATED***

In the Cascase.ascx.cd FilterControl and Cascade_Edit.ascx.cs FieldTemplate you will find a method GetChildListFilteredByParent this returns the values for the filtered DropDownList, but as you will see this list is an unordered list. To add sorting to this list we need to add a Linq OrderBy clause. As you will see the code in Listing 9 is making use of the Expression class to create an expression tree smile_confused these are not really hard to understand, it’s just that there are so few examples and tutorials for us to get our teeth into.

So what I’ve done here is add a OrderBy clause which does the trick :D

private IQueryable GetChildListFilteredByParent(object selectedParent) { var query = filterTable.GetQuery(context); // this make more sense as the parameter now has the table name (filteredTable.Name) // note the change from "product" to filterTable.Name var parameter = Expression.Parameter(filterTable.EntityType, filterTable.Name); // product.Category var property = Expression.Property(parameter, filterTableColumnName); // selectedCategory var constant = Expression.Constant(selectedParent); // product.Category == selectedCategory var predicate = Expression.Equal(property, constant); // product => product.Category == selectedCategory var lambda = Expression.Lambda(predicate, parameter); // Products.Where(product => product.Category == selectedCategory) var whereCall = Expression.Call(typeof(Queryable), "Where", new Type[] { filterTable.EntityType }, query.Expression, lambda);

//================================== Order by ================================
if (filterTable.SortColumn != null)
{
    // this make more sense as the parameter now has the table name (filteredTable.Name)
    // table.sortColumn
    var sortProperty = Expression.Property(parameter, filterTable.SortColumn.Name);

    // Column => Column.SortColumn
    var orderByLambda = Expression.Lambda(sortProperty, parameter);

    //.OrderBy(Column => Column.SortColumn)
    MethodCallExpression orderByCall = Expression.Call(
        typeof(Queryable),
        "OrderBy",
        new Type[] { filterTable.EntityType, filterTable.SortColumn.ColumnType },
        whereCall,
        orderByLambda);


    //{
    //Table(Product).
    //Where(Products => (Products.Category = value(Category))).
    //OrderBy(Products => Products.ProductName)
    //}
    return query.Provider.CreateQuery(orderByCall);
}//================================== Order by ================================
else
{
    return query.Provider.CreateQuery(whereCall);
}

}

Listing 9 - GetChildListFilteredByParent

The section between the OrderBy comments is mine gleaned from various bits on the Internet, and also I’ve change the return line of the method to return the orderByCall which was whereCall previously.

To make this work you will need to add a DisplayColumn attribute to the metadata with the sort column added see Listing 10.

[MetadataType(typeof(ProductMD))] [DisplayColumn("ProductName","ProductName")] public partial class Product{}

Figure 10 – SortColumn added to DisplayColumn

The second parameter of DisplayColumn is the SortColumn when this is added then the GroupBy will be added to the where clause.

Note: You can transplant this code strait into the Cascade Filter as well smile_teeth.

Note: It should be possible to sort the parent DropDownList using a similar method.

And that about wraps it up.

Until next time.smile_teeth