.Net Core POST Request Array size limit - javascript

I have a UI written in JavaScript with a .net core backend. My ajax call in JS looks like this:
const data = { users: userArray, message: sendMessage, url: optUrl };
$.ajax({
type: "POST",
url: `${App_Url}/ui/Notifications/send`,
dataType: "application/json",
data: JSON.stringify(data),
success: (response) => {
// if (spinner)
// spinner.stop();
//
notifySuccess("Users imported successfully!");
$("#user_table").html(response);
},
error: (error) => {
//if (spinner)
// spinner.stop();
//
notifyErrors(error.responseJSON.errors);
}
});
Now the problem that I'm having is that when my userArray is bigger than some number (not sure what the number is) then my server seems to just drop the call. For example when my userArray is size 15 I can set a break point in the .NET controller and get to it just fine. However when my userArray is 1000 I don't even get to the break point and my server returns a 500. What is causing this and how do I go about fixing it?
My controller:
[HttpPost("send")]
[DisableRequestSizeLimit]
public async Task<IActionResult> SendNotificationsAsync(CustomNotificationRq request)
{
var token = await _service.GetMiddlewareToken();
var users = request.users;
var message = request.message;
var url = request.url;
var response = await _service.SendNotificationAsync(users.ToList(), message, url, token.token);
return response;
}

You need to increase this value in your web.config in your .net core api application
<appSettings>
<add key="aspnet:MaxJsonDeserializerMembers" value="150000" />
</appSettings>

Related

jQuery $.AJAX to replace with JavaScript fetch - mvc4 method arguments are always null

We had a working jQuery script which calls an MVC4 C# controller method like this:
// C# controller method
public ActionResult MyMethod(String text, String number)
{
... do something ...
... returns a partial view html result ...
}
// javascript call
var myData = { text:"something", number: "12" };
$.ajax({ url:ourUrl, type:"GET", data: myData,
success: function(data) { processAjaxHtml( data )}});
Now we want to replace this $.ajax call to a native fetch(...) call like this (and use a Promise):
function startAjaxHtmlCall(url) {
var result = fetch( url, {method: "GET", body: myData});
return result.then( function(resp) { return resp.text(); });
}
starAjaxCall(ourUrl).then( resp => processAjaxHtml(resp) );
We have problems, and somehow we cannot find the answers:
using GET we cannot attach body parameters (I see that the html GET and POST calls are quite different, but the $.ajax somehow resolved this problem)
we changed the GET to POST but the controller method still got "null"-s in the parameters
we changed the fetch call to "body: JSON.stringify(myData)" but the method still gots null
we constructed a temp class with the 2 properties, changed the method parameters as well - but the properties still contains null
we added to the [FromBody] attribute before the method class parameter but it got still nulls
we replace body: JSON.stringify(myData) to body: myData - still the same
Note: we tried this in Firefox and Chrome, the code is MVC5, C#, .NET Framework 4.5 (not .CORE!) hosted by IIS.
We changed the javascript call as the following (and everything works again):
var promise = new Promise((resolve, reject) => {
$.ajax({
url: url,
method: method,
data: myData,
success: function(data) { resolve(data); },
error: function(error) { reject(error); },
});
});
return promise;
// note: .then( function(resp) { return resp.text(); }) // needs no more
So: what do we wrong? what is the information we do not know, do not understand about fetch? How to replace $.ajax to fetch in this situation correctly? can it works with GET again? how to modify the controller method to receive the arguments?
GET requests do not have a BODY, they have querystring parameters. Using URLSearchParams makes it easy
var myData = { text:"something", number: "12" };
return fetch('https://example.com?' + new URLSearchParams(myData))
.then( function(resp) { return resp.text(); })
Other way of building the URL
const url = new URL('https://example.com');
url.search = new URLSearchParams(myData).toString();
return fetch(url)...
If you were planning on sending JSON to the server with a post request
fetch('https://example.com', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(myData)
});

Asp .Net Core 2.2 Razor Pages Ajax Call Post not working

