ASP.NET MVC 2.0 Implementation of searching in jqgrid

Probably you have problem on the server side. Could you append your question with the code of DynamicGridData action which you currently use. The action should have filters as the parameter.

Some parts of your current code are definitively wrong. For example jqGrid is the jQuery plugin. So the methods of jQuery will be extended with the main jqGrid method which you use as jQuery("#list").jqGrid(...);. So after the initializing of jqGrid jQuery("#list").jqGrid will be a function. In you code (the last statement) you overwrite the jQuery("#list").jqGrid method with the object { search: { ... } }. What you should do instead is

jQuery.extend(jQuery.jgrid.search, {
    odata : ['equal', 'not equal','contains']
});

like for example here is described how to overwrite the emptyrecords default value. You don’t need to include the values which are already the same in the default jqGrid settings.

Moreover if you use searchoptions: { sopt: ['eq', 'ne', 'cn']} on all searchable columns you don’t need to do the change.

In the text of your question you don’t explained what you want to do. Your current code is so that you use the filter Message equal to true at the initial grid loading. Strange is that there are no column with the name Message in the grid. If you want just send some additional information to the server you should better use postData parameter:

postData: {Message:true}

I continue to recommend you to remove garbage from the jqGrid definition like imgpath and multipleSearch parameters of jqGrid and sortable: true, search: true, sorttype: 'text', autoFit: true, stype:'text', align: 'left' which are either unknown or default.

UPDATED: The original code of the Phil Haack demo is very old and it use LINQ to SQL. Like I wrote before (see here) Entity Framework (EF) allows to use sorting, paging and filtering/searching without any AddOns like LINQ Dynamic Query Library in form System.Linq.Dynamic. So I made the demo you you which is modification of the the Phil Haack demo to EF.

Because you use the old version of Visual Studio (VS2008 with ASP.NET MVC 2.0) I made the demo also in VS2008.

You can download my VS2008 demo from here and VS2010 demo here.

In the code I show (additionally to the usage of Advanced Searching and Toolbar Searching in ASP.NET MVC 2.0) how to return exception information from ASP.NET MVC in JSON format and how to catch the information with the loadError method and display the corresponding error message.

To construct the Where statement from the ObjectQuery represented EF object I define the following helper class:

public class Filters {
    public enum GroupOp {
        AND,
        OR
    }
    public enum Operations {
        eq, // "equal"
        ne, // "not equal"
        lt, // "less"
        le, // "less or equal"
        gt, // "greater"
        ge, // "greater or equal"
        bw, // "begins with"
        bn, // "does not begin with"
        //in, // "in"
        //ni, // "not in"
        ew, // "ends with"
        en, // "does not end with"
        cn, // "contains"
        nc  // "does not contain"
    }
    public class Rule {
        public string field { get; set; }
        public Operations op { get; set; }
        public string data { get; set; }
    }

