Getting "The JSON request was too large to be deserialized" - asp.net-mvc

I'm getting this Error:
The JSON request was too large to be deserialized.
Here's a scenario where this occurs. I have a class of country which hold a list of shipping ports of that country
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
public List<Port> Ports { get; set; }
}
I use KnockoutJS on the client side to make a cascading drop downs. So we have an array of two drop downs, where the first one is country, and the second one is ports of that country.
Everything is working fine so far, this my client side script:
var k1 = k1 || {};
$(document).ready(function () {
k1.MarketInfoItem = function (removeable) {
var self = this;
self.CountryOfLoadingId = ko.observable();
self.PortOfLoadingId = ko.observable();
self.CountryOfDestinationId = ko.observable();
self.PortOfDestinationId = ko.observable();
};
k1.viewModel = function () {
var marketInfoItems = ko.observableArray([]),
countries = ko.observableArray([]),
saveMarketInfo = function () {
var jsonData = ko.toJSON(marketInfoItems);
$.ajax({
url: 'SaveMarketInfos',
type: "POST",
data: jsonData,
datatype: "json",
contentType: "application/json charset=utf-8",
success: function (data) {
if (data) {
window.location.href = "Fin";
} else {
alert("Can not save your market information now!");
}
},
error: function (data) { alert("Can not save your contacts now!"); }
});
},
loadData = function () {
$.getJSON('../api/ListService/GetCountriesWithPorts', function (data) {
countries(data);
});
};
return {
MarketInfoItems: marketInfoItems,
Countries: countries,
LoadData: loadData,
SaveMarketInfo: saveMarketInfo,
};
} ();
The problem occurs when a country like China is selected, which has lots of ports. So if you have 3 or 4 times "China" in your array and I want to send it to the server to save. The error occurs.
What should I do to remedy this?

You have to adjust the maxJsonLength property to a higher value in web.config to resolve the issue.
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="2147483644"/>
</webServices>
</scripting>
</system.web.extensions>
Set a higher value for aspnet:MaxJsonDeserializerMembers in the appSettings:
<appSettings>
<add key="aspnet:MaxJsonDeserializerMembers" value="150000" />
</appSettings>
If those options are not working you could try creating a custom json value provider factory using JSON.NET as specified in this thread.

If you don't want to change a global setting in the web config
Using a global setting will activate large json responses throughout your entire application which might open you up to a denial of service attack.
If a few choice locations are allowed this, you can very quickly use another json serialiser using the Content method like so:
using Newtonsoft.Json;
// ...
public ActionResult BigOldJsonResponse()
{
var response = ServiceWhichProducesLargeObject();
return Content(JsonConvert.SerializeObject(response));
}
// ...

Setting doesn't always work.
The best way to handle this is through the controller,
You would have to write your own Serialize JSON method.
This is how I solved returning a very large json serialized
object as a response to a jquery .Ajax call.
C#: replace the JsonResult data type with ContentResult
// GET: Manifest/GetVendorServiceStagingRecords
[HttpGet]
public ContentResult GetVendorServiceStagingRecords(int? customerProfileId, int? locationId, int? vendorId, DateTime? invoiceDate, int? transactionId, int? transactionLineId)
{
try
{
var result = Manifest.GetVendorServiceStagingRecords(customerProfileId, locationId, vendorId, invoiceDate, null, null, transactionId, transactionLineId);
return SerializeJSON(result);
}
catch (Exception ex)
{
Log.Error("Could not get the vendor service staging records.", ex);
throw;
}
}
private ContentResult SerializeJSON(object toSerialize)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.MaxJsonLength = Int32.MaxValue; // Wahtever max length you want here
var resultData = toSerialize; //Whatever value you are serializing
ContentResult result = new ContentResult();
result.Content = serializer.Serialize(resultData);
result.ContentType = "application/json";
return result;
}
Then in Web.config file increase to maximum size
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="999999999" />
</webServices>
</scripting>
</system.web.extensions>
That work for me.

Related

How i get list from get in javascript

$(document).ready(function () {
$(".discussion").on("click", function () {
var id = $(this).attr('id');
var url = "/Chat/GetMessages/" + id;
$.get(url, null, function (data) {
var htm = "";
for (var i = 0; i < data.length; i++) {
htm += data[i].Content;
}
$("#messages").html(htm);
});
});
});
public ICollection<Message> GetMessages(int id)
{
var messages = this.disService.GetById(id).Messages;
return messages.ToList();
}
I want to show the collection from the function in controller in html. I do get request but the data from the request doesm't contain the data.
Now I have a
A circular reference was detected while serializing an object of type 'System.Collections.Generic.HashSet`1[[Forum.Model.Message, Forum.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'.
You have to return json as :
public JsonResult GetMessages(int id)
{
var messages = this.disService.GetById(id).Messages;
return Json(messages,JsonRequestBehavior.AllowGet)
}
Change your method to return JSON
public JsonResult GetMessages(int id)
{
...
return Json(messages, JsonRequestBehavior.AllowGet);
}
Edit
With the respect to the error message you indicated in your edit, you likely have circular references in your object hierarchy which is not supported by the JSON serializer. If you don't need all properties, you could only return what you want (it looks like you are using only the Content property of Messages), for example
return Json(new
{
Content = messages.Content,
AnotherProperty = messages.AnotherProperty // if you need any other properties of Message class
}, JsonRequestBehavior.AllowGet);
which has the added benefit of reducing the data being returned

adding a new object to observable array in knockout mvc

I'm using knockout mapping to help map a serverside object into JSON. I have an object with numerous collections in it so I don't want to have to recreate and map each piece manually in javascript. So, I'm using knockout-mapping to do this for me.
I was having issues, so I decided to try it with a simple example, so here is what I have for an ASP.NET MVC application:
C# Model:
public class Vaccinations
{
public string Vaccination { get; set; }
public System.DateTime VaccinationDate { get; set; }
}
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
public Dog()
{
this.Vaccinations = new System.Collections.Generic.List<Vaccinations>();
}
public System.Collections.Generic.List<Vacinations> Vacinations { get; set; }
}
As you can see, each Dog has a list of vaccinations they may or may not have.
In my controller, I create and return a pre-populated Dog object:
public ActionResult Load()
{
Dog rambo = new Dog
{
Name = "Rambo",
Age = 5
};
rambo.Vacinations = new System.Collections.Generic.List<Vacinations> {
new Vacinations { Vacination = "Rabies", VacinationDate = DateTime.Now.AddYears(-1) },
new Vacinations { Vacination = "Mumps", VacinationDate = DateTime.Now.AddYears(-2) }
};
return Json(rambo, JsonRequestBehavior.AllowGet);
}
In my view (Index.cshtml), I have it set up to show the dog and a list of it's vaccinations. I want to allow the user to click on an Add Vaccination button to add a new line to the collection and allow them to enter the data.
Again, I'm using knockout.mapping to do the mapping for me. In the javascript section, this is what I have:
var ViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.isValid = ko.computed(function () {
return self.Name().length > 3;
});
// Operations
self.save = function () {
$.ajax({
url: "Dog/Save",
type: "post",
contentType: "application/json",
data: ko.mapping.toJSON(self),
success: function (response) {
alert(response.Status);
}
});
};
self.addVaccination = function () {
self.Vaccinations.push(new self.Vaccination()); // <--- This doesn't work and I know why, so how do I do this?
}
};
$(function () {
$.getJSON("Dog/Load", null, function (data) {
ko.applyBindings(new ViewModel(data));
});
});
My question revolves around the "addVaccination" function that I've added to the ViewModel object. How do I specify a new "Vaccination" object without having to "code" one in Javascript? That was the entire reason for me using knockout mapping so I don't have to do that. But, I don't see any other way around it.
Is there a way to access the base Vaccination object from the Vaccinations observable array so I can create a new one?
And then the final question is, how to I pass this back to my controller? I'm not sure if this will work or not.
You can't directly. But what you can do is define a Vaccination instance at the server side and return it as a the default instance.
So, you need to return the old data and the default instance.
public ActionResult Load()
{
...
var data = new {
defaultVacination = new Vacination(),
rambo = rambo,
};
return Json(data , JsonRequestBehavior.AllowGet);
}
And on the client side you receive the same data and the default instance.
var ViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data.rambo, {}, self);
var defaultInstance = data.defaultVacination;
...
self.addVaccination = function () {
// clone the default instance.
self.Vaccinations.push(ko.utils.extend({}, defaultInstance));
}
I hope it helps.

