Following code is working locally very fine.
The users receive All pushes (notifications) when I send them via local PC.
But I need use proxy on the server and this code fires the error: "Failed to establish a connection: Network is unreachable".
Plz help me setup the proxy for this code.
using System;
using MediatR;
using System.IO;
using FirebaseAdmin;
using System.Threading;
using System.Threading.Tasks;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
namespace JMGlobal.Handlers
{
public class PushHandler : IRequestHandler<Request_Push, Response_Push>
{
public async Task<Response_Push> Handle(Request_Push request, CancellationToken cancellationToken)
{
try
{
var defaultApp = FirebaseApp.DefaultInstance;
if (defaultApp == null)
{
var keyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "jKey.json");
defaultApp = FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromFile(keyPath),
// HttpClientFactory --- ????
});
}
var message = new Message()
{
Token = request.FirebaseToken,
Apns = new ApnsConfig()
{
Aps = new FirebaseAdmin.Messaging.Aps()
{
Alert = new ApsAlert()
{
Title = "push",
Body = request.PushMessage
}
}
},
Notification = new Notification
{
Title = "System Notification",
Body = $"Message: {request.PushMessage}"
}
};
var messaging = FirebaseMessaging.DefaultInstance;
var result = await messaging.SendAsync(message); // here fires the error: "Failed to establish a connection: Network is unreachable"
return new Response_Push()
{ .... };
}
catch (Exception ex)
{..... }
}
}
}
Working version below.
using System;
using MediatR;
using System.IO;
using FirebaseAdmin;
using System.Threading;
using System.Diagnostics;
using System.Threading.Tasks;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Google.Apis.Http;
using System.Net.Http;
using System.Net;
using Microsoft.Extensions.Options;
namespace JMGlobal.Handlers
{
public class ProxyHttpClientFactory2 : Google.Apis.Http.HttpClientFactory
{
private readonly string proxyAddress;
private readonly bool proxyUseProxy;
private readonly bool proxyBypassOnLocal;
public ProxyHttpClientFactory2(string proxyAddress, bool proxyUseProxy, bool proxyBypassOnLocal)
{
this.proxyAddress = proxyAddress;
this.proxyUseProxy = proxyUseProxy;
this.proxyBypassOnLocal = proxyBypassOnLocal;
}
protected override HttpMessageHandler CreateHandler(CreateHttpClientArgs args)
{
var proxy = new WebProxy(Address: proxyAddress, BypassOnLocal: proxyBypassOnLocal, BypassList: null, Credentials: null);
var webRequestHandler = new HttpClientHandler()
{
Proxy = proxy,
UseProxy = proxyUseProxy,
UseCookies = false
};
return webRequestHandler;
}
}
public class PushHandler : IRequestHandler<Request_PushFromBackend, Response_PushFromBackend>
{
private readonly IDBCommander _commander;
private readonly ILogger<PushHandler> _logger;
public PushFromBackendHandler(ILogger<PushHandler> logger, IDBCommander commander)
{
_logger = logger;
_commander = commander;
}
public async Task<Response_PushFromBackend> Handle(Request_PushFromBackend request, CancellationToken cancellationToken)
{
var sw = Stopwatch.StartNew();
bool isProxyUsing = false;
try
{
var resultFCMcreds = await _commander.Get_FCMcredentials(); // settings from DB
var resultProxy = await _commander.Get_ProxySettings(); // settings from DB
var defaultApp = FirebaseApp.DefaultInstance;
if (defaultApp == null)
{
var serviceAccountEmail = resultFCMcreds?.ServiceAccountEmail;
var PrivateKey = resultFCMcreds?.PrivateKey;
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
ProjectId = resultFCMcreds?.ProjectId,
HttpClientFactory = new ProxyHttpClientFactory2(resultProxy?.Address, (bool)resultProxy?.UseProxy, (bool)resultProxy?.BypassOnLocal),
}.FromPrivateKey(PrivateKey));
defaultApp = FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromServiceAccountCredential(credential),
HttpClientFactory = new ProxyHttpClientFactory2(resultProxy?.Address, (bool)resultProxy?.UseProxy, (bool)resultProxy?.BypassOnLocal)
});
}
FirebaseAdmin.Messaging.Aps aps_data = new Aps();
if (request.PushMode == 1)
{
aps_data.ContentAvailable = true;
}
var message = new Message()
{
//Topic = "news",
Token = request.FirebaseToken,
Apns = new ApnsConfig()
{
Aps = aps_data
},
Data = new Dictionary<string, string>()
{
["Changed"] = "true",
},
Notification = (request.PushMode == 1) ? null : new Notification
{
Title = $"System Notification {((request.PushMode == 1) ? " (SILENT)" : " (NORMAL)")}",
Body = $"Message: {request.PushMessage}"
}
};
var messaging = FirebaseMessaging.DefaultInstance;
var result = await messaging.SendAsync(message);
_logger.LogInformation($"result: {result}");
return new Response_PushFromBackend()
{
....
};
}
catch (Exception ex)
{
return new Response_PushFromBackend()
{
Error = ex.Message
};
}
}
}
}
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 3 years ago.
Improve this question
When I do a POST I always get the 'Requested page not found. [404]', I cant figure out whats´wrong please advice
I have this web API with ASP.NET Core MVC project that works great and now I modify it, remove database and add custom ControllerBase instead.
But I get the [404] error somethings missing ..
here is my code
JavaScript
const uri = "api/book";
let todos = null;
function getCount(data) {
const el = $("#counter");
let name = "to-do";
if (data) {
if (data > 1) {
name = "to-dos";
}
el.text(data + " " + name);
} else {
el.text("No " + name);
}
}
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function (data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
$.each(data, function (key, item) {
const tr = $("<tr></tr>")
.append(
$("<td></td>").append(
$("<input/>", {
type: "checkbox",
disabled: true,
checked: item.isComplete
})
)
)
.append($("<td></td>").text(item.name))
.append(
$("<td></td>").append(
$("<button>Edit</button>").on("click", function () {
editItem(item.id);
})
)
)
.append(
$("<td></td>").append(
$("<button>Delete</button>").on("click", function () {
deleteItem(item.id);
})
)
);
tr.appendTo(tBody);
});
todos = data;
}
});
}
function addItem() {
const item = {
name: $("#add-name").val(),
isComplete: false
};
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
///alert("Something went wrong!");
var msg = '';
if (jqXHR.status === 0) {
msg = 'Not connect.\n Verify Network.';
} else if (jqXHR.status == 404) {
msg = 'Requested page not found. [404]';
} else if (jqXHR.status == 500) {
msg = 'Internal Server Error [500].';
} else if (exception === 'parsererror') {
msg = 'Requested JSON parse failed.';
} else if (exception === 'timeout') {
msg = 'Time out error.';
} else if (exception === 'abort') {
msg = 'Ajax request aborted.';
} else {
msg = 'Uncaught Error.\n' + jqXHR.responseText;
}
alert(msg);
///$('#post').html(msg);
},
success: function (result) {
getData();
$("#add-name").val("");
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function (result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function (key, item) {
if (item.id === id) {
$("#edit-name").val(item.name);
$("#edit-id").val(item.id);
$("#edit-isComplete")[0].checked = item.isComplete;
}
});
$("#spoiler").css({ display: "block" });
}
$(".my-form").on("submit", function () {
const item = {
name: $("#edit-name").val(),
isComplete: $("#edit-isComplete").is(":checked"),
id: $("#edit-id").val()
};
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$("#spoiler").css({ display: "none" });
}
My Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Xml.Linq;
using WorkSampleBookSearch.Model;
namespace WorkSampleBookSearch
{
[Route("api/[controller]")]
[ApiController]
public class BooksXmlController : ControllerBase
{
public BooksXmlController()
{
}
/// <summary>
/// Retrieve all items from PhoneBook.
/// </summary>
/// <returns>PhoneBook items List</returns>
// GET: api/Book
[HttpGet]
public IActionResult GetPhoneBookItems()
{
List<PhoneBookItem> PhoneBookItems = new List<PhoneBookItem>();
XDocument doc = XDocument.Load("books.xml");
foreach (XElement element in doc.Descendants("phonebookitems")
.Descendants("phonebookitem"))
{
PhoneBookItem phonebookitem = new PhoneBookItem
{
/// Id
/// Author
/// Title
/// Genre
/// Price
/// Publish_date
/// Description
Id = element.Element("id").Value,
Author = element.Element("author").Value,
Title = element.Element("title").Value,
Genre = element.Element("genre").Value,
Price = element.Element("price").Value,
Publish_date = element.Element("publish_date").Value,
Description = element.Element("description").Value
};
PhoneBookItems.Add(phonebookitem);
PhoneBookItems = PhoneBookItems.OrderBy(p => p.Title).ToList();
}
return Ok(PhoneBookItems);
}
/// <summary>
/// Returns a PhoneBook item matching the given id.
/// </summary>
/// <param name="id">Id of item to be retrieved</param>
/// <returns>PhoneBook item</returns>
// GET: api/Book/5
[HttpGet("{id}")]
public IActionResult GetPhoneBookItem(long id)
{
XDocument doc = XDocument.Load("books.xml");
XElement element = doc.Element("phonebookitems").Elements("phonebookitem").
Elements("id").SingleOrDefault(x => x.Value == id.ToString());
XElement parent = element.Parent;
PhoneBookItem phonebookitem = new PhoneBookItem
{
Id = element.Element("id").Value,
Author = element.Element("author").Value,
Title = element.Element("title").Value,
Genre = element.Element("genre").Value,
Price = element.Element("price").Value,
Publish_date = element.Element("publish_date").Value,
Description = element.Element("description").Value
};
return Ok(phonebookitem);
}
/// <summary>
/// Insert a PhoneBook item.
/// </summary>
/// <returns>Inserts new PhoneBook item in books.xml and saves the file</returns>
//POST: api/Book
[HttpPost]
public void PostPhoneBookItem(PhoneBookItem PhoneBookItem)
{
int maxId;
XDocument doc = XDocument.Load("books.xml");
bool elementExist = doc.Descendants("phonebookitem").Any();
if (elementExist)
{
maxId = doc.Descendants("phonebookitem").Max(x => (int)x.Element("id"));
}
else
{
maxId = 0;
}
/// Id
/// Author
/// Title
/// Genre
/// Price
/// Price
/// Publish_date
/// Description
XElement root = new XElement("phonebookitem");
root.Add(new XElement("id", maxId + 1));
root.Add(new XElement("author", PhoneBookItem.Author));
root.Add(new XElement("title", PhoneBookItem.Title));
root.Add(new XElement("genre", PhoneBookItem.Genre));
root.Add(new XElement("price", PhoneBookItem.Price));
root.Add(new XElement("publish_date", PhoneBookItem.Publish_date));
root.Add(new XElement("description", PhoneBookItem.Description));
doc.Element("phonebookitems").Add(root);
doc.Save("books.xml");
}
/// <summary>
/// Updates a PhoneBook item matching the given id.
/// </summary>
/// <param name="id">Id of item to be retrieved</param>
/// <param name="PhoneBookItem">Retrieved PhoneBook item</param>
/// <returns>Updates PhoneBook item in books.xml and saves the file</returns>
//PUT: api/Book/5
[HttpPut("{id}")]
public void PostPhoneBookItem(long id, PhoneBookItem PhoneBookItem)
{
XDocument doc = XDocument.Load("books.xml");
var items = from item in doc.Descendants("phonebookitem")
where item.Element("id").Value == id.ToString()
select item;
foreach (XElement itemElement in items)
{
/// Id
/// Author
/// Title
/// Genre
/// Price
/// Publish_date
/// Description
itemElement.SetElementValue("author", PhoneBookItem.Author);
itemElement.SetElementValue("title", PhoneBookItem.Title);
itemElement.SetElementValue("genre", PhoneBookItem.Genre);
itemElement.SetElementValue("price", PhoneBookItem.Price);
itemElement.SetElementValue("publish_date", PhoneBookItem.Publish_date);
itemElement.SetElementValue("description", PhoneBookItem.Description);
}
doc.Save("books.xml");
}
/// <summary>
/// Delete a PhoneBook item matching the given id.
/// </summary>
/// <param name="id">Id of item to be retrieved</param>
/// <returns>Deletes item from books.xml and saves the file</returns>
// DELETE: api/Book/5
[HttpDelete("{id}")]
public void DeletePhoneBookItem(long id)
{
XDocument doc = XDocument.Load("books.xml");
var elementToDelete = from item in doc.Descendants("phonebookitem")
where item.Element("id").Value == id.ToString()
select item;
elementToDelete.Remove();
doc.Save("books.xml");
}
}
}
Here is my startup
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
namespace WorkSampleBookSearch
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
Change the URI from
const uri = "api/book";
to
const uri = "api/booksxml";
Explanation:
[Route("api/[controller]")] means that the route will be be api/controllerName and in your case the name of the controller is BooksXml (the Controller suffix is not taken into consideration).
I have a jqGrid which I am using with asp.Net Web Forms , it shows the required information properly from the database , however it shows the search option as well but if I try to search lets say First Name that is Equal to Lijo , it just does not show up that record.The record exists.I know I am missing some stuff required for searching surely , here is the code
<script type="text/javascript">
$(function() {
$("#UsersGrid").jqGrid({
url: 'ModCust.ashx',
datatype: 'json',
height: 250,
width: 800,
colNames: ['Application No', 'First Name', 'Middle Name', 'Last Name'],
colModel: [
{ name: 'cApplicationNo', index: 'cApplicationNo', width: 100, sortable: true},
{ name: 'cFirstName', width: 100, sortable: true},
{ name: 'cMiddleName', width: 100, sortable: true },
{ name: 'cLastName', width: 100, sortable: true },
],
cmTemplate: { title: false},
rowNum: 10,
rowList: [10, 20, 30],
pager: '#UsersGridPager',
sortname: 'cApplicationNo',
viewrecords: true,
sortorder: 'asc',
caption: 'Customer Details'
});
$("#UsersGrid").jqGrid('navGrid', '#UsersGridPager', { edit: false, add: false, del: false });
});
</script>
Here is my ModCust.ashx handler
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.SqlClient;
using System.Web;
using System.Web.Script.Serialization;
namespace CwizBankApp
{
public struct JQGridResults
{
public int page;
public int total;
public int records;
public JQGridRow[] rows;
}
public struct JQGridRow
{
public string id;
public string[] cell;
}
[Serializable]
public class User
{
public string ApplicationNo { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
}
/// <summary>
/// Summary description for $codebehindclassname$
/// </summary>
public class ModCust : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
HttpRequest request = context.Request;
HttpResponse response = context.Response;
string _search = request["_search"];
string numberOfRows = request["rows"];
string pageIndex = request["page"];
string sortColumnName = request["sidx"];
string sortOrderBy = request["sord"];
int totalRecords;
//Collection<User> users = GetDummyUsers(numberOfRows, pageIndex, sortColumnName, sortOrderBy, out totalRecords);
Collection<User> users = GetUsers(numberOfRows, pageIndex, sortColumnName, sortOrderBy, out totalRecords);
string output = BuildJQGridResults(users, Convert.ToInt32(numberOfRows), Convert.ToInt32(pageIndex), Convert.ToInt32(totalRecords));
response.Write(output);
}
private string BuildJQGridResults(Collection<User> users, int numberOfRows, int pageIndex, int totalRecords)
{
JQGridResults result = new JQGridResults();
List<JQGridRow> rows = new List<JQGridRow>();
foreach (User user in users)
{
JQGridRow row = new JQGridRow();
row.id = user.ApplicationNo;
row.cell = new string[4];
row.cell[0] = user.ApplicationNo;
row.cell[1] = user.FirstName;
row.cell[2] = user.MiddleName;
row.cell[3] = user.LastName;
rows.Add(row);
}
result.rows = rows.ToArray();
result.page = pageIndex;
result.total = (totalRecords + numberOfRows - 1) / numberOfRows;
result.records = totalRecords;
return new JavaScriptSerializer().Serialize(result);
}
private Collection<User> GetDummyUsers(string numberOfRows, string pageIndex, string sortColumnName, string sortOrderBy, out int totalRecords)
{
var data = new Collection<User> {
new User(){ FirstName = "Bill", LastName = "Gates", ApplicationNo= "1", MiddleName = "Bill Gates"}
};
totalRecords = data.Count;
return data;
}
private Collection<User> GetUsers(string numberOfRows, string pageIndex, string sortColumnName, string sortOrderBy, out int totalRecords)
{
Collection<User> users = new Collection<User>();
string connectionString = "Server=Server;Database=CwizData;Trusted_Connection=True";
using (SqlConnection connection = new SqlConnection(connectionString))
{
using (SqlCommand command = new SqlCommand())
{
command.Connection = connection;
command.CommandText = "select cApplicationNo,cFirstName,cMiddleName,cLastName from Data_Customer_Log";
command.CommandType = CommandType.Text; // StoredProcedure;
SqlParameter paramPageIndex = new SqlParameter("#PageIndex", SqlDbType.Int);
paramPageIndex.Value = Convert.ToInt32(pageIndex);
command.Parameters.Add(paramPageIndex);
SqlParameter paramColumnName = new SqlParameter("#SortColumnName", SqlDbType.VarChar, 50);
paramColumnName.Value = sortColumnName;
command.Parameters.Add(paramColumnName);
SqlParameter paramSortorderBy = new SqlParameter("#SortOrderBy", SqlDbType.VarChar, 4);
paramSortorderBy.Value = sortOrderBy;
command.Parameters.Add(paramSortorderBy);
SqlParameter paramNumberOfRows = new SqlParameter("#NumberOfRows", SqlDbType.Int);
paramNumberOfRows.Value = Convert.ToInt32(numberOfRows);
command.Parameters.Add(paramNumberOfRows);
SqlParameter paramTotalRecords = new SqlParameter("#TotalRecords", SqlDbType.Int);
totalRecords = 0;
paramTotalRecords.Value = totalRecords;
paramTotalRecords.Direction = ParameterDirection.Output;
command.Parameters.Add(paramTotalRecords);
connection.Open();
using (SqlDataReader dataReader = command.ExecuteReader())
{
User user;
while (dataReader.Read())
{
user = new User();
user.ApplicationNo =Convert.ToString(dataReader["cApplicationNo"]);
user.FirstName = Convert.ToString(dataReader["cFirstName"]);
user.MiddleName = Convert.ToString(dataReader["cMiddleName"]);
user.LastName = Convert.ToString(dataReader["cLastName"]);
users.Add(user);
}
}
//totalRecords =(int)(paramTotalRecords.Value);
// totalRecords = 0;
}
return users;
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
Can someone help me out with this,
Any suggestions are welcome , thanks
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://stackoverflow.com/q/10698254/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.