    public GroupOp groupOp { get; set; }
    public List<Rule> rules { get; set; }
    private static readonly string[] FormatMapping = {
        "(it.{0} = @p{1})",                 // "eq" - equal
        "(it.{0} <> @p{1})",                // "ne" - not equal
        "(it.{0} < @p{1})",                 // "lt" - less than
        "(it.{0} <= @p{1})",                // "le" - less than or equal to
        "(it.{0} > @p{1})",                 // "gt" - greater than
        "(it.{0} >= @p{1})",                // "ge" - greater than or equal to
        "(it.{0} LIKE (@p{1}+'%'))",        // "bw" - begins with
        "(it.{0} NOT LIKE (@p{1}+'%'))",    // "bn" - does not begin with
        "(it.{0} LIKE ('%'+@p{1}))",        // "ew" - ends with
        "(it.{0} NOT LIKE ('%'+@p{1}))",    // "en" - does not end with
        "(it.{0} LIKE ('%'+@p{1}+'%'))",    // "cn" - contains
        "(it.{0} NOT LIKE ('%'+@p{1}+'%'))" //" nc" - does not contain
    };
    internal ObjectQuery<T> FilterObjectSet<T> (ObjectQuery<T> inputQuery) where T : class {
        if (rules.Count <= 0)
            return inputQuery;

        var sb = new StringBuilder();
        var objParams = new List<ObjectParameter>(rules.Count);

        foreach (Rule rule in rules) {
            PropertyInfo propertyInfo = typeof (T).GetProperty (rule.field);
            if (propertyInfo == null)
                continue; // skip wrong entries

            if (sb.Length != 0)
                sb.Append(groupOp);

            var iParam = objParams.Count;
            sb.AppendFormat(FormatMapping[(int)rule.op], rule.field, iParam);

            // TODO: Extend to other data types
            objParams.Add(String.Compare(propertyInfo.PropertyType.FullName,
                                         "System.Int32", StringComparison.Ordinal) == 0
                              ? new ObjectParameter("p" + iParam, Int32.Parse(rule.data))
                              : new ObjectParameter("p" + iParam, rule.data));
        }

        ObjectQuery<T> filteredQuery = inputQuery.Where (sb.ToString ());
        foreach (var objParam in objParams)
            filteredQuery.Parameters.Add (objParam);

        return filteredQuery;
    }
}

In the example I use only two datatypes integer (Edm.Int32) and string (Edm.String). You can easy expand the example to use more types based as above on the propertyInfo.PropertyType.FullName value.

The controller action which provide the data to the jqGrid will be pretty simple:

public JsonResult DynamicGridData(string sidx, string sord, int page, int rows, bool _search, string filters)
{

    var context = new HaackOverflowEntities();
    var serializer = new JavaScriptSerializer();
    Filters f = (!_search || string.IsNullOrEmpty (filters)) ? null : serializer.Deserialize<Filters> (filters);
    ObjectQuery<Question> filteredQuery =
        (f == null ? context.Questions : f.FilterObjectSet (context.Questions));
    filteredQuery.MergeOption = MergeOption.NoTracking; // we don't want to update the data
    var totalRecords = filteredQuery.Count();

    var pagedQuery = filteredQuery.Skip ("it." + sidx + " " + sord, "@skip",
                                        new ObjectParameter ("skip", (page - 1) * rows))
                                 .Top ("@limit", new ObjectParameter ("limit", rows));
    // to be able to use ToString() below which is NOT exist in the LINQ to Entity
    var queryDetails = (from item in pagedQuery
                        select new { item.Id, item.Votes, item.Title }).ToList();

    return Json(new {
                    total = (totalRecords + rows - 1) / rows,
                    page,
                    records = totalRecords,
                    rows = (from item in queryDetails
                            select new[] {
                                item.Id.ToString(),
                                item.Votes.ToString(),
                                item.Title
                            }).ToList()
                });
}

To send the exception information to the jqGrid in JSON form I replaced the standard [HandleError] attribute of the controller (HomeController) to the [HandleJsonException] which I defined as the following:

// to send exceptions as json we define [HandleJsonException] attribute
public class ExceptionInformation {
    public string Message { get; set; }
    public string Source { get; set; }
    public string StackTrace { get; set; }
}
public class HandleJsonExceptionAttribute : ActionFilterAttribute {
    // next class example are modification of the example from
    // the http://www.dotnetcurry.com/ShowArticle.aspx?ID=496
    public override void OnActionExecuted(ActionExecutedContext filterContext) {
        if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null) {
            filterContext.HttpContext.Response.StatusCode =
                (int)System.Net.HttpStatusCode.InternalServerError;

            var exInfo = new List<ExceptionInformation>();
            for (Exception ex = filterContext.Exception; ex != null; ex = ex.InnerException) {
                PropertyInfo propertyInfo = ex.GetType().GetProperty ("ErrorCode");
                exInfo.Add(new ExceptionInformation() {
                    Message = ex.Message,
                    Source = ex.Source,
                    StackTrace = ex.StackTrace
                });
            }
            filterContext.Result = new JsonResult() {Data=exInfo};
            filterContext.ExceptionHandled = true;
        }
    }
}

