Understanding ASP.NET Dynamic Data: Filter Templates


ASP.NET Dynamic Data lets you create extensible model-driven Web applications by inferring the appearance and behavior of entities from the model at run time. It relies on “scaffolding” to automatically generate different types of Web pages for each entity in the model from a set of reusable ASP.NET user controls and pages. Each of these “templates” encapsulates a particular type of functionality, such as a filter control or a data entry form, and can be reused automatically for all entities in the model. ASP.NET Dynamic Data supports template scaffolding for pages, entities, fields and filters. A good step-by-step introduction can be found here. In this article we will be focusing on scaffolding of the filter templates.

Queryable Data Sources

Dynamic Data relies on LINQ and deferred query execution for filtering. Here is how we might go about filtering the list of orders in the Northwind sample database to return only those placed after a given date using static code and the ADO.NET Entity Framework.

NorthwindEntities context = new NorthwindEntities();
IQueryable<Order> query = context.Orders;
query = query.Where(order => order.OrderDate > new DateTime(1996, 7, 15));
Order[] orders = query.ToArray();

In line two of this example, we create a deferred query object that, when executed, would return all orders from the database. In line three, we replace the original query with a new one, which has a where clause to filter the orders based on the order date. Up until now, the query is stored in a form of a LINQ Expression tree in memory of the executing program. Only in line four the ADO.NET Entity Framework transforms the query into a SQL SELECT statement and sends it to the database for execution. Once the response comes back from the database, the Entity Framework uses the returned data to create an array of Order objects.

Deferred query execution allows us to add practically any number of filters to the original query before it is sent to the database. Dynamic Data takes advantage of this powerful capability using the extensibility mechanisms built into the LINQ-based data source classes of ASP.NET 4.0 – LinqDataSource and EntityDataSource.

Queryable Data Source Class Diagram

Both of these classes implement the IQueryableDataSource interface which exposes the QueryCreated event. A queryable data source object raises this event after it has created the original LINQ query, before sending it to the database. QueryCreated event handlers receive a QueryCreatedEventArgs object which allows you to replace the original the original Query, similar to how the code in line three of our example does.

To allow creating filters declaratively, using page markup, ASP.NET also provides a control called QueryExtender. QueryExtender is typically placed on a page and linked to a data source using TargetControlID property. Under the hood, the control handles the data source’s QueryCreated event and uses its own collection of DataSourceExpression objects to modify the query.

<asp:EntityDataSource ID=”dataSource” runat=”server” />
<asp:QueryExtender TargetControlID=”dataSource” ID=”queryExtender” runat=”server”>
    <asp:DynamicFilterExpression ControlID=”filterRepeater” />
</asp:QueryExtender>

DataSourceExpression defines an abstract method called GetQueryable. QueryExtender passes the original query to GetQueryable of first DataSourceExpression, takes the query it returns and passes it to the second DataSourceExpression, and so on, giving each expression the chance to modify the original query by adding a filter, changing its sort order, etc.

Dynamic Filters

DynamicFilterExpression inherits from the DataSourceExpression and overrides its GetQueryable method to take advantage of rich filter templating capabilities provided by the ASP.NET Dynamic Data. Using ControlID property, DynamicFilterExpression is typically associated with a QueryableFilterRepeater, another Dynamic Data control.

<asp:QueryableFilterRepeater runat=”server” ID=”FilterRepeater”>
  <ItemTemplate>
    <asp:Label runat=”server” Text=’<%# Eval(”DisplayName”) %>OnPreRender=”Label_PreRender” />
    <asp:DynamicFilter runat=”server” ID=”DynamicFilter”
      OnFilterChanged=”DynamicFilter_FilterChanged” />
    <br />
  </ItemTemplate>
</asp:QueryableFilterRepeater>

QueryableFilterRepeater is a templated control, similar to the standard Repeater. It generates a DynamicFilter for each column in the queryable data source that can be filtered by Dynamic Data.

Dynamic Filter Class Diagram

DynamicFilter control uses the filter scaffolding functionality of Dynamic Data to locate and load an appropriate user control from the ~/DynamicData/Filters folder of the web application. These user controls inherit from QueryableFilterUserControl, a base class provided by Dynamic Data and designed for use with DynamicFilter.

