Newtonsoft JsonConvert DeserializeObject cannot ignore Default Value from entity err? - json.net

Here is the Sample code:
I have a TargetEntity which contains a bool list, by default, it is set to a seven elements list of true value.
What I am trying to do is deserialize from JSON, which contains a list of seven false value.
However, when deserializing it, it seems constructed a list of 14 values, which included the default ones and attached the deserialized from the JSON like this { true, true, true, true, true, true, true, false, false, false, false, false, false, false }
What is wrong here?
using Newtonsoft.Json;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var _entityList = JsonConvert.DeserializeObject<List<TargetEntity>>(
"[{\"BoolsList\": [false,false,false,false,false,false,false]}]",
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
Console.WriteLine("List size: " + _entityList[0].BoolsList.Count);
for(int i = 0; i < _entityList[0].BoolsList.Count; i++)
{
Console.WriteLine($"List element {i}: " + _entityList[0].BoolsList[i]);
}
}
public class TargetEntity
{
public List<bool> BoolsList { get; set; } = new List<bool> { true, true, true, true, true, true, true };
}
}
There is C# fiddle like for this code: https://dotnetfiddle.net/VMIXeg

It is a very interesting behavior, related with the serializer settings and the way the new object is constructed and populated :)
If what you want is to replace the default list and keep on using Newtonsoft.Json, you can replace the settings
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore }
by
new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }
Depending on your .NET version, you may also use System.Text.Json:
using System;
using System.Collections.Generic;
using System.Text.Json;
public class Program
{
public static void Main()
{
var _entityList = JsonSerializer.Deserialize<List<TargetEntity>>("[{\"BoolsList\": [false,false,false,false,false,false,false]}]");
...
}
...
}

Related

How to add Mock db tables in C# test cases

How to create mock db tables for the separate class file in test cases to access the service test case and also I need for that tables between parent and child relation
public static class MockTestData
{
// Test data for the DbSet<User> getter
public static IQueryable<EaepTieriiLangComp> Langcomps
{
get
{ return new List<EaepTieriiLangComp>
{
new EaepTieriiLangComp{EaepAssessmentId=1,LangCompId=1,IsPrimary ="Y",LangId =1,LangReadId=1,LangWrittenId=1,LangSpokenId=1,LangUnderstandId=1 },
new EaepTieriiLangComp{EaepAssessmentId=2,LangCompId=1 ,IsPrimary ="N",LangId =2,LangReadId=2,LangWrittenId=2,LangSpokenId=2,LangUnderstandId=2 }//Lang =obj,LangRead=objRead,LangSpoken =objSpeak,LangWritten=objWrite,LangUnderstand=objUnderstand
}.AsQueryable();
}
}
public static IQueryable<LookupLang> LookupLangs
{
get
{ return new List<LookupLang>
{
new LookupLang{LangId = 1,Description = "lang1",IsActive="Y"},
new LookupLang{LangId = 2,Description = "lang2",IsActive="N"}
}.AsQueryable();
}
}
}`
enter code here`
I tried for the above flow but i didnot get relatons for that tables
If you are using EF Core, you can create inmemory database, add data and make query to it.
Here is example:
First you need install Microsoft.EntityFrameworkCore.InMemory package. After this make options:
_options = new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase(databaseName: "DbTest")
.Options;
using var context = new SomeDbContext(_options);
context.Database.EnsureCreated();
Then add your data:
context.AddRange(
new LookupLang{LangId = 1,Description = "lang1",IsActive="Y"},
new LookupLang{LangId = 2,Description = "lang2",IsActive="N"}
)
And now you can use context for testing purposes
Thank you so much advise to use EF core.InMemory package it is working fine now I followed below code
Inmemory class
using Assessments.TierIIQueryDataModel;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
namespace AssessmentCommandTest.Helpers
{
public class InMemoryDataProviderQueryService : IDisposable
{
private bool disposedValue = false; // To detect redundant calls
public DbQueryContext CreateContextForInMemory()
{
var option = new DbContextOptionsBuilder<DbQueryContext>().UseInMemoryDatabase(databaseName: "Test_QueryDatabase").Options;
var context = new DbQueryContext(option);
if (context != null)
{
//context.Database.EnsureDeleted();
context.Database.EnsureCreated();
}
return context;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
}
and access to DbQueryContext conext file in my code and write mock tables as below
using AssessmentCommandTest.Helpers;
using Assessments.TierIIQueryDataModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AssessmentCommandTest.MockDbTables
{
public class MockQueryDbContext
{
public TierIIQueryContext MockTierIIQueryContexts()
{
//Create object for Inmemory DB provider
var factory = new InMemoryDataProviderQueryService();
//Get the instance of TierIIQueryContext
var context = factory.CreateContextForInMemory();
context.LookupLang.Add(new LookupLang { LangId = 1, Description = "Arabic", IsActive = "Y" });
context.LookupLang.Add(new LookupLang { LangId = 2, Description = "Bangali", IsActive = "Y" });
context.LookupLang.Add(new LookupLang { LangId = 3, Description = "English", IsActive = "Y" });
context.LookupLang.Add(new LookupLang { LangId = 4, Description = "French", IsActive = "Y" });
enter code here
context.SaveChanges();
return context;
}
}
}

Azure Functions: getting connection string from other file than local.settings.json

I have a azure functions .net core 2.2 project.
There is a class:
public static class A
{
[FunctionName("abc")]
public static async Task RunAsync(
[ServiceBusTrigger("topic1", "sub1", Connection = "ServiceBusConnectionString")] string msg,
[Inject] IInsertOrderAzureSqlFunction functionRunner)
{
//...
}
}
which uses ServiceBusTrigger. Connection for ServiceBusTrigger is obtained from local.settings.json file. Is it possible to put connection string in different file i.e. secret.settings.json? How to enforce ServiceBusTrigger to get Connection parameter value from other file than local.settings.json
local.settings.json:
{
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AzureWebJobsDashboard": "UseDevelopmentStorage=true",
"ServiceBusConnectionString": "connectionStringValue1",
"SqlConnection": "connectionStringValue2"
}
}
You can make use of the ConfigurationBuilder to add multiple secrets.settings.json or prod.settings.json etc and load it dynamically. Example code below.
Let's say you have a secrets.settings.json like this
{
"ConnectionStrings": {
"SqlConnectionString": "server=myddatabaseserver;user=tom;password=123;"
},
"MyCustomStringSetting": "Override Some Name",
"MailSettings": {
"PrivateKey": "xYasdf5678asjifSDFGhasn1234sDGFHg"
}
}
Update 1
Make use of Dependency Injection using IWebJobsStartup and you can do it this way.
[assembly: WebJobsStartup(typeof(Startup))]
namespace MyFunctionApp
{
public class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
var config = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddJsonFile("secret.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
var myString = config["MyCustomStringSetting"];
builder.Services.PostConfigure<ServiceBusAttribute(serviceBusOptions =>
{
serviceBusOptions.Connection = myString;
});
}
}
}
The trigger will only fall back to using the options.ConnectionStringvalue if the connection string is not set by the attribute. So in your function definition, make sure to set ConnectionStringSetting to "":