On the client side I used the following JavaScript code:

var myGrid = $('#list'),
    decodeErrorMessage = function(jqXHR, textStatus, errorThrown) {
        var html, errorInfo, i, errorText = textStatus + '\n' + errorThrown;
        if (jqXHR.responseText.charAt(0) === '[') {
            try {
                errorInfo = $.parseJSON(jqXHR.responseText);
                errorText = "";
                for (i=0; i<errorInfo.length; i++) {
                   if (errorText.length !== 0) {
                       errorText += "<hr/>";
                   }
                   errorText += errorInfo[i].Source + ": " + errorInfo[i].Message;
                }
            }
            catch (e) { }
        } else {
            html = /<body.*?>([\s\S]*)<\/body>/.exec(jqXHR.responseText);
            if (html !== null && html.length > 1) {
                errorText = html[1];
            }
        }
        return errorText;
    };
myGrid.jqGrid({
    url: '<%= Url.Action("DynamicGridData") %>',
    datatype: 'json',
    mtype: 'POST',
    colNames: ['Id', 'Votes', 'Title'],
    colModel: [
        { name: 'Id', index: 'Id', key: true, width: 40,
            searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge'] }
        },
        { name: 'Votes', index: 'Votes', width: 40,
            searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge'] }
        },
        { name: 'Title', index: 'Title', width: 400,
            searchoptions: { sopt: ['cn', 'nc', 'bw', 'bn', 'eq', 'ne', 'ew', 'en', 'lt', 'le', 'gt', 'ge'] }
        }
    ],
    pager: '#pager',
    rowNum: 10,
    rowList: [5, 10, 20, 50],
    sortname: 'Id',
    sortorder: 'desc',
    rownumbers: true,
    viewrecords: true,
    altRows: true,
    altclass: 'myAltRowClass',
    height: '100%',
    jsonReader: { cell: "" },
    caption: 'My first grid',
    loadError: function(jqXHR, textStatus, errorThrown) {
        // remove error div if exist
        $('#' + this.id + '_err').remove();
        // insert div with the error description before the grid
        myGrid.closest('div.ui-jqgrid').before(
            '<div id="' + this.id + '_err" style="max-width:'+this.style.width+
            ';"><div class="ui-state-error ui-corner-all" style="padding:0.7em;float:left;"><span class="ui-icon ui-icon-alert" style="float:left; margin-right: .3em;"></span><span style="clear:left">' +
                        decodeErrorMessage(jqXHR, textStatus, errorThrown) + '</span></div><div style="clear:left"/></div>')
    },
    loadComplete: function() {
        // remove error div if exist
        $('#' + this.id + '_err').remove();
    }
});
myGrid.jqGrid('navGrid', '#pager', { add: false, edit: false, del: false },
              {}, {}, {}, { multipleSearch: true, overlay: false });
myGrid.jqGrid('filterToolbar', { stringResult: true, searchOnEnter: true, defaultSearch: 'cn' });
myGrid.jqGrid('navButtonAdd', '#pager',
            { caption: "Filter", title: "Toggle Searching Toolbar",
                buttonicon: 'ui-icon-pin-s',
                onClickButton: function() { myGrid[0].toggleToolbar(); }
            });

As the result if one types any non-numeric text (like ‘ttt’) in the searching toolbar one receive exception the controller action code (in Int32.Parse(rule.data)). One the client side one will see the following message:

enter image description here

I send from the controller to the jqgrid the information about all internal exceptions. So for example, the error in connection to the SQL server will looks like

enter image description here

In the real world one verify the users input and throws exception with application oriented error message. I used in the demo specially no such kind of validation to show that all kind of exception will be cached and display by jqGrid.

UPDATED 2: In the answer you will find the modified VS2010 demo (downloadable from here) which demonstrate the usage of jQuery UI Autocomplete. Another answer extend the code more to export the grid contain in Excel format.

Leave a Comment