Notice that all of the classes in the diagram above – DynamicFilterExpression, QueryableFilterRepeater, DynamicFilter and QueryableFilterUserControl – define a GetQueryable method that takes an IQueryable as a parameter and returns an IQueryable. At run time, the responsibility to modify the underlying LINQ query (an IQueryable instance) is passed from a QueryExtender all the way down the chain to a QueryableFilterUserControl, which was implemented to support filtering for a particular type of column, such as a Boolean or a foreign key.

GetQueryable Sequence Diagram

Filter Controls

Out of the box, ASP.NET Dynamic Data provides filter controls for Boolean, Enumeration and Foreign Key columns. This selection is rather limited, especially when you compare it to the wide range of edit controls available for the various column types. Luckily, creating a new filter template is relatively easy.

In the remainder of this article we will create a template filter for columns of DateTime type. The filter control will allow users to filter date values based on a date value they enter and the operator they select, such as equals, before and after. The completed control will look similar to the picture below.

image

  • Add a new user control, DateTime.ascx, to the ~/DynamicData/Filters folder of your Dynamic Data web application. Name of the ASCX file is important, it will be used to provide a filter hint to Dynamic Data.
<%@ Control Language=”C#” CodeBehind=”DateTime.ascx.cs”
  Inherits=”DynamicDataDemo.DateFilter” %> 

<asp:DropDownList ID=”dropDownList” runat=”server” CssClass=”DDFilter”>
  <asp:ListItem Text=”Equals” Value=”==” />
  <asp:ListItem Text=”Before” Value=”<” />
  <asp:ListItem Text=”After” Value=”>” />
</asp:DropDownList> 

<asp:TextBox ID=”textBox” runat=”server” CssClass=”DDTextBox” Columns=”12″ /> 

<asp:CustomValidator ID=”validator” runat=”server” ControlToValidate=”textBox”
  OnServerValidate=”Validate” Text=”*” CssClass=”DDValidator”/>

In this ASCX, we have a drop-down list that displays a list of date operators the users can select from, and a text box, where they can enter a date value. The custom validator, used for the sake of simplicity, ensures that the value they enter in the text box is a valid date.

  • In the code-behind file, change the base class from UserControl to QueryableFilterUserControl. Here is what it will look like at a glance.

Code-behind File at a Glance

  • GetQueryable is the main method in our filter control. Defined as abstract in the base class, this method is responsible for adding a filter to the query it receives.
public override IQueryable GetQueryable(IQueryable source)
{
  if (string.IsNullOrEmpty(this.textBox.Text) || !this.validator.IsValid)
  {
    return source;
  } 

  DateTime date = DateTime.Parse(this.textBox.Text);
  ConstantExpression value = Expression.Constant(date); 

  ParameterExpression parameter = Expression.Parameter(source.ElementType);
  MemberExpression property = Expression.Property(parameter, this.Column.Name);
  if (Nullable.GetUnderlyingType(property.Type) != null)
  {
    property = Expression.Property(property, “Value”);
  } 

  BinaryExpression comparison;
  switch (this.dropDownList.SelectedValue)
  {
    case “==”:
      comparison = Expression.Equal(property, value);
      break;
    case “>”:
      comparison = Expression.GreaterThan(property, value);
      break;
    case “<”:
      comparison = Expression.LessThan(property, value);
      break;
    default:
      Debug.Fail(“Unexpected operator”);
      return source;
  } 

  LambdaExpression lambda = Expression.Lambda(comparison, parameter); 

  MethodCallExpression where = Expression.Call(
    typeof(Queryable),
    “Where”,
    new Type[] { source.ElementType },
    new Expression[] { source.Expression, Expression.Quote(lambda) }); 

  return source.Provider.CreateQuery(where);
}

Unfortunately, because this control can be used for any DateTime column of any entity in our model, we cannot use strongly-typed code to implement this method. Instead of IQueryable<Order> we have to write code at the next level up - with the IQueryable interface and build the LINQ expression tree for our filter from the ground up. When expressed with strongly-typed code, the intent of this method could be distilled to the following pseudo-code:

IQueryable<Order> GetQueryable(IQueryable<Order> source)
{
  if (string.IsNullOrEmpty(this.textBox.Text) || !this.validator.IsValid)
  {
    return source;
  } 

  DateTime date = DateTime.Parse(this.textBox.Text); 

  Expression<Func<Order, bool>> lambda = null;
  switch (this.dropDownList.SelectedValue)
  {
    case “==”:
      lambda = order => order.OrderDate == date;
      break;
    case “>”:
      lambda = order => order.OrderDate > date;
      break;
    case “<”:
      lambda = order => order.OrderDate < date;
      break;
    default:
      Debug.Fail(“Unexpected operator”);
      break;
  } 

  return source.Where(lambda);
}

