Sending large JSON data from C# Web API to Javascript Client - javascript

My calls to the server are built in the following way:
I have a callServer function on the client that sends data to my API;
My API then receives the data from it's controller and pass it to a function that makes a call to the DB;
The controller sends the response back to the client.
This is how I do it (these are just the important pieces of the code, not everything is here):
API (Context.cs):
public static IEnumerable<Dictionary<string, object>> Proc_example(int? value)
{
using (NpgsqlConnection conn = new NpgsqlConnection(ConnStr))
{
try
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "proc_example";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add(new NpgsqlParameter("value", convertNullValue(value)));
var reader = cmd.ExecuteReader();
return new DrToDictionary().Serialize(reader);
}
}
catch (Exception e)
{
throw e;
}
finally
{
conn.Close();
}
}
}
API (Controller.cs):
[Authorize]
public JsonResult example(int? value)
{
try
{
var data = Context.Proc_example(value);
return Json(new AppServerResult()
{
Result = new { list = data }
}, JsonRequestBehavior.AllowGet);
}
catch (Exception e)
{
return Json(new AppServerResult()
{
HasError = true,
ErrorMessage = "Houve um erro ao consultar, tente novamente."
});
}
}
Client (callServer):
app.callServer = function (options, sucessCallback, httpErrCallback, serverErrCallback, operationErrorCallback) {
if (options.source == undefined)
options.headers = { "Authorization": $.cookie('Token') }
options.success = function (result) {
try {
var _result = result;
if (typeof _result == "string") {
_result = jQuery.parseJSON(_result);
}
if (typeof _result.HasError != "undefined" && _result.HasError) {
if (!_result.IsValidationError) {
if (typeof __apiCallerErrorCallback == "function")
__apiCallerErrorCallback(_result.ErrorMessage);
if (typeof serverErrCallback == "function") {
serverErrCallback(_result.ErrorMessage);
}
} else {
app.notifyValidationException(_result.ErrorMessage);
}
} else {
if (typeof sucessCallback == "function") {
if (_result.Result != undefined) sucessCallback(_result.Result);
else sucessCallback(_result);
}
}
} catch (ex) {
throw ex;
}
};
options.error = function (result) {
if (typeof httpErrCallback == "function") {
httpErrCallback();
}
if (typeof __apiCallerErrorCallback == "function")
__apiCallerErrorCallback("HTTP Error");
}
};
jQuery.ajax(options);
};
Calling example:
callingTheServerExample = function (callback, value) {
app.callServer({
type: "POST",
url: app.webAPIRootPath + "Controller/example",
data: "{ 'value':" + JSON.stringify(ko.toJS(value)) + " }",
contentType: "application/json; charset=utf-8",
dataType: "json"
}, function (result) {
if (typeof callback == "function")
callback(result);
});
}
My problem is that when my JSON gets too large, my API gives me a JsonMaxLength exception when sending tha data back to the client (the exception happens on the controller class). I'd prefer not to set my maxJsonLength to a higher value. Instead, I'd like to find a way to send my JSON in chunks from the API and mount it on the client through javascript.
Is there a way to do it?
EDIT:
I forgot to add a detail: I'm using a REST API. As stated in the comments below, pagination is a solution, but what I need is to get the whole record set to be available at once. It's possible to do it using pagination, but it seems to be slower and to cause more API calls. Streaming appears to be a solution, but I never did this before and I can't figure out how to implement it.
EDIT 2:
I've implemented my paging like this:
public JsonResult example(int? value, int page)
{
try
{
var data = Context.Proc_example(value, page);
if (pPage > 0)
{
var pagedResult = data.Skip((page - 1) * 20).Take(20).ToList();
return Json(new AppServerResult()
{
Result = new { list = data}
}, JsonRequestBehavior.AllowGet);
}
else
{
return Json(new AppServerResult()
{
Result = new { list = data}
}, JsonRequestBehavior.AllowGet);
}
}
catch (Exception e)
{
return Json(new AppServerResult()
{
HasError = true,
ErrorMessage = "Houve um erro ao consultar, tente novamente."
});
}
}
But I still didn't achieve what I wanted. Would someone think of a better way?

