DataFilter and DataFilterBuilder Helpers 

The DataFilter helper allows the Controller receives directly a boolean LinQ expression to be used to filter some data, when the View is posted. This way the Controller don't need to know the details of the filtering, and the ViewModel don't need to include all properties the filter will be based on. 

This feature increases the modularity of the application and th reusability of a controller, since changes in the filtering criteria doesn't require changes to the Controller. Moreover, it allows the user specifies dynamically the kind of filtering he would like to apply. The set of "allowed" filtering criteria can be enlarged without any change in the Controller and in the code behind it.

The dynamic selection of the filtering criteria can be accomplished easily with the help of the ViewList and VieListFor helpers, by putting in each element of the Choice Set a different DataFilter helper.

The specification of a filtering criterion is done by providing an implementation of the IFilterDescription<T> interface. The IFilterDescription<T> interface is defined as:

 

namespace MVCControlsToolkit.Controls.DataFilter
{
    public interface IFilterDescription<TData>
    {
        Expression<Func<TData, bool>> GetExpression();
    }
}

 

The TData generic will be instantiated with the class the filtering will be applied to. Please notice, that this class doesn't absolutely need to be an EF class, since LinQ filtering can be applied to any IQueryable. Thus, in order to avoid using directly an EF class it is enough to specify a new class in some select LinQ statement, and then to apply filtering to the IQueryable obtained this way.

The implementation of the IFilterDescription<T> will contain properties whose value define the filtering criterium. For instance:

 

public class ToDoItemByNameFilter: 
        IFilterDescription<ToDoItem>
    {
        [Required]
        public string Name {get; set;}
        public System.Linq.Expressions.Expression<Func<ToDoItem, bool>> GetExpression()
        {
            System.Linq.Expressions.Expression<Func<ToDoItem, bool>> res=null;
            Name=Name.Trim();
            if (!string.IsNullOrEmpty(Name))
            {
                Name=Name.Trim();
                res= m => (m.Name == Name);
                
            }
            return res;
        }
    }

 

The above implementation is specific for the ToDoItem class, and contains the property Name that is rendered in the View. Then, the value of Name prompted by the user is used to define a filter that selects all ToDoItem objects whose Name is equal to the value of the Name property of the above ToDoItemByNameFilter class provided by the user.

The namespace MVCControlsToolkit.Linq contains a class that helps in building dynamically filter expressions possibly composed by several clauses: FilterBuilder<T>, where T is the DataType we would like to apply the filter to. Clauses may be added to the filter or not depending on the value of input fields by means of the Add method:

 

public FilterBuilder<T> Add(bool toAdd, Expression<Func<T, bool>> filterClause)

filterClause is added to the filter being built only if toAdd is true. Since toAdd may depends on the user input(for instance it may be connected to a chekbox that selects that filtering criterium) the user is enabled to select dynamically its global filtering strategy. Add returns the same object it was called by, in such a way that more Add calls can be chained one after the other. When we finish adding clauses we call the Get() method to take the whole filter expression.

Below an implementation of IFilterDescription<T> that uses The FilterBuilder<T> class (This example is contained in the Mvc3 Razor Theming Example) :

 

    public class ToDoGeneralFilter :
        IFilterDescription<ToDoView>
    { 
        [Display(Prompt = "chars the name of item starts with")]
        public string Name { get; set; }
        public bool NameEnabled { get; set; }
        [Display(Name = "From Date")]
        public DateTime FromDate { get; set; }
        public bool FromDateEnabled { get; set; }
        [Display(Name = "To Date")]
        public DateTime ToDate { get; set; }
        public bool ToDateEnabled { get; set; }
        public System.Linq.Expressions.Expression<Func<ToDoView, bool>> GetExpression()
        {
            System.Linq.Expressions.Expression<Func<ToDoView, bool>> res = new FilterBuilder<ToDoView>()
                .Add(NameEnabled && (!string.IsNullOrWhiteSpace(Name)), m => m.Name.StartsWith(Name))
                .Add(FromDateEnabled, m => m.DueDate >= FromDate)
                .Add(ToDateEnabled, m => m.DueDate <= ToDate)
                .Get();
            return res;
        }
    }

 

 