As you can see in the actual implementation above, we have to explicitly perform all tasks the compiler would normally take care for us, such as declaring the parameter of the lambda expression, extracting the underlying value from a nullable property and calling the proper version of the generic Where method based on the entity type. When looking at the actual code above, remember that Where is an extension method defined by the Queryable class, and not by the IQueryable interface.

  • FilterControl property returns the control that should receive focus when user selects the Label generated for it by the QueryableFilterRepeater. In our date filter, this can be either the drop-down list or the text box.
public override Control FilterControl
{
  get { return this.textBox; }
}

  • The Page_Load event handler performs control initialization tasks that would be typically done declaratively, using markup in a regular filter form where information about columns being filtered is known at design time. In a Dynamic Data filter control, this information is available at run-time through the Column property of the base class, which gives us a MetaColumn object describing the column for which our filter control was generated.
protected void Page_Load(object sender, EventArgs e)
{
  this.textBox.ToolTip = this.Column.Description;
  this.validator.ErrorMessage = “Invalid date specified for ” +
      this.Column.DisplayName;
  this.validator.ToolTip = this.validator.ErrorMessage;
}

  • And finally, the Validate method handles the server-side validation event of the CustomValidator our control uses to ensure that a valid date was entered in the text box.
protected void Validate(object sender, ServerValidateEventArgs e)
{
  DateTime value;
  e.IsValid = DateTime.TryParse(e.Value, out value);
}

Filter Scaffolding

Unlike for scaffolding of the data entry controls, ASP.NET Dynamic Data doesn’t use the same convention-based logic for filter controls. In order to instruct the framework to render a custom filter control for the OrderDate of our Order table, we need to mark the OrderDate property in our model with the FilterUIHintAttribute.

  • Add a new class to the project that contains your Entity Framework (EDMX) or LINQ to SQL (DBML) file and generated entity classes.

using System.ComponentModel.DataAnnotations; 

namespace DynamicDataDemo
{
    [MetadataType(typeof(Order.Metadata))]
    partial class Order
    {
        public class Metadata
        {
            [FilterUIHint(“DateTime”)]
            public object OrderDate;
        }
    }
}

Taking advantage of the fact that Entity Framework generates entity classes as partial, we can use MetadataTypeAttribute to extend it with a custom metadata class and place an attribute on a property defined in the generated code without modifying generated code directly and risking loosing our changes the next time it is regenerated.

  • Place a FilterUIHintAttribute on the property you want to have a filter control. The hint string you specify must match the name of the user control file (DateTime.ascx) without the extension.

Filter Criteria Change Notification

If you are to run your ASP.NET Dynamic Data application now, you should be able to see the new filter for the OrderDate column on the Orders/List.aspx page. However, you will also notice that entering a date in the text box does not filter the results displayed in the grid automatically. This is because our data source control is not aware that filter criteria has changed and that query needs to be executed again. All built-in filter controls are drop-down lists and solve this problem by notifying the data source as soon as the selected value changes. Our custom filter uses a text box and cannot post back to server every time user presses a key when entering a date value. Instead, we will use a Search button to let the user perform the search when they are done with the criteria.

  • Modify ~/DynamicData/PageTemplates/List.aspx to add a “Search” button just below the filter criteria.
<asp:QueryableFilterRepeater runat=”server” ID=”FilterRepeater”>
  <ItemTemplate>
    <asp:Label runat=”server” Text=’<%# Eval(”DisplayName”) %>OnPreRender=”Label_PreRender” />
    <asp:DynamicFilter runat=”server” ID=”DynamicFilter”
      OnFilterChanged=”DynamicFilter_FilterChanged” />
    <br />
  </ItemTemplate>
</asp:QueryableFilterRepeater>
<br />
<asp:Button ID=”searchButton” runat=”server” Text=”Search”
 OnClick=”SearchButton_Click” />
  • Implement SearchButton_Click method in the code-behind file.
protected void SearchButton_Click(object sender, EventArgs e)
{
    ((IQueryableDataSource)this.GridDataSource).RaiseViewChanged();
}

