Click here to Skip to main content
Click here to Skip to main content

Using jqGrid’s search toolbar with multiple filters in ASP.NET MVC

By , 12 Feb 2010
 
Prize winner in Competition "Best ASP.NET article of February 2010"

MvcGrid_Sample

Introduction

Very often, we have a requirement to display data in tabular form and manipulate it. In classic ASP.NET, built-in controls are available. It's always better to use custom scripts to solve this problem in ASP.NET MVC. jqGrid is one of such solutions.

Background

jqGrid

jqGrid is a plugin for jQuery, which allows you to display data in tabular form with greater functionality. Its features include:

  • supports tables within tables
  • Demo1.PNG

  • supports data editing
  • Demo2.PNG

  • supports tree-like structure to display data
  • supports themes on the jQuery UI
  • records searching, filtering of each column, sorting by columns, page navigation, etc.

License

"The jqGrid is released under the GPL and MIT licenses. This license policy makes the software available to everyone for free (as in free beer), and you can use it for commercial or Open Source projects, without any restriction (the freedom above). Of course, the creation of this software has taken some time, and its maintenance will take even more. All the time we use for enhancing jqGrid is time you save on your own projects. Consider all the time you have saved by simply downloading the source file and trying to give it a price: this is the amount of money you should consider sending us to continue our good work."

Official site: trirand.com. Version 3.6.3. is available currently. Note: I use an older version. It has some bugs in IE, but in Firefox it works alright.

Using the code

Using jqGrid

Consider which files we must include to use this plug-in:

<link href="../../Scripts/jqGrid/themes/smoothness/jquery-ui-1.7.2.custom.css" 
  rel="stylesheet" type="text/css" />
<link href="../../Scripts/jqGrid/themes/ui.jqGrid.css" 
  rel="stylesheet" type="text/css" />
<script language="javascript" type="text/javascript" 
  src="<%= Url.Content("/Scripts/jquery-1.3.2.js") %>"></script>
<script language="javascript" type="text/javascript" 
  src="<%= Url.Content("/Scripts/jquery-ui-1.7.2.custom.min.js") %>">

It's obvious that we should include jQuery and JQuery UI scripts, the jqGrid plug-in, and the jqGridHomeIndex.js file which contains the grid declaration. Besides, there are some auxiliary files in the directory \Scripts\jqGrid. We must set the jqGrid settings to help the plug-in to configure itself properly for our needs. In it, we define the headers, columns, format of the data used, the size of the table, etc.:

$('#grid').jqGrid({
    colNames: ['Online', 'Computer', 'IP', 'User'],
    colModel: [
                { name: 'IsOnline', width: 100, index: 'IsOnline', 
                  searchoptions: { sopt: ['eq', 'ne']} },
                { name: 'Name', index: 'Name', 
                  searchoptions: { sopt: ['eq', 'ne', 'cn']} },
                { name: 'IP', index: 'IP', 
                  searchoptions: { sopt: ['eq', 'ne', 'cn']} },
                { name: 'User', index: 'User', 
                  searchoptions: { sopt: ['eq', 'ne', 'cn']} }
              ],
    sortname: 'Name',
    rowNum: 10,
    rowList: [10, 20, 50],
    sortorder: "asc",
    datatype: 'json',
    caption: 'Result of scanning',
    viewrecords: true,
    mtype: 'GET',
    jsonReader: {
        root: "rows",
        page: "page",
        total: "total",
        records: "records",
        repeatitems: false,
        userdata: "userdata"
    },
    url: "/Home/GetData"
})

You can find a lot of information about this plug-in in the Internet, so I won't talk about its settings.

Search

jqGrid has the ability to set filters for searching the necessary data only. For this, in its navigator panel, the following options for searching should be provided:

.navGrid('#pager', { view: false, del: false, add: false, edit: false },
   {}, // default settings for edit
   {}, // default settings for add
   {}, // delete
   {closeOnEscape: true, multipleSearch: true, 
         closeAfterSearch: true }, // search options
   {}
 );
  • multipleSearch - allows search by multiple criteria
  • closeOnEscape - allows to close the search toolbar by pressing the Escape key
  • closeAfterSearch - allows to close the search toolbar after search