Knockout simple object mapping

I have a simple MVC Model like this :
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MobileNumber { get; set; }
public string EmailAddress { get; set; }
}
I would like to map one object of this type inside a knockout viewmodel in order to populate it:
var UserViewModel = function () {
var self = this;
self.user = ko.mapping.fromJS({});
$.getJSON("/UserManagement/CreateEmptyUser", function (data) {
ko.mapping.fromJS(data, self.user);
});
self.createUser = function (data, eventArgs) {
var user = data;
$.ajax({
type: "post",
contentType: "application/json",
data: ko.toJSON(user),
url: "#Url.Action("CreateUser")",
success: function () {
window.location = "#Url.Action("AddNew")";
}
});
};
};
The problem I am having is that I have no clue how to map any single object. I've tried using
self.user = ko.mapping.fromJS([]);
$.getJSON("/UserManagement/CreateEmptyUser", function (data) {
ko.mapping.fromJS(data,{}, self.user);
});
which is being used for arrays and trying to extract the element at index 0, the other solution I thought would work is the one in the 2nd comment block. Everything I found on google led me to mapping entire arrays, but nothing towards simple object mapping. Is there a proper way to do this? I would like to keep the model separated and not manually describe its properties in javascript.
Thanks,
Alex Barac
I think your problem is that you are using the mapping from method to instantiate your view model, which will give it no properties, so when you try to map the data after your JSON call, there are no properties on self.user to populate. Either create your user object with the properties you want on there, or else use mapping.fromJS to create your viewmodel in the first place:
self.user = { FirstName: ko.observable(''), etc... }
and leave your mapping line as it is, or
self.user;
$.getJSON("/UserManagement/CreateEmptyUser", function (data) {
self.user = ko.mapping.fromJS(data);
}
I kept working and checking this and I found the solution I wanted at the beginning. The idea is using #Paul Manzotti 's suggestion,
self.user;
$.getJSON("/UserManagement/CreateEmptyUser", function (data) {
self.user = ko.mapping.fromJS(data);
}
but replacing the self.user; with self.user={}; and
self.user = ko.mapping.fromJS(data)
with
ko.mapping.fromJS(data,{},self.user);
If using this, there is no need to manually declare every object's property in the viewmodel. Hope this will be of use to those that stumble upon the same issue.
If your viewModel in question needs to be an observable itself, then I found that use the mapping to return an observable array as the OP mentioned, and then returning the first item in that array works. This is also useful for return one object in ajax that you then wish to push onto an existing observableArray.
I have a helper function for this:
koMappingUsingArray = function (jsObject, mapping) {
var observableObjectArray = ko.observableArray();
var objectArray = new Array(jsObject);
ko.mapping.fromJS(objectArray, mapping, observableObjectArray);
return observableObjectArray()[0];
};
and then call it such as:
self.myObject = ko.observable();
$.ajax({
type: "POST",
url: "....",
data: ko.mapping.toJSON(???),
contentType: "application/json; charset=utf-8",
success: function (returnedObject) {
self.myObject (koMappingUsingArray(returnedObject));
});

knockoutjs mapping from/to POCO object

Is there a way to map from/to a POCO and knockoutjs observable?
I have a Note class:
public class Note
{
public int ID { get; set; }
public string Date { get; set; }
public string Content { get; set; }
public string Category { get; set; }
public string Background { get; set; }
public string Color { get; set; }
}
and this is my javascript:
$(function () {
ko.applyBindings(new viewModel());
});
function note(date, content, category, color, background) {
this.date = date;
this.content = content;
this.category = category;
this.color = color;
this.background = background;
}
function viewModel () {
this.notes = ko.observableArray([]);
this.newNoteContent = ko.observable();
this.save = function (note) {
$.ajax({
url: '#Url.Action("AddNote")',
data: ko.toJSON({ nota: note }),
type: "post",
contentType: "json",
success: function(result) { }
});
}
var self = this;
$.ajax({
url: '#Url.Action("GetNotes")',
type: "get",
contentType: "json",
async: false,
success: function (data) {
var mappedNotes = $.map(data, function (item) {
return new note(item.Date, item.Content, item.Category, item.Color, item.Background);
});
self.notes(mappedNotes);
}
});
}
Ignore the fact that the save function is not used (to simplify the code here).
So, when I load the page I call the server and I retrieve a list of Note objects and I map it in javascript. Notice how ID is not mapped because I dont need it in my view.
So far so good, I see the notes on screen, but how I can save the notes back to the server?
I tried to convert the note (Im saving just the new note and not the entire collection) to JSON and send it to my controller but I don't know how to access to the note in the controller. I tried:
public string AddNote(string date, string content, string category, string background, string color)
{
// TODO
}
but is not working. I want to have something like:
public string AddNote(Note note) {}
(Btw, what's the best return for a method that just save data on DB? void?)
So, How I do this? I tried knockout.mapping plugin but it is quite confusing and I don't get it working for me.
Thank you.
ASP.NET MVC's model binder will look for properties that are case-sensitive. You need to pass your JSON object back to the server with the property names matching your poco object.
I usually do 1 of 2 things:
Make my javascript object property names capital (that way in JS, I know that this object will at some point be a DTO for the server)
function Note(date, content, category, color, background) {
this.Date = date;
this.Content = content;
this.Category = category;
this.Color = color;
this.Background = background;
};
In my AJAX call i will just create an anonymous object to pass back to the server (note this does not require ko.toJSON):
$.ajax({
url: '#Url.Action("AddNote")',
data: JSON.stringify({ note: {
Date: note.date,
Content: note.content,
Category: note.category,
Color: note.color,
Background: note.background
}
}),
type: "post",
contentType: "application/json; charset=utf-8",
success: function(result) { }
});
(note the different contentType parameter as well)
You will want to make your ActionMethod take in a (Note note) and not just the array of parameters.
Also, because the modelbinders look through the posted values in a couple different ways. I've had luck posting JSON objects with out specifying the ActionMethod parameter name:
instead of:
{ note: {
Date: note.date,
Content: note.content,
Category: note.category,
Color: note.color,
Background: note.background
}
}
just do:
{
Date: note.date,
Content: note.content,
Category: note.category,
Color: note.color,
Background: note.background
}
(but this can get dicey with arrays binding to collections and complex types...etc)
As far as the 'Best' signature for a return on a method that does a db call, we generally prefer to see boolean, but that also depends on your needs. Obviously if it is trivial data, void will be fine, but if its a bit more critical, you may want to relay a boolean (at the least) to let your client know it might need to retry (especially if there's a concurrency exception).
If you really need to let your client know what happened in the database, you can foray into the world of custom error handling and exception catching.
Also, if you need to display very specific information back to your user depending upon a successful/unsuccessful database commit, then you could look at creating custom ActionResults that redirect to certain views based upon what happened in the database transaction.
Lastly, as far as getting data back from the server and using Knockout...
again the mapping plugin will work if your property names are the same case or you create a slightly more explicit mapping
My own trick with my JS objects is below. The initialize function is something i created that should be reusable across all your objects as it just says "if the property names match (after being lowercased), either set them by calling the function (knockout compatible) or just assign the value.:
function Note(values){ //values are what just came back from the server
this.date;
this.content;
this.category;
this.color;
this.background;
initialize(values); //call the prototyped function at the bottom of the constructor
};
Note.prototype.initialize = function(values){
var entity = this; //so we don't get confused
var prop = '';
if (values) {
for (prop in values) {
if (values.hasOwnProperty(prop.toLowerCase()) && entity.hasOwnProperty(prop.toLowerCase())) {
//the setter should have the same name as the property on the values object
if (typeof (entity[prop]) === 'function') {
entity[prop](values[prop]); // we are assuming that the setter only takes one param like a Knockout observable()
} else {// if its not a function, then we will just set the value and overwrite whatever it was previously
entity[prop] = values[prop];
}
}
}
}
};

Return PDF to browser using JSON and MVC?

I have a link as follows.
#Html.ActionLink("Create Report", "Screenreport", "Reports", null, new { #class = "subNavA AddBorderTop", id = "screenReport", title = "Create Report" })
Once the link is clicked, I have a the following jQuery code which creates a JSON object and post the information.
$().ready(function () {
// Create Report fron the screen data
$("#screenReport").live("click", function (event) { GenerateScreenReport(this, event); });
}) /* end document.ready() */
function GenerateScreenReport(clikedtag, event) {
var table = $(".EvrakTable").html();
var screendata = tableParser(table);
var Screentable = { Screenlist: screendata };
var myurl = $(clikedtag).attr("href");
var title = $(clikedtag).attr("title");
$.ajax({
url: myurl,
type: 'POST',
data: JSON.stringify(Screentable),
dataType: 'json',
contentType: 'application/json',
success: function () { alert("Got it"); }
});
};
To Handle JSON I have the following two classes. Realize two classes in the same namespace
namespace MyProject.ViewModels
{
public class Screenrecord
{
public string Fname{ get; set; }
public string LName { get; set; }
public string Age { get; set; }
public string DOB { get; set; }
}
public class Screentable
{
public List<Screenrecord> Screenlist { get; set; }
}
}
ANd in my controller, I have the following code:
[HttpPost]
public FileStreamResult Screenreport(Screentable screendata)
{
MemoryStream outputStream = new MemoryStream();
MemoryStream workStream = new MemoryStream();
Document document = new Document();
PdfWriter.GetInstance(document, workStream);
document.Open();
document.Add(new Paragraph("Hello World"));
document.Add(new Paragraph(DateTime.Now.ToString()));
document.Close();
byte[] byteInfo = workStream.ToArray();
outputStream.Write(byteInfo, 0, byteInfo.Length);
outputStream.Position = 0;
return new FileStreamResult(outputStream, "application/pdf");
}
This code is supposed to gerate PDF.
if I leave [HttpPost] as it is, it does NOT generate PDF and it goes to /Screenreport page, however I see my JSON is passed to the controller properly.
(screendata is populated properly - in controller)
But if I comment out [HttpPost], it DOES generate a PDF but screendata (in controller) is null.
Can someone please explain whats's going on and help me figure it out. Thanksin advance.
You cannot use AJAX to download files, because javascript doesn't allow you to save the downloaded content.
To workaround this you need to take 2 steps.
First: make the HTTP Post request, and in the controller action we would store the File content in a Memory stream.Second: on success make another call by setting the window.location to the Download Action method
In your Controller create this 2 actions:
public ActionResult GenerateFile()
{
MemoryStream fileStream = new MemoryStream { Position = 0 };
//position = 0 is important
var fName = string.Format("File-{0}.xlsx", DateTime.Now.ToString("s"));
Session[fName] = fileStream;
return Json(new { success = true, fName }, JsonRequestBehavior.AllowGet);
}
public ActionResult DownloadFile(string fName)
{
var ms = Session[fName] as MemoryStream;
if (ms == null)
return new EmptyResult();
Session[fName] = null;
return File(ms, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fName);
}
In your javascript:
$('#Donwload-button').click(function () {
data = JSON.stringify(YOURDATA);
$.ajax({
contentType: 'application/json; charset=utf-8',
dataType: 'json',
type: 'POST',
url: "/YOURCONTROLLER/GenerateFile",
data: data,
success: function (d) {
if (d.success) {
window.location = "/YOURCONTROLLER/DownloadFile" + "?fName=" + d.fName;
}
},
error: function () {
alert("Error");
}
});
});
I feel obligated to post my answer since I didn't hear from anyone. I ended up creating a form that includes a hidden input, then saved my json object in the hidden input and then submit the form. This time I will get input as an string not a json or xml.
var $hidInput = $("#dataToReport");
$hidInput.val(JSON.stringify(Screentable));
$('#frmScreenreport').submit();
Thanks all anyways.

Resources