How to Use JSON.Net for JSON Modelbinding in an MVC5 Project

How to use Json.NET for JSON modelbinding in an MVC5 project?

I've finally found an answer. Basically I don't need the MediaTypeFormatter stuff, that's not designed to be used in MVC environment, but in ASP.NET Web APIs, that's why I do not see those references and namespaces (by the way, those are included in the Microsoft.AspNet.WeApi NuGet package).

The solution is to use a custom value provider factory. Here is the code required.

    public class JsonNetValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
// first make sure we have a valid context
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");

// now make sure we are dealing with a json request
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;

// get a generic stream reader (get reader for the http stream)
var streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
// convert stream reader to a JSON Text Reader
var JSONReader = new JsonTextReader(streamReader);
// tell JSON to read
if (!JSONReader.Read())
return null;

// make a new Json serializer
var JSONSerializer = new JsonSerializer();
// add the dyamic object converter to our serializer
JSONSerializer.Converters.Add(new ExpandoObjectConverter());

// use JSON.NET to deserialize object to a dynamic (expando) object
Object JSONObject;
// if we start with a "[", treat this as an array
if (JSONReader.TokenType == JsonToken.StartArray)
JSONObject = JSONSerializer.Deserialize<List<ExpandoObject>>(JSONReader);
else
JSONObject = JSONSerializer.Deserialize<ExpandoObject>(JSONReader);

// create a backing store to hold all properties for this deserialization
var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
// add all properties to this backing store
AddToBackingStore(backingStore, String.Empty, JSONObject);
// return the object in a dictionary value provider so the MVC understands it
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}

private static void AddToBackingStore(Dictionary<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[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;
}
}

And you can use it like this in your Application_Start method:

// remove default implementation    
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault());
// add our custom one
ValueProviderFactories.Factories.Add(new JsonNetValueProviderFactory());

Here is the post which pointed me to the right direction, and also this one gave a good explanation on value providers and modelbinders.

How do I sub in JSON.NET as model binder for ASP.NET MVC controllers?

I have done this, and also heavily customized the serialization that Json.NET is doing, by:

Replace the default formatter in global.asax.cs, Application_Start:

GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
GlobalConfiguration.Configuration.Formatters.Add(new CustomJsonMediaTypeFormatter());

And my CustomJsonMediaTypeFormatter is:

public static class CustomJsonSettings
{
private static JsonSerializerSettings _settings;

public static JsonSerializerSettings Instance
{
get
{
if (_settings == null)
{
var settings = new JsonSerializerSettings();

// Must convert times coming from the client (always in UTC) to local - need both these parts:
settings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = System.Globalization.DateTimeStyles.AssumeUniversal }); // Critical part 1
settings.DateTimeZoneHandling = DateTimeZoneHandling.Local; // Critical part 2

// Skip circular references
settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

// Handle special cases in json (self-referencing loops, etc)
settings.ContractResolver = new CustomJsonResolver();

_settings = settings;
}

return _settings;
}
}
}

public class CustomJsonMediaTypeFormatter : MediaTypeFormatter
{
public JsonSerializerSettings _jsonSerializerSettings;

public CustomJsonMediaTypeFormatter()
{
_jsonSerializerSettings = CustomJsonSettings.Instance;

// Fill out the mediatype and encoding we support
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
SupportedEncodings.Add(new UTF8Encoding(false, true));
}

public override bool CanReadType(Type type)
{
return true;
}

public override bool CanWriteType(Type type)
{
return true;
}

public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
{
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

// Create task reading the content
return Task.Factory.StartNew(() =>
{
using (StreamReader streamReader = new StreamReader(stream, SupportedEncodings.First()))
{
using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
{
return serializer.Deserialize(jsonTextReader, type);
}
}
});
}

public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
{
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

// Create task writing the serialized content
return Task.Factory.StartNew(() =>
{
using (StreamWriter streamWriter = new StreamWriter(stream, SupportedEncodings.First()))
{
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(streamWriter))
{
serializer.Serialize(jsonTextWriter, value);
}
}
});
}
}

And finally, the CustomJsonResolver:

public class CustomJsonResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, Newtonsoft.Json.MemberSerialization memberSerialization)
{
var list = base.CreateProperties(type, memberSerialization);

// Custom stuff for my app
if (type == typeof(Foo))
{
RemoveProperty(list, "Bar");
RemoveProperty(list, "Bar2");
}

return list;
}

private void RemoveProperty(IList<JsonProperty> list, string propertyName)
{
var rmc = list.FirstOrDefault(x => x.PropertyName == propertyName);

if (rmc != null)
{
list.Remove(rmc);
}
}
}

ASP MVC 5 How to read object from request with the help of JSON NET

correct ajax call