I've done so many ajax in razor pages but i can't figure out why this does not work. It keeps giving me error 400 on dev tools. It does not reach the page handler no matter what.
<script>
$.ajax({
url: "/Account/Users/Index?handler=Delete",
type: "POST",
data: {
id: id
},
success: function () {
swal("Utilizador Desactivado!", {
icon: "success",
});
},
error: function (xhr, ajaxOptions, thrownError) {
swal("Falha na ligação ao servidor. Tente novamente mais tarde.");
}
});
</script>
page handler
public async Task<IActionResult> OnPostDeleteAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _context.Users.FindAsync(id);
if (user != null)
{
user.IsActivo = false;
_context.Users.Attach(user).Property( u => u.IsActivo).IsModified = true;
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
I tried many url combinations and none work. I don't see what is wrong in here....
EDIT
It seems like the problem is the anti forgery token not being validated on razor page.
I wrote Ignore Anti forgery Token on the page model and everything works correctly
As you've already found out it's the anti forgery token, that is ruining your day.
Now, if you are inside a form, asp.net core will create a hidden input with that token for you. If you are not working with a form on your page, you'll have to call #Html.AntiForgeryToken(), which will add the token for you.
Still, this will not resolve the Bad Request for you. You have to add it to your ajax call:
$.ajax({
url: "/Account/Users/Index?handler=Delete",
type: "POST",
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val());
},
data: {
id: id
},
});
Additionally, add this line to your Startup.cs file:
services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
I don't know what you mapping of URL use, but as usual it consists of controllerName/actionName/. In your case try to use:
url: "/Account/OnPostDeleteAsync"
or
url: "/Users/OnPostDeleteAsync"
but if your URL is correct, then try to use [FromForm] attribute
public async Task<IActionResult> OnPostDeleteAsync([FromForm]int? id)
I hope this will help

Two requests in one time immediatly. ASP MVC + JQuery Ajax