for JsonMaxLength exception
Try
write in web.config
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="1000000">
</jsonSerialization>
</webServices>
</scripting>
</system.web.extensions>
By default the maxJsonLength is 102400.

Related

Javascript class with web socket prematurely ceasing execution

I am trying to build a JavaScript class that allows me to interact with my Home Assistant server via web sockets (ws library.) The script is intended to be executed in the node.js environment.
const WebSocket = require('ws');
class HomeAssistantWebSocket {
constructor(config = {}) {
this.config = config;
this.initialize();
}
config;
initialized = false;
initializeErrors = [];
authenticated = false;
ws = null;
authenticate = () => {
let {
config,
ws,
serialize
} = this;
console.log("Attempting to authenticate...");
ws.send(serialize({
"type": "auth",
"access_token": config["access_token"]
}));
return true;
}
openConnection = () => {
let {
ws
} = this;
ws.on('open', () => {
console.log("Connection opened!");
});
ws.on('message', (data) => {
this.handleMessage(data);
});
}
deserialize = (string) => {
try {
return JSON.parse(string);
} catch (error) {
return false;
}
}
handleMessage = (data) => {
let {
authenticate,
deserialize,
ws
} = this;
data = deserialize(data);
console.log(data);
if(data["type"] === "auth_required") {
authenticate();
}
if (data["type"] === "auth_ok" && !this.authenticated) {
this.authenticated = true;
console.log("Successfully authenticated");
}
if (data["type"] === "auth_ok") {
ws.send(JSON.stringify({
"id": 20,
"type": "subscribe_events",
}));
}
}
initialize = () => {
let {
config,
initialized,
initializeErrors,
} = this;
if (Object.keys(config).length < 1) {
initializeErrors.push("No config present.");
} else if (!config.hasOwnProperty("access_token") && typeof config["access_token"] === "string") {
initializeErrors.push("Config must contain a valid access_token.");
} else if (!config.hasOwnProperty("home_assistant_url") && typeof config["home_assistant_url"] === "string") {
initializeErrors.push("Config must contain a valid home_assistant_url");
}
if (this.initializeErrors.length === 0) {
this.ws = new WebSocket(config["home_assistant_url"]);
this.openConnection();
initialized = true;
console.log("Attempting to open connection...");
} else {
console.log("Failed to initialize:");
this.initializeErrors.forEach((e) => {
console.log(e);
});
}
return true;
}
serialize = (json) => {
try {
return JSON.Stringify(json);
} catch (error) {
return false;
}
}
}
const haWS = new HomeAssistantWebSocket({
"access_token": "redacted_access_token",
"home_assistant_url": "ws://homeassistant.local:8123/api/websocket"
});
I am running in to an issue where my code ceases execution after the authentication phase. My code prints the following in the console and then the script stops executing. No errors are present.
Connection opened!
{ type: 'auth_required', ha_version: '2021.2.3' }
Attempting to authenticate...
I have verified my code does properly connect to the web socket api and is communicating with the home assistant server. Does anyone see anything wrong with my code that would cause the script to stop execution/garbage collect the ws on message to prevent further messages from being received?
I have a very basic example working as expected outside of a class that makes it pass the authentication phase and leaves the socket open and receives data as expected. Any help would be greatly appreciated.
serialize = (json) => {
try {
return JSON.stringify(json);
} catch (error) {
return false;
}
}
I found the issue in the serialize function. I had an improper reference to the JSON.stringify function. In my code it was JSON.Stringify. It should be JSON.stringify.
It's always the little things...

JSON.parse unexpected end of input error in NodeJS MailParser