But, we must also determine which operations are available for searching on a particular column of the table. It's done in the colModel section in the searchoptions param:

colModel: [
{ name: 'IsOnline', width: 100, index: 'IsOnline', 
  searchoptions: { sopt: ['eq', 'ne']} },
{ name: 'Name', index: 'Name', searchoptions: { sopt: ['eq', 'ne', 'cn']} },
{ name: 'IP', index: 'IP', searchoptions: { sopt: ['eq', 'ne', 'cn']} },
{ name: 'User', index: 'User', searchoptions: { sopt: ['eq', 'ne', 'cn']} 
}],

A set of operations is defined for each field here:

  • eq - equal
  • ne - not equal
  • cn - contains

A pop-up search window will be available after we set these settings:

SearchToolbar.PNG

The drop down list match is used to determine how these settings are linked to each other - with the help of the AND or the OR operator. "+" and "-" are used to add or delete new filtering options. The server will send an asynchronous request with the following parameters after we press the Find button:

_search = true
filters {
"groupOp":"AND",
"rules":[
{"field":"IsOnline","op":"eq","data":"True"},
{"field":"Name","op":"cn","data":"asdasd"}
   ]
}
nd = 1265873327560
page = 1
rows = 10
sidx = Name
sord = asc
  • _search - determines if filtering is used
  • filters - if filtering is used, it provides information in JSON-format on its parameters:
    • groupOp - operator which applies to a group of rules, Rules (AND and OR)
    • rules - a set of rules, where:
      • field - a field where filtering is done
      • op - an operation which the user selected
      • data - a filter which the user entered
  • page - page number
  • rows - page size
  • sidx - a field where sorting is executed
  • sord - sorting direction (asc or desc)

The request is sent by a URL which is set in the jqGrid:

mtype: 'GET',
url: "/Home/GetData"

Now the server must process the request and return the relevant data.

jqGrid processing

The method which will handle this request is declared as follows:

public JsonResult GetData(GridSettings grid)

As shown, it is served with a strongly typed object GridSettings, and in response, it returns JSON-data. In order to bring the object to the method , we use the ASP.NET MVC feature, ModelBinder, which is provided to allow Action methods to take complex types as their parameters.

First of all, we define several types, which will contain data from the jqGrid:

  • GridSettings - stores the jqGrid structure
  • public class GridSettings
    {
        public bool IsSearch { get; set; }
        public int PageSize { get; set; }
        public int PageIndex { get; set; }
        public string SortColumn { get; set; }
        public string SortOrder { get; set; }
    
        public Filter Where { get; set; }
    }
  • Filter - a filter which is defined by the user in the search toolbar
  • [DataContract]
    public class Filter
    {
        [DataMember]
        public string groupOp { get; set; }
        [DataMember]
        public Rule[] rules { get; set; }
    
        public static Filter Create(string jsonData)
        {
            try
            {
                var serializer = 
                  new DataContractJsonSerializer(typeof(Filter));
                System.IO.StringReader reader = 
                  new System.IO.StringReader(jsonData);
                System.IO.MemoryStream ms =
                  new System.IO.MemoryStream(
                  Encoding.Default.GetBytes(jsonData));
                return serializer.ReadObject(ms) as Filter;
            }
            catch
            {
                return null;
            }
        }
    }

    The factoring method Create allows to create an object of this class from the serialized JSON data.

  • Rule - represents a rule from the filter
  • [DataContract]
    public class Rule
    {
        [DataMember]
        public string field { get; set; }
        [DataMember]
        public string op { get; set; }
        [DataMember]
        public string data { get; set; }
    }

In fact, these types are identical to the corresponding structures in jqGrid. Next, we create a class GridModelBinder, which inherits from IModelBinder and overrides the BindModel method. Inside this method, we expect the HttpContext.Request object will contain a query string from jqGrid:

public class GridModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, 
                            ModelBindingContext bindingContext)
    {
        try
        {
            var request = controllerContext.HttpContext.Request;
            return new GridSettings
            {
                IsSearch = bool.Parse(request["_search"] ?? "false"),
                PageIndex = int.Parse(request["page"] ?? "1"),
                PageSize = int.Parse(request["rows"] ?? "10"),
                SortColumn =  request["sidx"] ?? "",
                SortOrder = request["sord"] ?? "asc",
                Where = Filter.Create(request["filters"] ?? "")
            };
        }
        catch
        {
            return null;
        }
    }
}