In this method, we are calling the RaiseViewChanged method of the IQueryableDataSource interface to let the data source know that it needs to re-execute the query. Unfortunately, EntityDataSource implements this interface explicitly, which requires a type cast before this method can be called.

Here is how the completed Order/List.aspx page looks after the user entered an order date and clicked the Search button. Note that in the entity model, FilterUIHint attributes were specified for OrderDate, RequiredDate and ShippedDate columns of the Orders table.

 Dynamic Data list page with date filters

Conclusion

ASP.NET Dynamic Data provides powerful filtering capabilities that can be extended to fit needs of many data-driven web applications. Although creating dynamic filter controls is significantly more complex than creating statically typed controls, a dynamic filter control developed for a particular data type can be reused for any column of that type by simply applying the FilterUIHintAttribute to the property representing that column in the model. Unfortunately, Dynamic Data does not use the same convention-based logic for scaffolding of filter controls that it uses for scaffolding of data entry controls. This limitation requires you to explicitly place the FilterUIHintAttribute on every column instead of relying on the framework to infer this information based on the column type.

Download

Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

Thank you very much Oleg. I have been searching “((IQueryableDataSource)this.GridDataSource).RaiseViewChanged();” for hours… You are my hero today :) I will add your rss to my outlook…I have one question for you? Where did you find this info :)

Hi ,

I am facing an issue in the search functionality. The issue is that when we search the records and populate in a gridview or datagrid and apply appropriate paging to the grid, then after doing all this navigate to the last page and try to search on the last page. you will notice that you have click the ‘Search’ button twice to get the search results. If you click only once, it shows you no records. Does anyone have any idea about this.

Regards
Raminder Singh

Hello,

I was hoping if you could please provide an example of how you would implement this for a Text Search (Text Box) instead of a DateTime type of attribute. Is it possible?

Thank you in advance,

Luis.

[…] Comments 0 Dynamic Data provides a good architecture for adding custom filters to your application. To read more about understanding filtering read the following post […]

in page_load we have :
GridDataSource.Include = table.ForeignKeyColumnsNames;
i wonder if this could be made more configurable by reading a custom attribute of the table, that way the search would not be restricted to ForeignKeyColumnsNames as such.

nice article, cheers John, Perth.

Hi Oleg!!

I download your sample but it doesn’t show me any date combobox in the List.aspx, what im doing wrong?

Hi

Its very helpful post. How i can implement some thing like when user logged in they can see only certain data using filter. e.g. company xyz users can see only record start belongs to xyz company into dynamic data. Can you give me more idea about this please.

I have an ASP.Net 4.0 dynamic data web site with some custom controls whose behavior I’d like to organize. For instance, I have an entity (”lines”) with the following navigation properties: businesstype, department and section among others and I have them all set to be filtered (in all 5 base pages) as to shown exclusively records belonging to an specific businesstype_code (which is already a problem solved) but I also want to stay in the same Insert page as to allow my end users to do Bulk Insert (meaning that I set to blank all other fields but dropdownlistboxes - problem solved)but I need the second and third dropdownlist boxes to work in synchrony (the firts one only shows one record which is the name of the type of business gathered from the very beginning during login session - problem solved. What I need to know is how to set the third dropdownlist to accept incoming values selected from the second dropdownlist? So if I choose a department I need to show in the 3rd dropdownlist only those sections that belong to the department chosen. After insertion, I need to do this again and again until the user quits the screen. Is there a way of doing it?

[…] got a lot of inspiration and help from msdn and this blog post to get to a decent implementation for […]

Hi Olge,
Great article! I just want to know how to remove the time part of the database column DateTime value so that only the date excluding the time can be filtered by.
Regards

Hi,
can we somehow change the filters to multiselect listboxes.

I am facing an issue using Dynamic Data Project (VB.Net). I have got two tables Users & Roles. When I click on insert.aspx to assign new roles the users, there in the User DropDownList I could just see the forename of the user, I want to customize the DropDownList, So, it can display Forename & Surname together in the same control. Any idea about it will help me a lot.

[…] For filtering Dynamic Data offers us Filter Templates with FilterRepeater control. To get the idea of how Dynamic Data Filter Templates are working I highly recommend reading a great post of Oleg Sych “Understanding ASP.NET Dynamic Data: Filter Templates”. […]

Hi i am trying to do something similar to what has been asked in comment #7 ( Parimal). can you please email me information.
I need to filter the grid in dynamic data to show records of employee who is logged in on company website.