I am executing a code in NodeJS child_process.
I used MailParser of Andris9.
I used console.log(JSON.stringify({obj:mail_object})); to get the data of mail_object to the parent.
In the parent, I have this code, JSON.parse(retVal);.
The error came up is "Unexpected end of Input".
This error only shows when the email I received is having an attachment.
If the email doesnt have attachment, there is no error.
Here is the parent method,
getEmails(){
let workerProcess = child_process.spawn('node', [basePath+'imports/workers/retrieveEmail.js']);
workerProcess.stdout.on('data', function (data) {
try{
let retVal = new Buffer(data).toString();
retVal = JSON.parse(retVal);
console.log(retVal);
if(typeof retVal.err == "undefined"){
console.log(retVal.obj.from[0].address);
Fiber(function () {
var objs = [];
if (typeof retVal.obj.attachments !== "undefined") {
console.log("Test passed");
retVal.obj.attachments.forEach(function (attachment) {
let future = new Future();
Fiber(function () {
Files.write(attachment.content, {
fileName: attachment.fileName,
type: attachment.contentType
}, function (error, fileRef) {
if (error) {
future.throw(new Meteor.Error(500, error.message));
} else {
...
}
});
}).run();
var bool = true;
if (bool = future.wait())
objs.push(bool);
});
}
...
}).run();
}else{
console.log(retVal.err);
}
}catch(e){
console.log(e);
}
});
workerProcess.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
workerProcess.on('close', function (code) {
console.log('child process exited with code ' + code);
});
},
I removed some unnecessary codes.
Here is my retrieveEmail.js,
...
client.on("retr", function (status, msgnumber, data, rawdata) {
if (status === true) {
var mailparser = new MailParser({
streamAttachments: false
});
var timeStamp = Math.floor(Date.now());
mailparser.on("attachment", function (attachment, mail) {
console.log(JSON.stringify({err:"testpassed1"}));
});
mailparser.on("end", function (mail_object) {
console.log(JSON.stringify({err:"testpassed2"}));
console.log(JSON.stringify({obj:mail_object}));
});
mailparser.write(data);
mailparser.end();
client.dele(msgnumber);
} else {
console.log("RETR failed for msgnumber " + msgnumber);
client.quit();
}
});
...

How do I pass data from angular post to web api post

I have an angular post method through which I want to pass data to web api post but doesn't seem to work.
What could I be doing wrong?
customerPersistenceService.js
var customerPersistenceService = function ($http, $q) {
return {
save: function(customer) {
var deferred = $q.defer();
$http.post("/api/customers", customer)
.success(deferred.resolve)
.error(deferred.reject);
return deferred.promise;
},
update: function(customer) {
var deferred = $q.defer();
$http.put("/api/customers/{id}" + customer.id, customer)
.success(deferred.resolve)
.error(deferred.reject);
return deferred.promise;
}
};
};
customerEditCtrl.js
function customerEditCtr($stateParams, $location, customerPersistenceService) {
var vm = this;
vm.editableCustomer = {};
vm.selectedCustomerId = $stateParams.id;
customerPersistenceService.getById(vm.selectedCustomerId).then(
function (customer) {
vm.editableCustomer = customer;
});
};
vm.saveCommand = function () {
if (saveCustomer) {
var customer = vm.editableCustomer;
customer.id = vm.selectedCustomerId;
if (customer.id !== 0) {
customerPersistenceService.update(customer).then(
function (result) {
return result.data;
});
} else {
customerPersistenceService.save(customer).then(
function (result) {
return result.data;
});
}
}
};
};
In the CustomerAPIController.cs my methods look like these:
[HttpPost]
public HttpResponseMessage Post([FromBody]Customer newCustomer)
{
try
{
if (ModelState.IsValid)
{
_customerService.AddNewCustomer(newCustomer);
return Request.CreateResponse(HttpStatusCode.Created, newCustomer);
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
}
}
[HttpPut]
public HttpResponseMessage Put(int id, [FromBody]Customer editableCustomer)
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
if (id != editableCustomer.Id)
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
try
{
_customerService.UpdateCustomer(editableCustomer);
return Request.CreateResponse(HttpStatusCode.OK, "{success:'true', verb:'PUT'}");
}
catch (DbUpdateConcurrencyException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
}
}
Update:
After some investigation, I realise the vm.editableCustomer seems to contain an array of all customer objects making it hard to pass to the Web API POST.
I fail to understand how this object gets assigned with all customer objects.
There is a clear error:
$http.put("/api/customers/{id}" + customer.id, customer)
This will try to PUT to an url like this:
http://yoursite/api/customers/{id}927
You need to remove the {id} part, so that your url is correct:
http://yoursite/api/customers/927
The segment enclosed in braces only exists in the server side route template, and it's used to extract the parameter from the incoming URI.