In order to call this method when accessing Home/GetData, you must add the following attribute to the GridSettings class:

[ModelBinder(typeof(GridModelBinder))]
public class GridSettings

Now a grid object in the GetData method of the Home controller will be initialized correctly.

Filtering and sorting on the server

After we have learned how to get typed form data from the jqGrid, it would be nice to handle and apply them to the collection. Reflection and expression trees will help us here. Here is some information on expression trees from MSDN:

"Expression trees represent language-level code in the form of data. The data is stored in a tree-shaped structure. Each node in the expression tree represents an expression, for example, a method call or a binary operation such as x < y. The following illustration shows an example of an expression and its representation in the form of an expression tree. The different parts of the expression are color coded to match the corresponding expression tree node in the expression tree. The different types of the expression tree nodes are also shown."

ExpressionTrees.PNG

In the LinqExtensions class, I wrote two methods that use this approach:

  • OrderBy
  • Where

The OrderBy method is used for sorting:

public static IQueryable<T> OrderBy<T>(
   this IQueryable<T> query, string sortColumn, string direction)

This is the extension method of the IQueryable<T> object, with these parameters:

  • sortColumn - the name of the column which is sorted
  • direction - direction of sorting

Let us now consider what is done in this method:

  • Define sort order:
  • string methodName = string.Format("OrderBy{0}",
      direction.ToLower() == "asc" ? "" : "descending");
  • Then, we construct the lambda-expression as p => p.Name (or System.Linq.Expressions.Expression<func<t,tkey>>). We create the parameter p, whose type is defined in ElementType.
  • ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");
  • So we get a member of the class, which will be sorted. This code takes into account that a class can be a nested class. In this case, it's expected that they will be separated by a dot:
  • MemberExpression memberAccess = null;
    foreach (var property in sortColumn.Split('.'))
        memberAccess = MemberExpression.Property
           (memberAccess ?? (parameter as Expression), property);
  • The Lambda-expression is completed by the creation of the object which represents calling a method:
  • LambdaExpression orderByLambda = Expression.Lambda(memberAccess, parameter);
    MethodCallExpression result = Expression.Call(
                          typeof(Queryable),
                          methodName,
                          new[] { query.ElementType, memberAccess.Type },
                          query.Expression,
                          Expression.Quote(orderByLambda));
  • Return IQuerable<T>:
  • return query.Provider.CreateQuery<T>(result);

The Where method is used for filtering:

public static IQueryable<T> Where<T>(this IQueryable<T> query,
              string column, object value, WhereOperation operation)
  • column - name column
  • value - filtering value
  • operation - operation used for filtration

The enumeration WhereOperation is declared as follows:

public enum WhereOperation
{
    [StringValue("eq")]
    Equal,
    [StringValue("ne")]
    NotEqual,
    [StringValue("cn")]
    Contains
}

Here, I use the StringValue attribute, which allows to set the string value of the transfer line, which is very convenient in our case. More information about the StringValue attribute can be found here: StringValueAttribute by Matt Simner.

Depending on the type of operation, we must construct an appropriate lambda-expression. For this, we can use the predefined Equal and NotEqual methods of the Expression class, or use Reflection to access the desired method of another class, as we do in the case of the Contains operation:

Expression condition = null;
LambdaExpression lambda = null;
switch (operation)
{
    //equal ==
    case WhereOperation.Equal:
        condition = Expression.Equal(memberAccess, filter);
        break;
    //not equal !=
    case WhereOperation.NotEqual:
        condition = Expression.NotEqual(memberAccess, filter);
        break;
    //string.Contains()
    case WhereOperation.Contains:
        condition = Expression.Call(memberAccess,
            typeof(string).GetMethod("Contains"),
            Expression.Constant(value));
        break;
}
lambda = Expression.Lambda(condition, parameter);

By setting these methods, we can take advantage of them. During filtering, we must go through the entire collection of rules with a call to the Where method. Here is how we filter with the usage of the AND operator:

foreach (var rule in grid.Where.rules)
   query = query.Where<computer>(rule.field, rule.data,
             (WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op));

Filtering using the OR operator:

var temp = (new List<computer>()).AsQueryable();
foreach (var rule in grid.Where.rules)
{
    var t = query.Where<computer>(
    rule.field, rule.data,
    (WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op));
    temp = temp.Concat<computer>(t);
}
//remove repeating records
query = temp.Distinct<computer>();

Sorting is simpler:

query = query.OrderBy<computer>(grid.SortColumn, grid.SortOrder);

Return data jQuery

Now, when we have a collection of the required data, we can send it to the client. Let's see how this is done:

  • Counting the number of records which satisfy the conditions:
  • var count = query.Count();
  • Paging:
  • var data = 
      query.Skip((grid.PageIndex - 1) *grid.PageSize).Take(grid.PageSize).ToArray();
  • Convert to the data format which jqGrid expects. Here, an anonymous class can help us very much:
  • var result = new
    {
        total = (int)Math.Ceiling((double)count / grid.PageSize),
        page = grid.PageIndex,
        records = count,
        rows = (from host in data
                select new
                {
                    IsOnline = host.IsOnline.ToString(),
                    Name = host.Name,
                    IP = host.IP,
                    User = host.User,
                }).ToArray()
    };
  • Serialize in JSON format and send to the client:
  • return Json(result, JsonRequestBehavior.AllowGet);

Data source

We can use the database as a data source. To do this, we need to use ORM tools such as NHibernate (you must use the LinqToNHibernate extension) and LinqToSql, which provides the IQueryable interface.

History

  • 12 February 2010 - First version of the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Ilya Builuk
