-
-
Save DalSoft/1588818 to your computer and use it in GitHub Desktop.
//Example of a model that won't work with the current JsonValueProviderFactory but will work with JsonDotNetValueProviderFactory | |
public class CmsViewModel | |
{ | |
public bool IsVisible { get; set; } | |
public string Content { get; set; } | |
public DateTime Modified { get; set; } | |
public DateTime Created { get; set; } | |
//This property will not work with the current JsonValueProviderFactory | |
public dynamic UserDefined { get; set; } | |
} |
using System.Linq; | |
using System.Web.Mvc; | |
using System.Web.Routing; | |
namespace JsonDotNetValueProviderFactoryTestHarness | |
{ | |
// Note: For instructions on enabling IIS6 or IIS7 classic mode, | |
// visit http://go.microsoft.com/?LinkId=9394801 | |
public class MvcApplication : System.Web.HttpApplication | |
{ | |
public static void RegisterGlobalFilters(GlobalFilterCollection filters) | |
{ | |
filters.Add(new HandleErrorAttribute()); | |
} | |
public static void RegisterRoutes(RouteCollection routes) | |
{ | |
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); | |
routes.MapRoute( | |
"Default", // Route name | |
"{controller}/{action}/{id}", // URL with parameters | |
new { controller = "TestJsonValueProviderFactory", action = "Index", id = UrlParameter.Optional } // Parameter defaults | |
); | |
} | |
protected void Application_Start() | |
{ | |
AreaRegistration.RegisterAllAreas(); | |
RegisterGlobalFilters(GlobalFilters.Filters); | |
RegisterRoutes(RouteTable.Routes); | |
//Remove and JsonValueProviderFactory and add JsonDotNetValueProviderFactory | |
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault()); | |
ValueProviderFactories.Factories.Add(new JsonDotNetValueProviderFactory()); | |
} | |
} | |
} |
using System.Dynamic; | |
using System.Globalization; | |
using System.IO; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Converters; | |
namespace System.Web.Mvc | |
{ | |
public sealed class JsonDotNetValueProviderFactory : ValueProviderFactory | |
{ | |
public override IValueProvider GetValueProvider(ControllerContext controllerContext) | |
{ | |
if (controllerContext == null) | |
throw new ArgumentNullException("controllerContext"); | |
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) | |
return null; | |
var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream); | |
var bodyText = reader.ReadToEnd(); | |
return String.IsNullOrEmpty(bodyText) ? null : new DictionaryValueProvider<object>(JsonConvert.DeserializeObject<ExpandoObject>(bodyText, new ExpandoObjectConverter()) , CultureInfo.CurrentCulture); | |
} | |
} | |
} |
@Umar-Mukhtar The problem is that MVC has updated implementation of the original JsonValueProviderFactory. So now the result of GetValueProvider() is expected in a different format. This may be used as an example code for overriding of default JSON parsing by JSON.NET one with the recent version of MVC:
public sealed class JsonDotNetValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
var jsonData = GetDeserializedObject(controllerContext);
if (jsonData == null)
{
return null;
}
var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
AddToBackingStore(backingStore, String.Empty, jsonData);
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}
private static object GetDeserializedObject(ControllerContext controllerContext)
{
if (!controllerContext.HttpContext.Request.ContentType.StartsWith(
"application/json",
StringComparison.OrdinalIgnoreCase))
{
// not JSON request
return null;
}
var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
var bodyText = reader.ReadToEnd();
if (String.IsNullOrEmpty(bodyText))
{
// no JSON data
return null;
}
var jsonData = JsonConvert.DeserializeObject<ExpandoObject>(
bodyText, new ExpandoObjectConverter());
return jsonData;
}
private static void AddToBackingStore(IDictionary<string, object> backingStore, string prefix, object value)
{
var d = value as IDictionary<string, object>;
if (d != null)
{
foreach (var entry in d)
{
AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
}
return;
}
var l = value as IList;
if (l != null)
{
for (var i = 0; i < l.Count; i++)
{
AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
}
return;
}
// primitive
backingStore.Add(prefix, value);
}
private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}
private static string MakePropertyKey(string prefix, string propertyName)
{
return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
}
}
Also watch out the code of registering a new factory. It's wrong. It adds our custom JsonDotNetValueProviderFactory factory in the end of the ValueProviderFactories.Factories list. So it gets the lowest priority during model binding and this may break the values of deserialized objects if other factories have the parameters with the same name. The recommended registering code is:
public static void RegisterFactory()
{
var defaultJsonFactory = ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault();
var index = ValueProviderFactories.Factories.IndexOf(defaultJsonFactory);
ValueProviderFactories.Factories.Remove(defaultJsonFactory);
ValueProviderFactories.Factories.Insert(index, new JsonDotNetValueProviderFactory());
}
The idea is to insert our instance at the same index where default JsonValueProviderFactory was.
@ievgen, I was having trouble with the original version of the JsonDotNetValueProvider not wanting to cooperate with nested classes, but that reworked version seems to work for me, except for the case of a json request containing a list at the base level. I came upon a solution, and it is, thankfully, very simple. Just check whether the body text begins with "[" and ends with "]", then deserialize to an IList of ExpandoObject.
Here's my reworked GetDeserializedObject method:
private static object GetDeserializedObject(ControllerContext controllerContext) {
if(!controllerContext.HttpContext.Request.IsAjaxRequest()) return null;
StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
string bodyText = reader.ReadToEnd();
if(string.IsNullOrEmpty(bodyText)) {
// no JSON data
return null;
}
object retVal;
if(bodyText.StartsWith("[") && bodyText.EndsWith("]")) {
retVal = JsonConvert.DeserializeObject<IList<ExpandoObject>>(bodyText, new ExpandoObjectConverter());
} else {
retVal = JsonConvert.DeserializeObject<ExpandoObject>(bodyText, new ExpandoObjectConverter());
}
return retVal;
}
Edit
I noted that my previous code would bork if the base level array contained primitive types, instead of objects, because they could not be cast to ExpandoObject. Below is the updated method that will take this into account (as far as I've been able to tell).
private static object GetDeserializedObject(ControllerContext controllerContext) {
if(!controllerContext.HttpContext.Request.IsAjaxRequest()) return null;
StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
string bodyText = reader.ReadToEnd().Trim();
if(string.IsNullOrEmpty(bodyText)) {
// no JSON data
return null;
}
object retVal;
if(bodyText.StartsWith("[") && bodyText.EndsWith("]")) {
//base element is an array
IList<object> obj = JsonConvert.DeserializeObject<IList<object>>(bodyText, new ExpandoObjectConverter());
//the above conversion will leave nested objects as JContainers, so we need to locate them
//and convert them to ExpandoObject instances, but leave everything else alone
for(int c = 0; c < obj.Count; c++) {
if(obj[c] is JContainer) {
//convert the JContainer to an expando object
obj[c] = ((JContainer)obj[c]).ToObject<ExpandoObject>();
}
}
retVal = obj;
} else if(bodyText.StartsWith("{") && bodyText.EndsWith("}")) {
//base element is an object
retVal = JsonConvert.DeserializeObject<ExpandoObject>(bodyText, new ExpandoObjectConverter());
} else {
//base element is a primitive type... technically not correct, but let's not ignore it
retVal = JsonConvert.DeserializeObject<object>(bodyText);
}
return retVal;
}
Edit 2
In thinking about it, this could also probably be accomplished by altering the AddToBackingStore method to look for JContainer objects and handling them accordingly...
There's a better solution available in the link. It does not use ReadToEnd, thus saving memory. Follow AdaskoTheBeAsT's comments and make the changes to JsonSerializer.CreateDefault too.
https://json.codeplex.com/discussions/347099
I have used this but my object "filterList" always turns out to be 0 and when i comment in global.asax works fine but issue throws error: unable to de serialize large JSON object
example:
public JsonResult getSection(List<VW_GETATTRIBUTES> filterList)
{
I have a large list being posted from client but its not that large that i get this error.
Please help asap!