In the example above the user is enabled to use one or more among the three filtering criteria: some characters Name must starts with, and lower than/greater than constraints on DueDate.

 

The way the  properties of the IFilterDescription<T> to be filled by the user are rendered is specified by a template whose that is passed as argument to the DataFilter helper:

 

<%: Html.DataFilter(m=>m.ItemFilter, new ToDoItemByNameFilter(), "ToDoItemByNameFilterView") %>

 

The first argument of the helper is the ViewModel property that will be filled with the LinQ filter, the second one is an instance of the implementation of IFilterDescription<T>, and finally the last argument is the name of a typed partial view specific for the IFilterDescription<T> implementation class.There, one can specify how to render all properties that are used in the filter. Obviously such properties can be decorated with Validation attributes and they will be validated as normal ViewModel properties.

Instead of rendering the input fileds in a PartialView one can render them directly in the current view with the HtmlHelper returned by the DataFilterBuilder helper:

 

@{var filter = Html.DataFilterBuilder(m => m.ToDoFilter, new ToDoGeneralFilter());}

 

That accepts all arguments of the DataFilter helper, but the the template that is no more needed . The filter helper can be used to render all fields of ToDoGeneralFilter in exactly the same way we use the standard HtmlHelper of a View. Below an example of use:

<table>
         <tr>
             <td align="left" valign="top" style="width:300px">
              @filter.LabelFor(m => m.Name)
              @filter.CheckBoxFor(m => m.NameEnabled,
                new Dictionary<string, object>() { { "class", "NameGroup_checkbox" } })
              
              @filter.TypedTextBoxFor(m => m.Name, 
                new Dictionary<string, object> (){{"class", "NameGroup"}}, 
                watermarkCss: "watermark")
                @filter.ViewsOnOff("NameGroup", false)
             </td>
             <td align="left" valign="top" >
                @filter.LabelFor(m => m.FromDate)
                @filter.CheckBoxFor(m => m.FromDateEnabled,
                    new Dictionary<string, object>() { { "class", "FromGroup_checkbox" } })
                
                
             </td>
             <td align="left" valign="top" style="width:300px">
                 <div class ="FromGroup" >
                        @filter.DateTimeFor(m => m.FromDate, 
                            DateTime.Now.AddMonths(-6),
                            true).DateCalendar(
                                new CalendarOptions{
                                       ChangeYear = true,
                                       ChangeMonth =true
                                })
                 </div>     
                 @filter.ViewsOnOff("FromGroup", false) 
             </td>
             <td align="left" valign="top" >
                 @filter.LabelFor(m => m.ToDate)
                 @filter.CheckBoxFor(m => m.ToDateEnabled,
                    new Dictionary<string, object>() { { "class", "ToGroup_checkbox" } })
                 
                 
             </td>
             <td align="left" valign="top" style="width:300px">
                <div class ="ToGroup" >
                         @filter.DateTimeFor(m => m.ToDate,
                                DateTime.Now.AddMonths(6),
                                true).DateCalendar(
                                    new CalendarOptions{
                                           ChangeYear = true,
                                           ChangeMonth =true
                                    })
                    
                 </div>  
                 @filter.ViewsOnOff("ToGroup", false)
             </td>
         </tr>
     </table>

Please notice the use of the ViewsOnOff helper that enables the various checkboxes to attach/detach their associate input fields.

A complete tutoria on Data Filtering is here: Advanced Data Filtering Techniques in the Mvc Controls Toolkit

While the associated sample code is the  Mvc3 Razor - Filtering project contained in the BasicTutorialsCode file in the download area

Last edited Jun 22, 2014 at 11:52 AM by frankabbruzzese, version 12

Comments

No comments yet.