Using RabbitMQ in ASP.net core IHostedService

I am facing a problem that would like help.
I am developing a background process that will be listening to a queue in rabbitmq server.
It is OK if I run it in a .net core console application. However I would like to do it in a more elegant way such as web service (which has given me a lot of trouble where it does not work when installed) or an IIS hosted web application.
I face a problem of Scoped Service when I try to host the service (IHostedService) in .net core web application.
The code below is working fine in a console application. How can make it run as an IHostedService in a .net core web application.
What am I supposed to change.
Your help is appreciated.
CODE:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using PaymentProcessor.Models;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Microsoft.EntityFrameworkCore;
namespace PaymentProcessor
{
public class PaymentProcessingService : HostedService
{
IConfiguration configuration;
private EntitiesContext claimsContext;
private string connectionString;
private string HostName = "";
private string UserName = "";
private string Password = "";
private static int MaxRetries;
private IConnectionFactory factory;
private IConnection connection;
private IModel channel;
public PaymentProcessingService(IConfiguration configuration)
{
this.configuration = configuration;
this.connectionString = configuration.GetConnectionString ("StagingContext");
claimsContext = new EntitiesContext(connectionString);
HostName = this.configuration.GetValue<string>("Settings:HostName");
UserName = this.configuration.GetValue<string>("Settings:UserName");
Password = this.configuration.GetValue<string>("Settings:Password");
MaxRetries = this.configuration.GetValue<string>("Settings:MaxRetries").ConvertTo<int>();
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
connect:
factory = new ConnectionFactory { HostName = HostName, UserName = UserName, Password = Password };
try
{
connection = factory.CreateConnection();
channel = connection.CreateModel();
channel.ExchangeDeclare("payment_rocessing_exchange", "topic");
channel.QueueDeclare("payment_processing_queue", true, false, false, null);
channel.QueueBind("payment_processing_queue", "payment_processing_exchange", "processing");
var queueArgs = new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "payment_processing_exchange" },
{"x-dead-letter-routing-key", "processing_retry"},
{ "x-message-ttl", 10000 }
};
channel.ExchangeDeclare("payment_rocessing_exchange", "topic");
channel.QueueDeclare("payment_processing_retry_queue", true, false, false, queueArgs);
channel.QueueBind("payment_processing_retry_queue", "payment_processing_exchange", "processing_retry", null);
channel.ExchangeDeclare("payment_processing_exchange", "topic");
channel.QueueDeclare("payment_processing_error_queue", true, false, false, null);
channel.QueueBind("payment_processing_error_queue", "payment_processing_exchange", "processing_error", null);
channel.ExchangeDeclare("payment_processing_exchange", "topic");
channel.QueueDeclare("payment_integration_queue", true, false, false, null);
channel.QueueBind("payment_integration_queue", "payment_processing_exchange", "integration", null);
channel.BasicQos(0, 1, false);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var message = ea.Body.DeSerializeText();
try
{
var saveBundle = JObject.Parse(message);
var msg = (dynamic)((dynamic)saveBundle).Message;
string referenceNo = (string)msg.ReferenceNo;
var parameters = new[]
{
new SqlParameter
{
DbType = DbType.String,
ParameterName = "ReferenceNo",
Value =referenceNo
}
};
var result = claimsContext.Database.ExecuteSqlCommand("dbo.PaymentReferencesProcessSingle #ReferenceNo", parameters);
IBasicProperties props = channel.CreateBasicProperties();
props.Persistent = true;
props.ContentType = "text/plain";
props.DeliveryMode = 2;
channel.BasicPublish("payment_processing_exchange", "integration", props, (new MessageEnvelope { RetryCounts = 0, Message = JObject.FromObject(new { ReferenceNo = referenceNo }) }).Serialize());
}
catch (Exception ex)
{
MessageEnvelope envelope = JsonConvert.DeserializeObject<MessageEnvelope>(message);
if (envelope.RetryCounts < MaxRetries)
{
int RetryCounts = envelope.RetryCounts + 1;
MessageEnvelope messageEnvelope = new MessageEnvelope { RetryCounts = RetryCounts, Message = envelope.Message };
var data = messageEnvelope.Serialize();
channel.BasicPublish("payment_processing_exchange", "processing_retry", null, data);
}
else
{
var data = envelope.Serialize();
channel.BasicPublish("payment_processing_exchange", "processing_error", null, data);
}
}
finally
{
channel.BasicAck(ea.DeliveryTag, false);
}
};
channel.BasicConsume(queue: "payment_processing_queue", autoAck: false, consumer: consumer);
}
catch (Exception ex)
{
Thread.Sleep(10000);
goto connect;
}
}
}
}
and then
services.AddScoped<IHostedService, PaymentProcessingService>();
As Dekim mentioned a service should be registered.
Please have a look on an example I created on GitHub.
Program.cs looks like this:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;
namespace Core
{
internal class Program
{
static async Task Main(string[] args)
{
await new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ServiceRabbitMQ>(); // register our service here
})
.RunConsoleAsync();
}
}
}
Because the IHostedService requires the creation of a special scope according to the documentation.
From the Microsoft documentation cited in the above answer :
No scope is created for a hosted service by default.
Consider using : services.AddHostedService<MyHostedService>();

