Feb 6 2011

Data Filtering, in the New Mvc 3 Version of the Mvc Controls Toolkit

Category: Entity Framework | MVC | Asp.netFrancesco @ 05:11

See also an updated version of this post:  Advanced Data Filtering Techniques in the Mvc Controls Toolkit

Mvc Controls Toolkit Datagrid Updated Tutorial (updated version of Defining MVC Controls 2: Using the DataGrid)

Defining MVC Controls 3: Datagrid, Sorting, and Master-Detail Views

The Mvc Controls Toolkit offers an interesting data filtering feature: the controller can receive directly a LinQ expression defining the filtering criteria chosen by the user when a view is posted. This way the user can choose among several filtering options by clicking buttons, or selecting checkboxes and then filling the input fields of the selected filtering options. No need to define ViewModel properties for each of the available options, the action method of the controller receives always the same data structure: a LinQ Expression. New filtering options can be easily added modularly with the no need to modify the controller and the code behind it. This increases modularity and makes easier  software maintenance.

Let see how this works in practice with the help of the same example used in my previous posts about the Mvc Controls Toolkit library(see the links next to the title). The whole code used in this example can be downloaded here(the file named BasicTutorialsCode), while screenshots of the software running are here. .

First of all we add the property destined to contain the LinQ expression to the ViewModel:

         public int TotalPages { get; set; }
        public int CurrPage { get; set; }
        public int PrevPage { get; set; }
        public List<Tracker<ToDoView>> ToDoList {get; set;}
        public List<KeyValuePair<LambdaExpression, OrderType>> ToDoOrder { get; set; }
        public System.Linq.Expressions.Expression<Func<ToDoView, bool>> ToDoFilter { get; set; }

The LinQ Expression is a map from ToDoView, that is the class the filter will be applied to, and bool, since formally it defines a function that verifies if an instance of ToDoView satisfies the criterium of the filter.

Now we can use this expression directly in the method that retrieves the items:

if (filter == null)
                {
                    result = context.ToDo.Select(item =>
                        new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id }).ApplyOrder(order).Select(viewItem =>
                        new Tracker<ToDoView>
                                {
                                    Value = viewItem,
                                    OldValue = viewItem,
                                    Changed = false
                                }).Skip(toSkip).Take(pageDim).ToList();
                }
                else
                {
                    result = context.ToDo.Select(item =>
                        new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id }).Where(filter).ApplyOrder(order).Select(viewItem =>
                        new Tracker<ToDoView>
                        {
                            Value = viewItem,
                            OldValue = viewItem,
                            Changed = false
                        }).Skip(toSkip).Take(pageDim).ToList();
                }

The code above shows that it was enough to add a Where(filter) instruction to our original code to get the filtered data.

Now we need to define a class for each filtering option. For sake of semplicity let just define a single filtering option. Our class needs just to implement an interface:

public interface IFilterDescription<TData>
    {
        Expression<Func<TData, bool>> GetExpression();
    }

The GetExpression function returns the actual Filter. The input fields that the user is required to fill are represented by properties of this class, and are used in the implementation of GetExpression():

public class ToDoItemByNameFilter: 
        IFilterDescription<ToDoView>
    {
        [Required]
        [Display(Prompt="chars the name of item starts with")]
        public string Name {get; set;}
        public System.Linq.Expressions.Expression<Func<ToDoView, bool>> GetExpression()
        {
            System.Linq.Expressions.Expression<Func<ToDoView, bool>> res = null;
            Name=Name.Trim();
            if (!string.IsNullOrEmpty(Name))
            {
                Name=Name.Trim();
                res= m => (m.Name.StartsWith(Name));
                
            }
            return res;
        }
    }

In our case we would like to select all items whose Name field starts with the characters inserted in the Name property defined in our ToDoByNameFilter class.

We just Trim our input and then we use it in the LinQ expression returned by GetExpression.

Our ToDoItemByNameFilter plays also the role of ViewModel for a partial view that takes care of the user interface for our filter, so we can decorate its Name property with attributes defining both validation and appearance constraints. In our case we defined a WatermarK through the Prompt property of the Display attribute.

In order to make the watermark actually apppears and the textbox add some adequate formatting we can use the TypedTextBox of the Mvc Controls Toolkit in the partial view that defines the graphical appearance of our filter:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Controls.ToDoItemByNameFilter>" %>
<%@ Import Namespace=" MVCControlsToolkit.Core" %>
<%@ Import Namespace=" MVCControlsToolkit.Controls" %>
<%: Html.TypedTextBoxFor(m => m.Name, watermarkCss:"watermark") %>

Now it remains just to insert the helper of the filter in our View. In order to let the user selects among several filters we can use the ViewList defined in the Mvc Controls Toolkit. This way either by clicking some mutual exclusive checkboxes or by clicking some button, the user let the Partial View associate to a Filter to appear. In our case for sake of simplicity we give to the user just the choice between our previous Name filter and no filter at all:

<div >
    <%: Html.ViewList("ToDoFilter", "ToDoFilterSelected") %> 
        <input id="Checkbox1" type="checkbox" class = 'ToDoFilter_checkbox ByNameFilter_checkbox'/>  &nbsp; Filter by name 
        <span id='ByNameFilter' class='ToDoFilter'><%:Html.DataFilter(m => m.ToDoFilter, 
                                new Mvc_Examples.Controls.ToDoItemByNameFilter(),
                                           "ToDoFilterByName")%></span>  
    </div>
    <div >
    <input id="Checkbox2" type="checkbox" class = 'ToDoFilter_checkbox NoFilter_checkbox'/>  &nbsp; NoFilter
    <span id='NoFilter' class='ToDoFilter'></span>
    </div>

The arguments of our DataFilter helper are just an expression that selects the ViewModel property to fill with the filtering expression, an instance of the class that defines the filter, and the name of the Partial View that we defined before.

The ViewList helper selects one of two spans, one contains the filter helper and the other is simply empty. The selection is done with two checkboxes.Details on how to use of the VieList can be found in the documentation here.

By adding a new argument to our DetailView Helper we can make also our detail view appears in a jQuery UI dialog:

<% Html.DetailFormFor(Ajax, m => m.ToDoList, ExternalContainerType.div,
           "ToDoSubTasks", "Home", null, "isChangedToDo", "isDeletedToDo", detailDialog:
           new MVCControlsToolkit.Controls.DataGrid.Dialog
           {
               Title = "prova dialogo",
               Show = "slide",
               Hide = "slide",
               MinWidth=800

           });%>
</asp:Content>

THAT’S ALL!  Download the code and enjoy!

                             

                                       Stay Tuned!

                                               Francesco

Tags: , , , , , ,

Comments