Software Developer (Senior) EPAM Systems
Belarus Belarus
Member
Interested in design/development of framework functionality using the best patterns and practices.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionquestion about using EF database contextmemberJayveeJavier14 Mar '13 - 2:08 
hi ilya, i am new to the EF and instead of a generated list, i pass the EF database context, I just followed your code here and i have learned a lot. i just wonder why the DBcontext doesn't get sorted after it passed to the LinqExpressions function. But when I debug it, inside the IQueryable...
QuestionNeed help to implement custom searchmemberMember 321208015 Nov '12 - 19:56 
Hi,   I need to implement custom search using above code and do not want to use toolbar search of jqgrid like following link example.   http://zeeshanumardotnet.blogspot.in/2012/04/jqgrid-and-mvc-3-with-custom-paging-and.html[^]   Can anyone help me to head in right direction?
QuestionNeed help in implementing the same in multi tier application.memberMember 321208015 Nov '12 - 6:11 
Hi,   Great article! Helped me a lot and directed in right direction. However I am struggling in figuring it out as How can I implement the same in multi tier application with repository pattern.   I do not want to implement following repetitive code in each controller action....
AnswerRe: Need help in implementing the same in multi tier application.memberIlya Builuk15 Nov '12 - 7:25 
Hi! Some time ago, I was doing something like you are trying to achieve. Try to check out the following project http://catpic.codeplex.com[^] and try to debug it (web-project or unit tests).   Look at the following classes: 1. Expression factory which builds expression trees over...
GeneralRe: Need help in implementing the same in multi tier application.memberMember 321208015 Nov '12 - 16:33 
Thanks Ilya!!!
QuestionHelp Neededmembersaini arun24 Sep '12 - 19:46 
Hi,   I reached here while finding solution to one of the issue with jqGrid which is bothering me from quite some time. Can you please have a look at the question I posted in Q/A forum Filter jqGrid by multiple conditions[^]   Thanks
Questionduplicate results [modified]memberkodesnipper4 Jun '12 - 21:47 
Hi Ilya, your olution realy helped me out - just 1 question, despite adding a distinct method to the results of the OR operation, my results still have duplicate records see stack overflow question...
SuggestionExtending the solutionmembersahookris24 Apr '12 - 20:06 
Excellent article. Made it to work in MVC 3. Also extended 'where' for lt, le, gt, ge operations
GeneralMy vote of 5membermanoj kumar choubey3 Apr '12 - 23:57 
Nice
GeneralMy vote of 4memberBhargava Consultant19 Mar '12 - 20:22 
Good Approach I like it
QuestionAwesome Articlememberzyck18 Feb '12 - 16:08 
Thank you 10up
Suggestionexcellent postmemberbalaji10109 Feb '12 - 5:47 
excellent post
GeneralMy vote of 5mvpKanasz Robert19 Jan '12 - 0:46 
Great. This article is well written and saved me a lot of time.
QuestionValidation on searchmemberFellipe Vieira28 Dec '11 - 4:31 
Hi,   Thanks a lot for this article. It helped me a lot on my learning. But I have a doubt about searching for items listed in the grid. When I click the search button, it shows a popup, asking me to enter values for filtering, but, if I don't supply values in textfields, an exception...
AnswerRe: Validation on searchmemberIlya Builuk28 Dec '11 - 7:48 
Hi! Try to implement client-side validation using the following approach: http://stackoverflow.com/questions/2004993/validation-in-jqgrid[^] Also do not forget about server-side validation.
QuestionMuch simpler solution using System.Linq.Dynamicmemberdimitry.profus@gmail.com2 Oct '11 - 17:40 
public ActionResult CustomerGridData(GridSettings grid) {   int totalRecords; var data = _customerService.CustomerTable.SearchGrid(grid, out totalRecords);   //converting in grid format var result = new { total = (int)Math.Ceiling((double)totalRecords /...
AnswerRe: Much simpler solution using System.Linq.DynamicmemberMark J Bailey19 Oct '11 - 8:03 
Dimitry,   Very clean example and nice application of Dynamic LINQ! But how would you handle the exception thrown:   Operator '=' incompatible with operand types 'DateTime?' and 'String'   when the column is a datetime (string in colModel but DateTime? in C#/database?...
QuestionThanks for this, but I have an issue..memberNirosh25 Aug '11 - 18:11 
Hi Ilya,   Great work..   I managed to get this work in a MVC3 project. In that the records were not visible, though they looks to be loaded correctly (total records count, page numbers values are correct). Then I had to set 'repeatitems: true' to them to see. Is this the right way...
AnswerRe: Thanks for this, but I have an issue..memberNirosh25 Aug '11 - 21:17 
okay I got everything sorted out.. I added scripts to the view, then it started to work.. - A random opportunity is like a taller chair, those who sit hang on, those who hang on fall   L.W.C. Nirosh. Colombo, Sri Lanka.
QuestionGenerated SQL not parameterizedmemberPaul Newman UK22 Jun '11 - 8:03 
I changed the app to connect to a database (Oracle 11.2, via Entity Framework and the ODAC data access components, version 11.2.0.2.30 beta).   When I look at the SQL submitted to the database, the filter parameters are embedded as constants, e.g.: WHERE (N'User2' = "Extent1"."User")...
GeneralFantastic!memberMarc Merritt10 Jun '11 - 4:49 
Thanks very much for this article. I was able to adapt your code for a non-MVC project I've been working on for several months. Your solution was ideal for solving my technical issues.
QuestionQuestion about Date searching. [modified]memberMatthew Cuba31 May '11 - 8:22 
Very good article - it has helped me along with figuring out jqGrid. Thank you!   I've got two columns of data - one always has a Date and the other is nullable. Wanted to use the date picker for this so I added specified the colModel like this for those columns:   colModel: [...
AnswerRe: Question about Date searching.memberMatthew Cuba1 Jun '11 - 15:27 
So a solution for this problem came from a reply to a stackoverflow post by the very smart C# compiler fellow, Eric Lippert, here[^]. Essentially, what I did was something like his post suggested:   static Expression MyGreaterThan(Expression e1, Expression e2) { if...
GeneralRe: Question about Date searching.memberIlya Builuk1 Jun '11 - 21:42 
Thanks for your comment
QuestionError when use a getproperty(property) in a complex objectmemberRusito27 May '11 - 8:07 
Hi my friend, i use your code with very small modifications, but throw me some errors when the code call a type.GetProperty(prop) because the prop is in an another object of this property, for example:   public class A { string prop { get; set; } }   public class B { ...

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 12 Feb 2010
Article Copyright 2010 by Ilya Builuk
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid