Hi I’m going away for a little while.
Normal service will be resumed shortly.
ASP.Net, Dynamic Data and c# stuff: Focused on what new and cool, code tutorials and useful tricks and tips on getting the most out of ASP.Net. All in C#
Well what you need is this:
To do a complete job of moving to the Release of Visual Studio 2008 SP1 you will need to remove the runtime by:
Opening a Visual Studio 2008 Command Prompt (with Run as Administrator if on Vista) change to the folder where you unzipped the last Dynamic Data Runtime release you installed and run the Uninstall.cmd batch file.
And then:
Visual Studio 2008 Service Pack Preparation Tool This removes the Bata 1 of SP1 and cleans up a bit.
Visual Studio 2008 SP1 (also includes .NET 3.5 SP1) This installs the release version
In this post I’m going to add row rollover and two types of row click.
To add this feature to the List.aspx PageTemplate we need to add an event handler for the OnDataBound event.
To add the event handler go to Design view of the List.aspx page click the GridView click the lightening bolt at the top the properties, this will switch to the event view of the properties.
Figure 1 - Selecting Event Properties
Figure 2 - Double click the blank next to the Event name
Next double click the blank space to the right of the DataBound entry and this will take you to the code behind with a new event handler method called by default GridView1_DataBound.
protected void GridView1_DataBound(object sender, EventArgs e) {
}
Listing 1 – Empty OnDataBound event handler
Now we need to add the code that adds the rollover event to each row.
foreach (GridViewRow row in GridView1.Rows) { if (row.RowType == DataControlRowType.DataRow) { // Add javascript to highlight row row.Attributes["onmouseover"] =
"javascript:NAC_ChangeBackColor(this, true, '#BAD5E8'); this.style.cursor = 'hand';"; row.Attributes["onmouseout"] = "javascript:NAC_ChangeBackColor(this, false, '');";
}
}
Listing 2 – code to add mouse rollover
Also we need some JavaScript Listing 3 you can add this to head of the page or Master page inside some script tags or add it to an external JavaScript file and link it in the header of the page or Master page.
var lastColorUsed; function NAC_ChangeBackColor(row, highlight, RowHighlightColor) { if (highlight) { // set the background colour lastColorUsed = row.style.backgroundColor; row.style.backgroundColor = RowHighlightColor; } else { // restore the colour row.style.backgroundColor = lastColorUsed; } }
Listing 3 – Rollover helper JavaScript
If we want to add clicking the row to our project we will need to change the buttons in the GridView’s Columns –> TemplateField collection. Listing 4 shows how the buttons need to be changed:
<Columns> <asp:TemplateField> <ItemTemplate> <asp:HyperLink ID="EditHyperLink" runat="server" NavigateUrl='<%# table.GetActionPath(PageAction.Edit, GetDataItem()) %>' Text="Edit" /> <asp:LinkButton ID="DeleteLinkButton" runat="server" CommandName="Delete" CausesValidation="false" Text="Delete" OnClientClick='return confirm("Are you sure you want to delete this item?");'/> <asp:HyperLink ID="DetailsHyperLink" runat="server" NavigateUrl='<%# table.GetActionPath(PageAction.Details, GetDataItem()) %>' Text="Details" /> </ItemTemplate> </asp:TemplateField> </Columns>
<Columns> <asp:TemplateField> <ItemTemplate> <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Edit" CommandArgument='<%# table.GetActionPath(PageAction.Edit, GetDataItem()) %>' CausesValidation="true" Text="Edit"/> <asp:LinkButton ID="DeleteLinkButton" runat="server" CommandName="Delete" CausesValidation="false" Text="Delete" OnClientClick='return confirm("Are you sure you want to delete this item?");'/> <asp:LinkButton ID="LinkButton2" runat="server" CommandName="Details" CommandArgument='<%# table.GetActionPath(PageAction.Details, GetDataItem()) %>' Text="Details"/> </ItemTemplate> </asp:TemplateField> </Columns>
Listing 4 – changing the two HyperLink’s to LinkButtons
As we did above add an event handler for RowCommand OnRowCommand event see Listing 5 for the code to add to the interior.
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e) { String commandName = e.CommandName.ToLower(); if (commandName == "edit" || commandName == "details") Response.Redirect(e.CommandArgument.ToString()); }
Listing 5 – OnRowCommand event handler
What we have done in Listings 4 & 5 is move the functionality of the HyperLink to the code behind. Now Listing 5 just checks to make sure it’s a command we want to handle and then call the URL passed in the CommandArgument. Now we have restored the Hyperlink’s original functionality.
Now that we have changed the HyperLink control to LinkButton we can add the code that implements row click functionality. For this we will need to edit the GridView1_DataBound event handler and add the code in Listing 6 just after the last row.Attributes.
foreach (Control c in row.Cells[0].Controls) { if (c is LinkButton) { LinkButton selectButton = (LinkButton)c; // Get the javascript which is assigned to this LinkButton String jsClick = ClientScript.GetPostBackClientHyperlink(selectButton, ""); // Get command name in lowercase String commandName = selectButton.CommandName.ToLower(); // Add this javascript to the onclick Attribute of the row if (commandName == "details")
{ row.Attributes["onclick"] = jsClick; selectButton.Visible = false;
}
} }
Listing 6 – code to cycle through the rows and add the on click event code
If you run this as it is you will get an “Invalid postback or callback argument” error (but that will be hidden because of EnablePartialRendering in the Site.Master page is enabled) to overcome this we need to register the all the row click events for event validation by calling RegisterForEventValidation on each one see Listing 7 for the code.
// Register the dynamically created client scripts protected override void Render(HtmlTextWriter writer) { // The client scripts for gvReleased were created in gvReleased_RowDataBound foreach (GridViewRow row in GridView1.Rows) { if (row.RowType == DataControlRowType.DataRow) { // validate the controls event foreach (Control c in row.Cells[0].Controls) { if (c is LinkButton) { // get link button LinkButton selectButton = (LinkButton)c; if (selectButton.CommandName.ToLower() == "details") Page.ClientScript.RegisterForEventValidation(selectButton.UniqueID); } } } } base.Render(writer); }
Listing 7 – validate the controls event
So now we have rollover and click working :D
Next and this bit is a bit tricky as we add the extra bits to handle double click.
protected void GridView1_DataBound(object sender, EventArgs e) { foreach (GridViewRow row in GridView1.Rows) { if (row.RowType == DataControlRowType.DataRow) { // Add javascript to highlight row row.Attributes["onmouseover"] =
"javascript:NAC_ChangeBackColor(this, true, '#BAD5E8'); this.style.cursor = 'hand';"; row.Attributes["onmouseout"] = "javascript:NAC_ChangeBackColor(this, false, '');"; foreach (Control c in row.Cells[0].Controls) { if (c is LinkButton) { LinkButton selectButton = (LinkButton)c; // Get the javascript which is assigned to this LinkButton String jsClick = ClientScript.GetPostBackClientHyperlink(selectButton, ""); // Get command name in lowercase String commandName = selectButton.CommandName.ToLower(); // Add this javascript to the onclick Attribute of the row if (commandName == "details") row.Attributes["onclick"] = "NAC_StartSingleClick(\"" + jsClick + "\");"; // Add this javascript to the ondblclick Attribute of the row if (commandName == "edit") row.Attributes["ondblclick"] = "NAC_StartDblClick(\"" + jsClick + "\");"; // set the link button to be invisible if (commandName == "details" || commandName == "edit") selectButton.Visible = false; } } } } }
Listing 8 – final OnDataBound event handler
var NAC_TimeoutId = null; function NAC_StartSingleClick(event) { NAC_TimeoutId = setTimeout(event, 200); } function NAC_StartDblClick(event) { window.clearTimeout(NAC_TimeoutId); setTimeout(event, 1); }
Listing 9 – JavaScript functions to stop conflicts with double and single click events
In Listing 8 we altered the row.Attributes assignment to NAC_StartSingleClick(\"" + jsClick + "\"); so instead of calling the event directly it now goes through a launch function (see Listing 9 for details) in both function listed in Listing 9 the passed in button functions are surrounded with a setTimeout function one with a timeout of 200ms and the other 1ms; this is so that when you double click the NAC_StartDblClick has time to clear the waiting single click before it is actioned. This overcomes the click - double click conflict.
And also modify the OnRender event handler to deal with the edit button also.
// Register the dynamically created client scripts protected override void Render(HtmlTextWriter writer) { foreach (GridViewRow row in GridView1.Rows) { if (row.RowType == DataControlRowType.DataRow) { // validate the controls event foreach (Control c in row.Cells[0].Controls) { if (c is LinkButton) { // Get linkbutton LinkButton selectButton = (LinkButton)c; // Get command name in lowercase String commandName = selectButton.CommandName.ToLower(); if (commandName == "edit" || commandName == "details") Page.ClientScript.RegisterForEventValidation(selectButton.UniqueID); } } } } base.Render(writer); }
Listing 10 – final OnRender event handler
The OnRender event now handles both buttons click and double click.
Now if you click anywhere on the row you get redirected to the Details page and if you double click you get redirected to the Edit page.
For completeness here are the changes to the ListDetails.aspx if you are using it.
<Columns> <asp:TemplateField> <ItemTemplate> <asp:LinkButton ID="LinkButton2" runat="server" CommandName="Select" Text="Select"/> <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Edit" CausesValidation="true" Text="Edit"/> <asp:LinkButton ID="DeleteLinkButton" runat="server" CommandName="Delete" CausesValidation="false" Text="Delete" OnClientClick='return confirm("Are you sure you want to delete this item?");'/> </ItemTemplate> </asp:TemplateField> </Columns>
Listing 11 – changed to the ListDetails.aspx GridView1 columns collection
protected void GridView1_DataBound(object sender, EventArgs e) { if (GridView1.Rows.Count == 0) { DetailsView1.ChangeMode(DetailsViewMode.Insert); } foreach (GridViewRow row in GridView1.Rows) { if (row.RowType == DataControlRowType.DataRow) { // Add javascript to highlight row row.Attributes["onmouseover"] =
"javascript:NAC_ChangeBackColor(this, true, '#BAD5E8'); this.style.cursor = 'hand';"; row.Attributes["onmouseout"] = "javascript:NAC_ChangeBackColor(this, false, '');"; foreach (Control c in row.Cells[0].Controls) { if (c is LinkButton) { LinkButton selectButton = (LinkButton)c; // Get the javascript which is assigned to this LinkButton String jsClick = ClientScript.GetPostBackClientHyperlink(selectButton, ""); // Get command name in lowercase String commandName = selectButton.CommandName.ToLower(); // Add this javascript to the onclick Attribute of the row if (commandName == "select") row.Attributes["onclick"] = "NAC_StartSingleClick(\"" + jsClick + "\");"; // Add this javascript to the ondblclick Attribute of the row if (commandName == "edit") row.Attributes["ondblclick"] = "NAC_StartDblClick(\"" + jsClick + "\");"; // set the link button to be invisible if (commandName == "select" || commandName == "edit") selectButton.Visible = false; } } } } } // Register the dynamically created client scripts protected override void Render(HtmlTextWriter writer) { // The client scripts for gvReleased were created in gvReleased_RowDataBound foreach (GridViewRow row in GridView1.Rows) { if (row.RowType == DataControlRowType.DataRow) { // validate the controls event foreach (Control c in row.Cells[0].Controls) { if (c is LinkButton) { // Get linkbutton LinkButton selectButton = (LinkButton)c; // Get command name in lowercase String commandName = selectButton.CommandName.ToLower(); if (commandName == "edit" || commandName == "select") Page.ClientScript.RegisterForEventValidation(selectButton.UniqueID); } } } } base.Render(writer); }
Listing 12 – the additional code to be added to the ListDetails.aspx.cs code behind file
Here the single click activates Select and the double click activates Edit.
Happy coding
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.
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
<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***
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***
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
Figure 1 GridView_Edit FieldTemplate in action
[UIHint("GridView")] public object Order_Details { get; set; }
Listing 7 – Metadata
Until next time
For this article we are going to convert the CascadingFilter from Dynamic Data Futures project this was thought of by Noimed in this thread.
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).
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.
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
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
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.
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
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.