MVC application (ASP.NET MVC, client: jquery).
Problem: The second ajax-request wait, when the first ajax request will done.
I need, when the first and the second ajax-requests executes immediatly in one time.
The page sends to server to determine the count of records (the first ajax-request), very long (~5-7 seconds).
The operator click the buttom to open the card to edit it (the second ajax-request, fast, get the Dto-model).
The user doesn't need to wait the first request, he wants to work immediatly.
As a result, in Chrome in network page, two requests in status 'pending'. The second waits the first.
Question, how can I send requests, to execute asynchronously ?
The first ajax-request:
`window.jQuery`.ajax({
type: 'POST',
url: Url.Action("GetCountBooks", "Book");
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: JSON.stringify({ typeBook: "...", filter: "..." };),
success: function (data) {
// show in UI page the count of books by filter and params
},
error: function (data) {
//show error
}});
public class BookController : Controller
{
[HttpPost]
public NJsonResult GetCountBooks(string typeBook, Filter filter)
{
var data = DbProvider.GetCountBooks(typeBook, filter)
if (data.Result == ResultType.Success)
{
var count = data.Data;
return new NJsonResult
{
Data = new { Data = count }
};
}
return new NJsonResult
{
Data = new { Error = "Error while counting the books." }
};
}
}
The second ajax-request:
`window.jQuery`.ajax({
type: 'POST',
dataType: 'json',
contentType: "application/json",
url: Url.Action("GetBookById", "Book"),
data: JSON.stringify({ id: bookId }),
success: function (data) {
// show jquery dialog form to edit dto-model.
},
error: function (data) {
//show error
}});
public class BookController : Controller
{
[HttpPost]
public NJsonResult GetBookById(int id)
{
var data = DbProvider.GetBookById(id)
if (data.Result == ResultType.Success)
{
var book = data.Data;
return new NJsonResult
{
Data = new { Data = book }
};
return new NJsonResult
{
Data = new { Error = "The book is not found." }
};
}
return new NJsonResult
{
Data = new { Error = "Error while getting the book." }
};
}
}
I Cannot union ajax requests into one! The user can send various second request.
You need a fork-join splitter to fork 2 tasks and join based on some condition.
For example here is my implementation:
function fork(promises) {
return {
join: (callback) => {
let numOfTasks = promises.length;
let forkId = Math.ceil(Math.random() * 1000);
fork_join_map[forkId] = {
expected: numOfTasks,
current: 0
};
promises.forEach((p) => {
p.then((data) => {
fork_join_map[forkId].current++;
if (fork_join_map[forkId].expected === fork_join_map[forkId].current) {
if (callback) callback(data)
}
})
});
}
}}
Pass any number of async tasks (promises) into fork method and join when all are done. The done criteria here is managed by simple global object fork_join_map which tracks the results of your fork-join process (global is not good but its just an example). The particular fork-join is identified by forkId which is 0..1000 in this example which is not quite good again, but I hope you got the idea.
With jQuery you can create promise with $.when( $.ajax(..your ajax call) )
In the end you can join your promises like this
fork([
$.when( $.ajax(..your ajax call 1) ),
$.when( $.ajax(..your ajax call 2) )
]).join(() => {
// do your logic here when both calls are done
});
It's my own implementation, there may be already-written library functions for this in jQuery - I dont know. Hope this will give you a right direction at least.
The solution is to add attribute to Asp Controller: [SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
http://johnculviner.com/asp-net-concurrent-ajax-requests-and-session-state-blocking/

How to consume an AJAX JSON response in AngularJS?

An AngularJS app needs to retrieve a JSON object from a REST service called from a Spring Boot back end. How do I modify the code below so that the response can be parsed into the properties of the returned JSON object?
For example, I will want to extract the firstname, lastname, and other properties from the JSON object after it is returned.
Here is the AngularJS controller that calls the REST service:
angular.module('confirm', []).controller('confirm', function($scope, $http, $routeParams) {
// set the default value
$scope.confirmStatus = "blank";
$scope.$on('$viewContentLoaded', function() {
var str1 = "/confirm-email?d=";
var str2 = $routeParams.d;
var res = str1.concat(str2);
var fnm3 = "nothing";
$http.post(res).then(function(response) {
fnm3 = response.data.firstname;//this line halts the program
//replacing with following line doesn't work.
//$scope.weblead = response.data;
});
$scope.confirmStatus = "success";
document.write(fnm3);
});
});
And here is the Spring Boot method that delivers the JSON response:
#RequestMapping(value = "/confirm-email", method = RequestMethod.POST)
public #ResponseBody WebLead confirmEmail(HttpSession session, #RequestParam(value="d") String dval) {
WebLead dummy = new WebLead();dummy.setFirstname("justAtest");
try{
System.out.println("The Server Heard The registration form Request!");
System.out.println("dval is: "+dval);
String sid = session.getId();
System.out.println("session id is: "+sid);
try{
List<WebLead> wleads = myrepo.findBySessionid(dval);
if(wleads.size()>0){//replace with better handling later
System.out.println("wleads.size is > 0 !");
wleads.get(0).setEmailConfirmed("true");
myrepo.save(wleads.get(0));
return myrepo.findBySessionid(dval).get(0);
}
return dummy;
} catch(Exception e){return dummy;}
} catch(Exception e){return dummy;}
}
NOTE: We know that the post was processed at the server, because the terminal logs starting with the SYSO in the /confirm-email handler are:
The Server Heard The registration form Request!
dval is: a1b2c3
session id is: E1F844262F254E9B0525504723DBA490
2016-01-07 12:11:49.773 DEBUG 7288 --- [nio-9000-exec-9] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2016-01-07 12:11:49.774 DEBUG 7288 --- [nio-9000-exec-9] tRepository$SaveToSessionResponseWrapper : Skip invoking on
2016-01-07 12:11:49.774 DEBUG 7288 --- [nio-9000-exec-9] tRepository$SaveToSessionResponseWrapper : Skip invoking on
2016-01-07 12:11:49.774 DEBUG 7288 --- [nio-9000-exec-9] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally
2016-01-07 12:11:49.774 DEBUG 7288 --- [nio-9000-exec-9] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
You can use JSON.parse(jsonData). Here is an example that should get you there.
$http.post(res).then(function(response) {
$scope.data = JSON.parse(response.data);
fnm3 = $scope.data.firstname;
});
Also, when I am working with the REST service I have, I like to put console logs in the function when debugging and then I remove them before I finish. This gives you an easy way to see what the service gave you back.
$http.post(res).then(function(response) {
console.log(response);
});
First of all i wouldn't recommend building your own strings with parameters when doing requests with angular. You can simply do something like this:
$http({
method: 'POST',
url: '/confirm-email',
headers: {
'Content-Type': 'application/json'
},
params: {
d: $routeParams.d
}
}).then(function(response) {
...
});
Secondly if you put the content-type header like above to "application/json" and if the server supports it, then angular will JSON.parse your data automatically and simply return a javascript object to you as response.data.
And the third and last problem is that your content will never display the given data in your code, because the POST call is asynchronous, but document.write the content of the variable fnm3 immediately after firing the request.
You have two options to fix this:
(Fast, easy, but bad) do a second document.write WITHIN the callback of your post-request
(The correct way) Define your value on the angular-scope instead:
$scope.fnm3 = response.data.firstname;
and define a corresponding template to do some angular two-way-binding magic with it, like:
Firstname: {{ fnm3 }}
For the purpose of writing less code and more efficiency and in order to make reusability of calling services (since you call your services in multiple pages) use the following code :
App.service("metaService", function ($http,$cookies) {
this.CallService = function (verb, serviceName, Data) {
var Url = BaseURL + serviceName;
switch (verb) {
case "get":
{
return $http({
method: verb,
url: Url
, headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': '*/*',
}
});
break;
}
case "post":
case "put":
case "delete":
{
return $http({
method: verb,
url: Url,
data: Data
, headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': '*/*',
}
});
break;
}
}
}
});
then you can call callservice method like so
var SearchData = metaService.CallService1(method, "meta/selectAll", searchedMeta);
SearchData.success(function (data) {
$scope.Title = data.Title
}

Download CloudBlockBlob as savable file to a browser from .Net MVC 6

I'm trying to provide a browser user the ability to select a file from a list an download a file back to the user as a file download.
My JavaScript looks like this:
$scope.getFile = function (podUri, podName) {
$http.get('api/getDharmaPod', { params: { containerName: $scope.selectedContainer, podName: podName } })
.success(function (data, status, headers, config) {
$("#audio").text("Got file: ");
})
.error(function (data, status, headers, config) {
alert("got an error:" + status);
});
};
I've also tried the following that I found on stackOverflow
$scope.getFile = function (podUri, podName) {
$http({
method: 'GET',
params: { containerName: $scope.selectedContainer, podName: podName },
cache: false,
url: 'api/getDharmaPod',
headers: {
'Content-Type': 'audio/mpeg; charset=utf-8'
}
}).success(function (data, status) {
console.log(status);
}).error(function (data, status) {
alert("got an error:" + status);
});
};
But the result is the same: the browser silently receives the server's transmission and doesn't offer to save it anywhere.
My MVC controller method looks like this:
[HttpGet, Route("api/getDharmaPod")]
public async Task<HttpResponse> GetDharmaPod(string containerName, string podName)
{
var dharmaBlob = AzureStorageAccess.GetBlob(containerName, podName);
MemoryStream memStream = new MemoryStream();
dharmaBlob.DownloadToStream(memStream);
Response.ContentType = "audio/mpeg";
await Response.SendAsync(memStream.ToArray());
return null;
}
I've also tried:
[HttpGet, Route("api/getDharmaPod")]
public FileResult GetDharmaPod(string containerName, string podName)
{
var dharmaBlob = AzureStorageAccess.GetBlob(containerName, podName);
MemoryStream memStream = new MemoryStream();
dharmaBlob.DownloadToStream(memStream);
Response.ContentType = "audio/mpeg";
return File(memStream.ToArray(), "audio/mpeg", podName);
}
Again the browser receives the data but doesn't see it as a file to be stored. It just receives it into a variable. I'd want it to see it as a download and save it to the download file.
I'm not sure if I'm sending it incorrectly or receiving it incorrectly or both :-(
Thanks for any guidance.
I've decided to go another way since I can't seem to find a solution.
My solution is to just download the file directly from the container using the blob's url. I've made it somewhat secure by generating a shared access key in my controller as follows:
public static string GetSharedAccessKey(string containerName)
{
CloudStorageAccount storageAccount = GetAccount(_useDev);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
SharedAccessBlobPolicy blobPolicy = new SharedAccessBlobPolicy
{
Permissions = SharedAccessBlobPermissions.Read,
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(1)
};
return container.GetSharedAccessSignature(blobPolicy);
}
this returns an access key that can be appended to the 'a' tag's href link for the blob file that allows access for one hour.
I'm now getting a file that the browser is storing in the downloads directory as expected.
The result of allowing direct access to the storage account is also more efficient for the server-side app.

Categories

Resources