In your question you used mostly the demo project from my old answer. All other later answers which shows how to implement Advanced Searching, paging and sorting on the server (for example this one or this one) I used more recent ASP.NET technologies, mostly ASP.NET MVC. On the other side the code from the demos one can decide in following parts:
- The server code which provide some interface which can be used by jqGrid to get JSON response. It can be ASP.NET MVC controller action, WFC method, WebMethod of ASMX web service or General ASHX Handler like you use.
- The server code which analyse the input parameter send by jqGrid. The default names of the parameters are
page
, rows
, sidx
, sord
, _search
, filters
. One can rename the parameters using prmNames
option of jqGrid.
- Access to the database. The part of the server code depends on the technology which you use. It can be for example Entity Framework, LINQ to SQL or more old but with good performance
SqlCommand
with SqlDataReader
.
- Encoding of results as JSON. One can use for example standard JavaScriptSerializer or DataContractJsonSerializer or high-performance JSON framework Json.NET (knows as Newtonsoft.JSON). Microsoft uses and supports the open source Json.NET serializer in the new version of ASP.NET MVC 4.0 and ASP.NET Web API. One can include Json.NET in the ASP.NET project and can update it to the last recent version using NuGet.
- Handling on the server the exceptions and reporting the error information to the clients (jqGrid) in the JSON format. The code is a little different depend on the technology used on the server. The client code in
loadError
callback of jqGrid should decode the error information and display it in some form. In case of usage ASP.NET MVC I shown in the answer how to implement HandleJsonExceptionAttribute
attribute which can be used as [HandleJsonException]
instead of the standard [HandleError]
. In case of usage WCF one can use WebFaultException<string>
(see here). In case of General ASHX Handler one can use Application_Error
of Global.asax
for the purpose.
- Optionally one can includes in the server code setting of ETag in the HTTP header. It allows to control client side cache on the server side. If the client need JSON data previously returned from the server it will automatically send
If-None-Match
part in the HTTP request to the server which contain ETag
from the previous server response. The server can verify whether the server data are changed since the last response. At the end the server will either returns the new JSON data or an empty response which allows the client to use the data from the old response.
- One need to write JavaScript code which create jqGrid.
I made the demo project which demonstrate all the above steps. It contains small Database which I fill with information about some from the famous mathematicians. The demo display the grid
where one can use sorting, paging, toolbar filtering or advanced searching. In case of some error (for example if you stop Windows Service "SQL Server (SQLEXPRESS)") you will see the error message like the following:
The C# code which implements ASHX handle in the demo is
using System;
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Newtonsoft.Json;
namespace jqGridASHX {
// ReSharper disable InconsistentNaming
public class jqGridData : IHttpHandler {
// ReSharper restore InconsistentNaming
public void ProcessRequest (HttpContext context) {
// to be able to use context.Response.Cache.SetETag later we need the following:
context.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
// to use with HTTP GET we want be sure that caching of data work correct
// so we request revalidation of data by setting in HTTP header the line
// "Cache-Control: private, max-age=0"
context.Response.Cache.SetMaxAge (new TimeSpan (0));
string numberOfRows = context.Request["rowsPerPage"];
int nRows, iPage;
if (String.IsNullOrEmpty (numberOfRows) || !int.TryParse (numberOfRows, NumberStyles.Integer, CultureInfo.InvariantCulture, out nRows))
nRows = 10; // default value
string pageIndex = context.Request["pageIndex"];
if (String.IsNullOrEmpty(pageIndex) || !int.TryParse(pageIndex, NumberStyles.Integer, CultureInfo.InvariantCulture, out iPage))
iPage = 10; // default value
string sortColumnName = context.Request["sortByColumn"];
string sortOrder = context.Request["sortOrder"];
string search = context.Request["isSearching"];
string filters = context.Request["filters"];
// we can use high-performance Newtonsoft.Json
string str = JsonConvert.SerializeObject (
MyData.GetDataForJqGrid (
nRows, iPage, sortColumnName,
!String.IsNullOrEmpty (sortOrder) &&
String.Compare (sortOrder, "desc", StringComparison.Ordinal) == 0
? MyData.SortOrder.Desc
: MyData.SortOrder.Asc,
search != null && String.Compare (search, "true", StringComparison.Ordinal) == 0,
filters));
context.Response.ContentType = "application/json";
// calculate MD5 from the returned data and use it as ETag
byte[] hash = MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(str));
string newETag = Convert.ToBase64String(hash);
// compare ETag of the data which already has the client with ETag of response
string incomingEtag = context.Request.Headers["If-None-Match"];
if (String.Compare(incomingEtag, newETag, StringComparison.Ordinal) == 0)
{
// we don't need return the data which the client already have
context.Response.SuppressContent = true;
context.Response.StatusCode = (int)HttpStatusCode.NotModified;
return;
}
context.Response.Cache.SetETag(newETag);
context.Response.Write(str);
}
public bool IsReusable {
get { return false; }
}
}
}
It uses Newtonsoft.Json
for JSON serialization and uses MD5 hash as ETag
.
The method MyData.GetDataForJqGrid
provide the data from the database. In the demo I use mainly the code from the answer, so I use Entity Framework to access the database:
using System;
using System.Data.Objects;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json;
namespace jqGridASHX
{
public static class MyData
{
public enum SortOrder {
Asc,
Desc
}
public static Object GetDataForJqGrid(int nRows, int iPage,
string sortColumnName, SortOrder sortOrder,
bool isSearch, string filters)
{
var context = new MyDatabaseEntities();
var f = (!isSearch || string.IsNullOrEmpty(filters)) ? null : JsonConvert.DeserializeObject<Filters>(filters);
ObjectQuery<User> filteredQuery =
f == null ? context.Users : f.FilterObjectSet(context.Users);
filteredQuery.MergeOption = MergeOption.NoTracking; // we don't want to update the data
var totalRecords = filteredQuery.Count();
var pagedQuery =
filteredQuery.Skip(
"it." + (String.IsNullOrEmpty(sortColumnName) ? "Id" : sortColumnName) + " " + sortOrder,
"@skip",
new ObjectParameter("skip", (iPage - 1) * nRows))
.Top("@limit", new ObjectParameter("limit", nRows));
// to be able to use ToString() below which is NOT exist in the LINQ to Entity
// we should include in queryDetails only the properies which we will use below
var queryDetails = (from item in pagedQuery
select new {
item.Id, item.FirstName, item.LastName, item.Birthday
}).ToArray();
return new {
total = (totalRecords + nRows - 1) / nRows,
page = iPage,
records = totalRecords,
rows = (from item in queryDetails
select new[] {
// In the demo we send Id as the 4-th element of row array.
// The value will be not displayed in the grid, but used as rowid
// (the id attribute of the <tr> in the <table>)
item.FirstName,
item.LastName,
item.Birthday == null? String.Empty : ((DateTime)item.Birthday).ToString("yyyy-MM-dd"),
item.Id.ToString (CultureInfo.InvariantCulture)
}).ToArray()
};
}
}
}
where the class Filters
using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Text;
namespace jqGridASHX
{
public class Filters
{
// ReSharper disable InconsistentNaming
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; }
// ReSharper restore InconsistentNaming
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 (var rule in rules)
{
var 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);
ObjectParameter param;
switch (propertyInfo.PropertyType.FullName)
{
case "System.Int32": // int
param = new ObjectParameter("p" + iParam, Int32.Parse(rule.data));
break;
case "System.Int64": // bigint
param = new ObjectParameter("p" + iParam, Int64.Parse(rule.data));
break;
case "System.Int16": // smallint
param = new ObjectParameter("p" + iParam, Int16.Parse(rule.data));
break;
case "System.SByte": // tinyint
param = new ObjectParameter("p" + iParam, SByte.Parse(rule.data));
break;
case "System.Single": // Edm.Single, in SQL: float
param = new ObjectParameter("p" + iParam, Single.Parse(rule.data));
break;
case "System.Double": // float(53), double precision
param = new ObjectParameter("p" + iParam, Double.Parse(rule.data));
break;
case "System.Boolean": // Edm.Boolean, in SQL: bit
param = new ObjectParameter("p" + iParam,
String.Compare(rule.data, "1", StringComparison.Ordinal) == 0 ||
String.Compare(rule.data, "yes", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(rule.data, "true", StringComparison.OrdinalIgnoreCase) == 0);
break;
default:
// TODO: Extend to other data types
// binary, date, datetimeoffset,
// decimal, numeric,
// money, smallmoney
// and so on.
// Below in the example for DateTime and the nullable DateTime
if (String.Compare(propertyInfo.PropertyType.FullName, typeof(DateTime?).FullName, StringComparison.Ordinal) == 0 ||
String.Compare(propertyInfo.PropertyType.FullName, typeof(DateTime).FullName, StringComparison.Ordinal) == 0)
{
// we use below en-US locale directly
param = new ObjectParameter("p" + iParam, DateTime.Parse(rule.data, new CultureInfo("en-US"), DateTimeStyles.None));
}
else {
param = new ObjectParameter("p" + iParam, rule.data);
}
break;
}
objParams.Add(param);
}
var filteredQuery = inputQuery.Where(sb.ToString());
foreach (var objParam in objParams)
filteredQuery.Parameters.Add(objParam);
return filteredQuery;
}
}
}
The code from Global.asax.cs
is
using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Web;
using Newtonsoft.Json;
namespace jqGridASHX
{
internal class ExceptionInformation
{
public string Message { get; set; }
public string Source { get; set; }
public string StackTrace { get; set; }
public string ErrorCode { get; set; }
}
public class Global : HttpApplication
{
protected void Application_Error(object sender, EventArgs e)
{
if (Request.ContentType.Contains("application/json"))
{
Response.Clear();
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
Response.Cache.SetMaxAge(new TimeSpan(0));
Response.ContentType = "application/json";
var exInfo = new List<ExceptionInformation>();
for (var ex = Server.GetLastError(); ex != null; ex = ex.InnerException) {
PropertyInfo propertyInfo = ex.GetType().GetProperty ("HResult");
exInfo.Add (new ExceptionInformation {
Message = ex.Message,
Source = ex.Source,
StackTrace = ex.StackTrace,
ErrorCode = propertyInfo != null && propertyInfo.GetValue (ex, null) is int
? "0x" + ((int)propertyInfo.GetValue (ex, null)).ToString("X")
: String.Empty
});
}
Response.Write(JsonConvert.SerializeObject(exInfo));
Context.Server.ClearError();
}
}
}
}
On the client side I used pure HTML page to show mostly clear that you can include the solution in any your project inclusive Web Form project. The page has the following code
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>https://mcmap.net/q/430357/-how-to-implement-multiple-searching-in-jqgrid/315935</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="Content/themes/redmond/jquery-ui.css" />
<link rel="stylesheet" href="Scripts/jqGrid/4.3.3/ui.jqgrid.css" />
<link rel="stylesheet" href="Content/Site.css" />
<script src="Scripts/jquery-1.7.2.min.js" type="text/javascript"></script>
<script src="Scripts/jquery-ui-1.8.20.min.js" type="text/javascript"></script>
<script src="Scripts/jqGrid/4.3.3/i18n/grid.locale-en.js" type="text/javascript"></script>
<script type="text/javascript">
$.jgrid.no_legacy_api = true;
$.jgrid.useJSON = true;
</script>
<script src="Scripts/jqGrid/4.3.3/jquery.jqGrid.min.js" type="text/javascript"></script>
<script src="Scripts/json2.min.js" type="text/javascript"></script>
<script src="Scripts/Common.js" type="text/javascript"></script>
<script src="Scripts/MyPage.js" type="text/javascript"></script>
</head>
<body>
<table id="list"><tr><td></td></tr></table>
<div id="pager"></div>
</body>
</html>
where MyPage.js
is
/// <reference path="jquery-1.7.2.js" />
/// <reference path="jqGrid/4.3.3/i18n/grid.locale-en.js" />
/// <reference path="jqGrid/4.3.3/jquery.jqGrid.src.js" />
/// <reference path="Common.js" />
$(function () {
"use strict";
$("#list").jqGrid({
url: "jqGridData.ashx",
colNames: ["First Name", "Last Name", "Birthday"],
colModel: [
{ name: "FirstName", width: 200 },
{ name: "LastName", width: 180 },
{ name: "Birthday", width: 100, formatter: "date", align: "center",
searchoptions: { sopt: ["eq", "ne", "lt", "le", "gt", "ge"], dataInit: function (elem) {
$(elem).datepicker({
dateFormat: "m/d/yy",
minDate: "1/1/1753",
defaultDate: "4/30/1777",
autoSize: true,
changeYear: true,
changeMonth: true,
showButtonPanel: true,
showWeek: true
});
}}
}
],
jsonReader: {
cell: "",
// The Id value will be sent as the 4-th element of row array.
// The value will be not displayed in the grid, but used as rowid
// (the id attribute of the <tr> in the <table>)
id: "3"
},
rowNum: 10,
rowList: [10, 20, 30],
pager: "#pager",
rownumbers: true,
viewrecords: true,
sortname: "Birthday",
sortorder: "desc",
caption: "Famous Mathematicians"
}).jqGrid("navGrid", "#pager", { edit: false, add: false, del: false })
.jqGrid('filterToolbar', { stringResult: true, searchOnEnter: true, defaultSearch: "cn" });
});
and Common.js
:
/// <reference path="jquery-1.7.2.js" />
/// <reference path="jqGrid/4.3.3/i18n/grid.locale-en.js" />
/// <reference path="jqGrid/4.3.3/jquery.jqGrid.src.js" />
$.extend($.jgrid.defaults, {
height: "100%",
altRows: true,
altclass: "myAltRowClass",
shrinkToFit: false,
gridview: true,
rownumbers: true,
viewrecords: true,
datatype: "json",
sortable: true,
scrollrows: true,
headertitles: true,
loadui: "block",
viewsortcols: [false, "vertical", true],
prmNames: { nd: null, page: "pageIndex", rows: "rowsPerPage", sort: "sortByColumn", order: "sortOrder", search: "isSearching" },
ajaxGridOptions: { contentType: "application/json" },
ajaxRowOptions: { contentType: "application/json", type: "PUT", async: true },
ajaxSelectOptions: { contentType: "application/json", dataType: "JSON" },
serializeRowData: function (data) {
var propertyName, propertyValue, dataToSend = {};
for (propertyName in data) {
if (data.hasOwnProperty(propertyName)) {
propertyValue = data[propertyName];
if ($.isFunction(propertyValue)) {
dataToSend[propertyName] = propertyValue();
} else {
dataToSend[propertyName] = propertyValue;
}
}
}
return JSON.stringify(dataToSend);
},
resizeStop: function () {
var $grid = $(this.bDiv).find('>:first-child>.ui-jqgrid-btable:last-child'),
shrinkToFit = $grid.jqGrid('getGridParam', 'shrinkToFit'),
saveState = $grid.jqGrid('getGridParam', 'saveState');
$grid.jqGrid('setGridWidth', this.newWidth, shrinkToFit);
if ($.isFunction(saveState)) {
saveState.call($grid[0]);
}
},
gridComplete: function () {
$("#" + this.id + "_err").remove();
},
loadError: function (xhr) {
var response = xhr.responseText, errorDetail, errorHtml, i, l, errorDescription;
if (response.charAt(0) === '[' && response.charAt(response.length - 1) === ']') {
errorDetail = $.parseJSON(xhr.responseText);
var errorText = "";
for (i = 0, l = errorDetail.length; i < l; i++) {
if (errorText.length !== 0) {
errorText += "<hr/>";
}
errorDescription = errorDetail[i];
errorText += "<strong>" + errorDescription.Source + "</strong>";
if (errorDescription.ErrorCode) {
errorText += " (ErrorCode: " + errorDescription.ErrorCode + ")";
}
errorText += ": " + errorDescription.Message;
}
errorHtml = '<div id="errdiv" class="ui-state-error ui-corner-all" style="padding-left: 10px; padding-right: 10px; max-width:' +
($(this).closest(".ui-jqgrid").width() - 20) +
'px;"><p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em;"></span><span>' +
errorText + '</span></p></div>';
$("#" + this.id + "_err").remove();
$(this).closest(".ui-jqgrid").before(errorHtml);
}
}
});
$.extend($.jgrid.search, {
multipleSearch: true,
recreateFilter: true,
closeOnEscape: true,
searchOnEnter: true,
overlay: 0
});
UPDATED: I made some small improvements: included json2.js (see here) to support JSON.stringify
in old web browsers, fixed format of date in the jQuery UI Datepicker and included support for DateType
and DateType?
(System.Nullable<DateType>
) in the class Filters
. So you can download the current version of the project from the same location.
SqlCommand
andSqlDataReader
butommand.CommandText
which you currently use don't uses any parameters (@PageIndex
,@NumberOfRows
...). The example which you used as the template was oriented on the STORED PROCEDURE which uses the parameters and return@TotalRecords
parameter additionally to the rows of data. Can you use Entity Framework instead? In the case you can get the code ofDynamicGridData
from the answer and can replaceGetUsers
andBuildJQGridResults
with the code. – Hardshell