- 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.
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
- Cascade.ascx
- Cascade.ascx.cs
From the sample website DynamicDataFuturesSample root to our projects App_Code folder
- CascadeAttribultes.cs
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.
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.
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...
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.
Figure 3 – How the project should look after adding the files and references
Modifying the added files
Remove the namespace for the CascadeAttribute.cs file and save that’s done.
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
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:
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.
Figure 5 – Cascade FieldTemplate 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 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.
And that about wraps it up.
Until next time.
11 comments:
"there is no way of Dynamic Data knowing what FieldTemplate to use in Read-Only mode."
Actually, there is a way to tell.
FieldTemplayUserControl has a property "Mode".
namespace System.Web.UI.WebControls
{
// Summary:
// Represents the different data-entry modes for a data-bound control or a particular
// field in ASP.NET Dynamic Data.
public enum DataBoundControlMode
{
// Summary:
// Represents the display mode, which prevents the user from modifying the values
// of a record or a data field.
ReadOnly = 0,
//
// Summary:
// Represents the edit mode, which enables users to update the values of an
// existing record or data field.
Edit = 1,
//
// Summary:
// Represents the insert mode, which enables users to enter values for a new
// record or data field.
Insert = 2,
}
}
Here is a snippet of what i'm using in my project:
Visible = (Mode != DataBoundControlMode.Insert);
yes I know all about the Mode property in DD this issue I talking about is that DD doe'nt know which to use as you've added a UIHint [UIHint("Cascade")] to the field/column so DD will add _Edit for the edit and look for the "Cascade" FieldTemplate (Read only mode) and will not know to use the ForeignKey.ascx FT in its place so you copy it. In future version of DD you will be able to specify just the FT you want to overide i.e. [UIHint("Cascade_Edit")] or [UIHint("Cascade_Insert")] but ath the moment if you try this it will asume that the readonly FT is called say Cascade_Edit and the edit is called Cascade_Edit_Edit. see what I mean.
Steve :D
Excellent article!
One question: when I switch to "Edit" mode, the cascade controls do not reflect the field values (e.g. "reset" themselves).
Is there a way to make them behave in a more natural manner?
Thanks in advance!
Yep that's a TODO: ;-)
Hopefully I'll get around to it soon.
Steve :D
Please, can you help how we can fix issue with Edit mode.
The cascade controls do not reflect the field values (e.g. "reset" themselves).
Thanks
Sorry Azamat, Ive had no luck with that as yet, what Ive done is create custom cascading field templates. also have a look at my article "Dynamic Data – Cascading FieldTemplates"(http://csharpbits.notaclue.net/2009/01/dynamic-data-cascading-fieldtemplates.html)
Steve :D
Download Code?????
Hi there, if you go to the last article in this series the download is there.
Steve :D
Hi Steve, i'm trying to use your Cascade FieldTemplate with EF but...
"Unable to create a constant value of type '%TableName%'. Only primitive types ('such as Int32, String, and Guid') are supported in this context."
OK, in the model.edmx you have foreignKey not as primitive types... So when try to iterate the rows: "foreach (var row in filterItems)" crashes... I'll noted that in GetChildListFilteredByParent() it gets the query to send to the EF, perhaps i can modify something there...
Can you help me?
Hi,
Question : when i switch to List Mode from a Edit mode, the Cascade filter return to standard Value "All".
Any help ?
I think you need to look at the old Ajax History or Filter History for .Net 4
Steve
Post a Comment