Promises in angularjs controller - how to implement

I am trying to implement a basic function using promises in one of my controllers just so I can ensure it is working correctly before adding in more complex functionality. I am getting a "TypeError: undefined is not a function" on the ".then(function(data){" in the lockPromise method.
Function called from view
$scope.lockPromise = function(fieldId) {
$scope.getLockMessage2(fieldId).getWeather()
.then(function(data) {
if (data === "returned SUCCESS info") {
alert("data is good");
} else {
alert("FAILED");
}
}, function(error) {
alert(error);
});
};
Second function in ctrl
$scope.getLockMessage2 = function(fieldId) {
return{
getWeather: function() {
return $http.get('/api/getData')
.then(function(response) {
if (typeof response.data === "string") {
return response.data;
} else {
return $q.reject(response.data);
}
}, function(response) {
return $q.reject(response.data);
});
}
};
};
API GET
[Route("api/getData")]
public HttpResponseMessage GetData()
{
string data = JsonConvert.SerializeObject("returned SUCCESS info");
return new HttpResponseMessage
{
Content = new StringContent(data, Encoding.UTF8, "application/json")
};
}
EDIT 1:
code updated to reflect comments
Change
$scope.getLockMessage2(fieldId).then
to
$scope.getLockMessage2(fieldId).getWeather().then
Your $scope.getLockMessage2 return an object, not function.
I think the code should be (not tested):
$scope.lockPromise = function(fieldId) {
$scope.getLockMessage2(fieldId).getWeather()
.then(function(data) {
if (data === "good") {
alert("data is good");
} else {
alert("FAILED");
}
}, function(error) {
alert(error);
});
};

AJAX request error: "SyntaxError: JSON.parse: unexpected character"