$.ajax({ 
type: "POST",
url: "/Site/Update",
dataType: "json",
data: {JSON.stringify(filtersData) }, //without name because I'll use custom ModelBinder
contentType: "application/json; charset=utf-8",
traditional: true,
success: function (data) {
alert("ok");
},
error:function (xhr, ajaxOptions, thrownError) {
alert(xhr.status);
alert(thrownError);
}
});

Bind JSON to JToken in MVC Controller

In this particular case, changing the default serializer for incoming JSON by writing a custom ValueProviderFactory did not work. This appears to be because JToken is an abstract class and the default ModelBinder can't create a model instance where abstract classes are involved.

The solution was to create a custom ModelBinder for the Action:

public class JsonNetModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (!IsJSONRequest(controllerContext))
{
return base.BindModel(controllerContext, bindingContext);
}

var request = controllerContext.HttpContext.Request;
request.InputStream.Seek(0, SeekOrigin.Begin);
var jsonStringData = new StreamReader(request.InputStream).ReadToEnd();

return JsonConvert.DeserializeObject(jsonStringData, bindingContext.ModelType);
}
private static bool IsJSONRequest(ControllerContext controllerContext)
{
var contentType = controllerContext.HttpContext.Request.ContentType;
return contentType.Contains("application/json");
}
}

And use the custom ModelBinder on the Action as follows:

public class TestController : Controller
{
[HttpPost]
public ActionResult TestAction([ModelBinder(typeof(JsonNetModelBinder))] JToken json)
{
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
}

ASP.NET MVC 5 - Model Binding Not Working

The problem has to-do with AJAX and ASP.NET MVC. MVC doesn't like any serialization from AJAX. When you pass AJAX an object, it manually serializes it and MVC expects to deserialize it in the manner AJAX serializes it. So any manual serialization will break this process. In your method above you are ending up with an encoded string. However if you change your AJAX call to:

let jsonData = "[{\"CarrierStateMapGuid\":\"de4abaa8-42d2-4e00-657a08d5577ac94a\",\"QuestionTag\":\"CoQstPAVT500006\",\"MemberOf\":\"Quote\",\"Condition\":\"0\",\"QuestionType\":\"List\",\"TrueAnswer\":\"NoDiscount\",\"TrueExplanation\":\"No Discount\",\"FalseAnswer\":null,\"FalseExplanation\":null,\"DeleteRequest\":false}]";
$.ajax({
url: "/api/CarrierQuestionMappingApi/UpdateQuestionMaps",
type: "POST",
contentType: "application/json; charset=utf-8",
data: {
Updates: jsonData
}
});

The data will be sent as form data and properly serialize on the controller.

Model binding and inheritance ASP MVC 5

JSON NET Custom deserializer not work at all

ASP MVC 5 How to read object from request with the help of JSON NET

look at answers in two links above, the correct ajax call is described

And the server code below

mvc action

public string Update(Site site)
{
TextBlock block = site.Pages[0].Rows[0].BuildBlocks[0] as TextBlock;
siteRepository.Add(site);
return "Success";
}

AbstractJsonCreationConverter I've created it in Infrastructure folder

public abstract class AbstractJsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jsonObject);

public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}

public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var target = Create(objectType, jsonObject);
serializer.Populate(jsonObject.CreateReader(), target);
return target;
}

public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

And in the same folder concrete class

public class JsonBuildBlockConverter : AbstractJsonCreationConverter<AbstractBuildBlock>
{
protected override AbstractBuildBlock Create(Type objectType, JObject jsonObject)
{
var type = jsonObject["contentType"].ToString();
switch(type)
{
case "text":
return new TextBlock();
default:
return null;
}
}
}

and one more class in Infrastructure

internal class SiteModelBinder : System.Web.Mvc.IModelBinder
{
public object BindModel(ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext)
{
// use Json.NET to deserialize the incoming Position
controllerContext.HttpContext.Request.InputStream.Position = 0; // see: https://stackoverflow.com/a/3468653/331281
Stream stream = controllerContext.RequestContext.HttpContext.Request.InputStream;
var readStream = new StreamReader(stream, Encoding.UTF8);
string json = readStream.ReadToEnd();
return JsonConvert.DeserializeObject<Site>(json, new JsonBuildBlockConverter());
}
}

The last class is ModelBinder that will be called to parse variables of type Site, to make it work you need to register it in Global.asax.cs in ApplicationStart()

 protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ModelBinders.Binders.Add(typeof(Site), new SiteModelBinder()); //RegisterModelBinder for Site
}

This is all.

Asp.net MVC 5 Modelbinding multiple JSON objects (knockoutjs)

Try to convert observables to JSON first and then convert the whole object to json string:

data: JSON.stringify({
timeRanges: ko.toJS(self.timeRanges()),
geoRequirements: ko.toJS(self.geoRequirements())
}),


Related Topics



Leave a reply



Submit