How to implement multiple searching in jqGrid

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.

Building a ViewModel

Greetings,
I have a ViewModel for a ProductCategory.
The ProductCategory has a boolean Active field.
Is is possible to have a single ProductCategoryViewModel and be able to get a collection of all ProductCategories and a collection of ACTIVE ProductCategories?
Or, would I have to create an ActiveProductCategoryViewModel?
I'm using MVVM-Light with RIA in Silverlight...so, I have a ProductCategory service with a GetProductCategories method and a GetActiveProductCategories method. I want to be able to get the ActiveProductCategories to populate a dropdown...but also get ALL the ProductCategories for maintenance and historical purposes etc.
Thanks!
Butcher
I assume you have another ViewModel with a collection of ProductCategoryViewModel objects? If so, I think its fine to have another collection of just the active product categories. I'm not sure you need a separate service method for this, since you can just filter your collection of product categories based on the Active value.
If this view model would be called ProductCategoriesViewModel, it might look like this:
using System.Collections.Generic;
using System.Linq;
using GalaSoft.MvvmLight;
namespace OCEAN.EPP.ViewModel
{
public class ProductCategoriesViewModel : ViewModelBase
{
public ProductCategoriesViewModel()
{
if (IsInDesignMode)
{
ProductCategories = new List<ProductCategoryViewModel>
{
new ProductCategoryViewModel { Active = false },
new ProductCategoryViewModel { Active = false },
new ProductCategoryViewModel { Active = true },
new ProductCategoryViewModel { Active = true },
};
}
else
{
// Code runs "for real": Connect to service, etc...
}
}
public const string ProductCategoriesPropertyName = "ProductCategories";
private List<ProductCategoryViewModel> _productCategories = new List<ProductCategoryViewModel>();
public List<ProductCategoryViewModel> ProductCategories
{
get { return _productCategories; }
set
{
if (_productCategories == value)
return;
_productCategories = value;
FilterActiveProductCategories();
RaisePropertyChanged(ProductCategoriesPropertyName);
}
}
public const string ActiveProductCategoriesPropertyName = "ActiveProductCategories";
private List<ProductCategoryViewModel> _activeProductCategories = new List<ProductCategoryViewModel>();
public List<ProductCategoryViewModel> ActiveProductCategories
{
get { return _activeProductCategories; }
set
{
if (_activeProductCategories == value)
return;
_activeProductCategories = value;
RaisePropertyChanged(ActiveProductCategoriesPropertyName);
}
}
private void FilterActiveProductCategories()
{
ActiveProductCategories = ProductCategories.Where(pc => pc.Active).ToList();
}
}
}

Resources