I'm working on an ASP MVC application, in one page I try to make an AJAX request using jQuery and I get this error (in Firefox):
SyntaxError: JSON.parse: unexpected character.
This is my JavaScript function:
function deleteGoal(id) {
var dataget = { "goalId": id };
$.ajax({
type: "GET",
url: '#Url.Action("DeleteGoal", "Goals")',
data: dataget,
dataType: "json",
success: function (json) {
if (json.isValid == false) {
Growl.error({
title: 'Error sending messages',
text: json.error
});
return false;
}
else {
alert("success");
}
},
error: function (xhr, status, error) {
alert(error);
},
});
}
which is called in the following way:
<a class="btn btn-red" id="delete_#val.Id" onclick="javascript:deleteGoal('#val.Id')">#ViewBag.Translator.Translate("Delete")</a>
The ID passed as the parameter is a GUID.
This is the controller's full code:
class GoalsController : BaseController
{
private const string ErrorViewPath = "../Shared/Error";
public ActionResult Goals(Guid? nodeId = null, string groupByCriteria = "Type", string sortCriteria = "LastModified")
{
try
{
ViewBag.Translator = SessionManager.Translator;
ViewBag.NodeId = nodeId;
if (!IsUserLogged())
{
return RedirectToAction("Login","Account");
}
if (!IsUserRegistered())
{
return RedirectToAction("Register", "Account", null);
}
if (SessionManager.UserStatus < (long)UserStatus.AmwayInitialized)
{
return RedirectToAction("Activation", "Account");
}
var res = Proxy.GetGoals(nodeId == null ? (Guid)SessionManager.NodeId : (Guid)nodeId);
if (res.HasErrors)
{
return ReportErrors(res, ErrorViewPath);
}
if (res.Value.Count == 0)
{
return View(new List<IGrouping<object,GoalContract>>());
}
ViewBag.groupBy = groupByCriteria;
ViewBag.sortBy = sortCriteria;
PropertyInfo sortPinfo = res.Value[0].GetType().GetProperty(sortCriteria);
PropertyInfo groupPinfo = res.Value[0].GetType().GetProperty(groupByCriteria);
res.Value.Sort(new Comparison<GoalContract>((x, y) => CompareGoalContract(x, y, sortPinfo)));
var groups = res.Value.GroupBy(g => g.GetType().GetProperty(groupByCriteria).GetValue(g));
return View(groups);
}
catch (Exception ex)
{
return ReportErrors(ex, ErrorViewPath);
}
}
private int CompareGoalContract(GoalContract t1, GoalContract t2, PropertyInfo property)
{
IComparable v1 = (IComparable)property.GetValue(t1);
IComparable v2 = (IComparable)property.GetValue(t2);
return v1.CompareTo(v2);
}
public ActionResult EditGoals(Guid? nodeId, string groupByCriteria = "Type", string sortCriteria = "LastModified")
{
try
{
ViewBag.Translator = SessionManager.Translator;
if (!IsUserLogged())
{
return RedirectToAction("Login", "Account");
}
if (!IsUserRegistered())
{
return RedirectToAction("Register", "Account", null);
}
if (SessionManager.UserStatus < (long)UserStatus.AmwayInitialized)
{
return RedirectToAction("Activation", "Account");
}
var res = Proxy.GetGoals(nodeId == null ? (Guid)SessionManager.NodeId : (Guid)nodeId);
if (res.HasErrors)
{
return ReportErrors(res, ErrorViewPath);
}
if (res.Value.Count == 0)
{
return View(new List<IGrouping<object, GoalContract>>());
}
ViewBag.groupBy = groupByCriteria;
ViewBag.sortBy = sortCriteria;
PropertyInfo sortPinfo = res.Value[0].GetType().GetProperty(sortCriteria);
PropertyInfo groupPinfo = res.Value[0].GetType().GetProperty(groupByCriteria);
res.Value.Sort(new Comparison<GoalContract>((x, y) => CompareGoalContract(x, y, sortPinfo)));
var groups = res.Value.GroupBy(g => g.GetType().GetProperty(groupByCriteria).GetValue(g));
return View(groups);
}
catch (Exception ex)
{
return ReportErrors(ex, ErrorViewPath);
}
}
[HttpPost]
[AllowAnonymous]
public ActionResult SaveGoal(GoalContract contract)
{
try
{
contract.LastModified = DateTime.UtcNow;
var result = Proxy.SaveGoal(contract);
return Json(new { error = result.PrintErrors(), isValid = !result.HasErrors});
}
catch (Exception ex)
{
return Json(new { error = ex.Message, isValid = false, isException = true });
}
}
[HttpPost]
public ActionResult DeleteGoal(Guid goalId)
{
try
{
var result = Proxy.DeleteGoal(goalId);
return Json(new { error = result.PrintErrors(), isValid = !result.HasErrors, isException = false }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(new { error = ex.Message, isValid = false, isException = true }, JsonRequestBehavior.AllowGet);
}
}
}
Please change
$.ajax({
type: "GET",
url: '#Url.Action("DeleteGoal", "Goals")',
to
$.ajax({
type: "POST",
url: '#Url.Action("DeleteGoal", "Goals")',
and if you don't want to do a post then from the action remove attribute
[HttpPost]
so that your action looks like
public ActionResult DeleteGoal(Guid goalId)
{
try
{
var result = Proxy.DeleteGoal(goalId);
return Json(new { error = result.PrintErrors(), isValid = !result.HasErrors, isException = false }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(new { error = ex.Message, isValid = false, isException = true }, JsonRequestBehavior.AllowGet);
}
}
Hope that helps.

Categories