In part 1 of this series I discussed extending the LinqDataSource to provided advanced querying capabilities using the System.Linq.Dynamic namespace. Part 2 demonstrated creating advanced WhereParameters which allowed for for complex binary logic. In this posting I am going to go through our first ASP.NET Web Control which brings this powerful functionality to the user.
My original intention of this blog series was to create a more powerful DynamicFilterControl to replace the one currently in the Dynamic Data portion of the ASP.NET 3.5 Extensions. During a recent project where we use the Dynamic Data framework, it became apparent that the repeater limited our layout ability and a DynamicFilterForm was better suited. I choose the name DynamicFilterForm to follow the naming convention of the ASP.NET databound controls.
So, let’s start off with a demo of the the DynamicFilterForm. In this demo I am building on the Products example used in previous postings. For the List.aspx page we’d like to be able to filter on the fields displayed in the DataGrid.
![DynamicFilterForm_1[5]](http://heyse.us/blog/wp-content/uploads/WindowsLiveWriter/ARicherDynamicFilterRepeaterPat3DynamicF_FFD6/DynamicFilterForm_1%5B5%5D_thumb.png)
The search allows for all of the fields, except weight, to be search on. The Name, Product Number, Class and Color allow for partial string matches, ListPrice allows for a range, Subcategory uses a single selection drop down which is populated through the ForiegnKey, and Product Model allows for multiple selection.
The DynamicFilterForm allows for 3 commands: Search, Clear, and Browse. (More on these later).
Let’s take a look at the ASP.NET code that runs the form:
<cc1:DynamicFilterForm DataSourceID="GridDataSource" runat="Server" ID="DynamicFilterForm1">
<FilterTemplate>
<div>
Search
</div>
<table>
<tr>
<td>
Name:
</td>
<td>
<cc1:DynamicFilterControl ID="DynamicFilterControl3" runat="server" DataField="Name"
FilterMode="Contains" />
</td>
<td>
Product Number:
</td>
<td>
<cc1:DynamicFilterControl ID="DynamicFilterControl5" runat="server" DataField="ProductNumber"
FilterMode="Contains" />
</td>
<td>
Class:
</td>
<td>
<cc1:DynamicFilterControl ID="DynamicFilterControl6" runat="server" DataField="Class"
FilterMode="Contains" />
</td>
<td rowspan="2" valign="top">
Product Model:
</td>
<td rowspan="2" valign="top">
<cc1:DynamicFilterControl ID="DynamicFilterControl7" runat="server" DataField="ProductModel" FilterMode="MultiSelect" />
</td>
<td rowspan="2" valign="top">
<asp:LinkButton runat="server" CommandName="Search">Search</asp:LinkButton><br />
<asp:LinkButton runat="server" CommandName="Clear">Clear</asp:LinkButton><br />
<asp:LinkButton runat="server" CommandName="Browse">Browse</asp:LinkButton>
</td>
</tr>
<tr>
<td>
List Price:
</td>
<td>
<cc1:DynamicFilterControl ID="DynamicFilterControl1" runat="server" DataField="ListPrice"
FilterMode="Range" />
</td>
<td>
Color:
</td>
<td>
<cc1:DynamicFilterControl ID="DynamicFilterControl4" runat="server" DataField="Color"
FilterMode="Contains" />
</td>
<td>
Subcategory:
</td>
<td>
<cc1:DynamicFilterControl ID="DynamicFilterControl2" runat="server" DataField="ProductSubcategory" />
</td>
</tr>
</table>
</FilterTemplate>
</cc1:DynamicFilterForm>

The DynamicFilterForm mimics the functionality of the other DynamicData controls. The DynamicFilterForm is a container object which has a FilterTemplate template that defines the layout. Inside the FilterTemplate you define DynamicFilterControls. These controls are responsible for loading the correct user control from the ~/App_Shared/DynamicDataFilters/ folder. Here you can see I have created filter controls for each of the field types declared in the DynamicDataFields folder which are included in the base solution.
The DynamicFilterControl has a property FilterMode which allows for the following different enumeration values: Equals, Contains, Range, Multiselect. This mode is appended to the field type like the DynamicControls. So if you want to allow for a range of integers, the control name would be Integer_Range.ascx. To facilitate quicker development and code reuse each of the custom DynamicExpressionParameters (EqualsExpressionParameter, InExpressionParameter, etc…) has a corresponding FilterTemplateUserControlBase(EqualsFilterUserControlBase, InFilterControlUserBase, etc…).
Here are the abstract methods for the FilterTemplateUserControlBase which need to be implemented.
public abstract class FilterTemplateUserControlBase : FieldTemplateUserControlBase, IWhereParametersProvider
{
public abstract IEnumerable<Parameter> GetWhereParameters(IDynamicDataSource dataSource);
public abstract void LoadQueryStringParameters(NameValueCollection parameters);
public abstract NameValueCollection SaveQueryStringParameters();
public abstract void Clear();
}
GetWhereParameters is the meat of this class, it is what is responsible for instantiating and returning the ExpressionParameter which is added to the DynamicLinqDataSource control’s where parameters. Don’t you love the separation of concerns? I DO! The Clear method is called by the parent DynamicFilterForm when the Clear command is issued, this clears all values and researches to bring back the unfiltered result set. LoadQueryStringParameters and SaveQueryStringParameters allow for the filter criteria to be stored in a URL.
<rant> One of my biggest annoyances on the web is developers who store state in session or form variables around searching. Web users expect to be able to copy the URL of the page they are on and send it to their friend so that they can see the same thing. I run into this every time my friends and I are trying to plan a trip someplace and we are using Orbitz, Travelocity, etc… </rant>
Here is an example of the RangeFilterUserControlBase and the Integer_Range.ascx.cs control which extends it.
public abstract class RangeFilterUserControlBase : FilterTemplateUserControlBase
{
public abstract string MinValue { get; set; }
public abstract string MaxValue { get; set; }
public abstract TypeCode Type { get; }
public override IEnumerable<Parameter> GetWhereParameters(IDynamicDataSource dataSource)
{
yield return new RangeExpressionParameter() { Name = DataField, MinValue = MinValue, MaxValue = MaxValue, Type = Type };
}
public override void LoadQueryStringParameters(NameValueCollection parameters)
{
MinValue = parameters[string.Format("{0}_Min", DataField)];
MaxValue = parameters[string.Format("{0}_Max", DataField)];
}
public override NameValueCollection SaveQueryStringParameters()
{
NameValueCollection parameters = new NameValueCollection();
parameters.Add(string.Format("{0}_Min", DataField), MinValue);
parameters.Add(string.Format("{0}_Max", DataField), MaxValue);
return parameters;
}
public override void Clear()
{
MinValue = string.Empty;
MaxValue = string.Empty;
}
}
The Integer_Range.ascx control has two TextBoxes named tbMin and tbMax.
public partial class Integer_Range : Catalyst.Web.DynamicData.RangeFilterUserControlBase
{
public override string MinValue
{
get
{
return tbMin.Text;
}
set
{
tbMin.Text = value;
}
}
public override string MaxValue
{
get
{
return tbMax.Text;
}
set
{
tbMax.Text = value;
}
}
public override TypeCode Type
{
get { return this.MetaMember.TypeCode; }
}
}
As always, below is the source to the solution so far. In my next post I will demonstrate creating your own FilterUserControl by re-implementing filtering on the Weight column.
Download Solution: DynamicData.zip