multiple ajax calls to async Action breaks code - c#

I'm writing a web application in .Net Core Framework and certain elements of the front end use Ajax calls to API that returns certain data.
If I'm on a page that requires 1 API call, it works fine, however as soon as I get on a page that makes more than 1 calls, debugging (well, going line by line in the code) becomes hell with the debugger jumping up and down the code) and in the end the results all end up as errors.
Rarely will I get the results if a website makes more calls, sometimes it happens but odds of that are.. 1 / 50 and this is unacceptable.
Weirdly enough, after I visit such page and then go back to a page that makes 1 call, even that 1 call ends with an error.
I'm not really sure what I need to include here as a code so I'll add both jquery and the action.
Before I put any code here, I'd like to explain my question..
I'd like to know what exactly is causing this, and why are the ajax calls causing parallel executions of the Action and how could I force that ajax calls are ran one by one or do anything else to fix this issue. I believe at most 30,40 calls should be made by some Views and if even 2 cause such issues, there's obviously a problem here, but unfortunately I'm not skilled enough yet to see and fix it.
Any help will be greatly appreciated.
jquery (I've removed pieces of code simply because they aren't relevant. it's just some class changes):
$(window).ready(function () {
$('.price').each(function (i, e) {
var id = $(e).data('skinid');
var exterior = $(e).data('exterior');
var type = $(e).data('type');
$.ajax({
type: "GET",
url: "/Skin/GetPrice",
data: { Id: id, Exterior : exterior, Type : type },
cache: false,
dataType: "json",
}).complete(function (data) {
var price = data.responseJSON.price;
if (data.responseJSON.querySuccessful)
{
if (data.responseJSON.listingExists)
{
//do stuff here
}
else
{
//other stuff here
}
}
else
{
//print error..
}
});
});
});
Action is quite long so I'll short it up as well:
[HttpGet]
public async Task<ActionResult> GetPrice(int Id = 0, string Exterior = null, string Type = null)
{
PriceViewModel model = null;
if (_context.Skins.Any(x => x.Id == Id))
{
if (Type == "Skin")
{
Skin Query = _context.Skins.Where(x => x.Id == Id).Include(x => x.Weapon).Include(x => x.Quality).Select(x => x).Single();
using (var client = new HttpClient())
{
try
{
string uri = "/market/priceoverview/?currency=3&appid=730&market_hash_name=" + Query.Weapon.Name.Replace(" ", "%20") + "%20|%20" + Query.Name.Replace(" ", "%20") + "%20(" + Exterior.Replace(" ", "%20") + ")";
client.BaseAddress = new Uri("http://steamcommunity.com");
var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode(); // Throw in not success
var stringResponse = await response.Content.ReadAsStringAsync();
JObject data = JObject.Parse(stringResponse);
if (data["success"].Value<bool>())
{
if (data["lowest_price"] != null)
{
// return good model
}
//return no result model
}
}
catch
{
// return error
}
}
}
if (Type == "Skin-List")
{
Skin Query = _context.Skins.Where(x => x.Id == Id).Include(x => x.Weapon).Include(x => x.Quality).Select(x => x).Single();
Dictionary<Exterior, PriceViewModel> PossibleExteriors = new Dictionary<Exterior, PriceViewModel>();
bool possible = false;
foreach (Exterior e in _context.Exteriors)
{
if (e.Name == Query.BestExterior)
possible = true;
if (possible)
PossibleExteriors.Add(e, null);
if (e.Name == Query.WorstExterior)
possible = false;
}
List<Exterior> exteriors = PossibleExteriors.Keys.ToList();
foreach (Exterior e in exteriors)
{
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri("http://steamcommunity.com");
string uri = "/market/priceoverview/?currency=3&appid=730&market_hash_name=" + Query.Weapon.Name.Replace(" ", "%20") + "%20|%20" + Query.Name.Replace(" ", "%20") + "%20(" + e.Name.Replace(" ", "%20") + ")";
var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode(); // Throw in not success
var stringResponse = await response.Content.ReadAsStringAsync();
JObject data = JObject.Parse(stringResponse);
if (data["success"].Value<bool>())
{
if (data["lowest_price"] != null)
{
model = new PriceViewModel
{
QuerySuccessful = true,
ListingExists = true,
ListingUrl = "http://steamcommunity.com/market/listings/730/" + Query.Weapon.Name + " | " + Query.Name + " (" + e.Name.Replace(" ", "%20") + ")",
Price = data["lowest_price"].ToString()
};
PossibleExteriors[e] = model;
}
else
{
model = new PriceViewModel
{
QuerySuccessful = true,
ListingExists = false,
ListingUrl = "http://steamcommunity.com/market/listings/730/" + Query.Weapon.Name + " | " + Query.Name + " (" + e.Name.Replace(" ", "%20") + ")",
Price = ""
};
PossibleExteriors[e] = model;
}
}
}
catch
{
model = new PriceViewModel
{
QuerySuccessful = false,
ListingExists = false,
Price = ""
};
PossibleExteriors[e] = null;
}
}
}
if (PossibleExteriors.Values.Any(x => x != null))
{
//return good model
}
else
{
//return empty
}
}
}
//return error
}

I'd like to know what exactly is causing this, and why are the ajax calls causing parallel executions of the Action and how could I force that ajax calls are ran one by one or do anything else to fix this issue.
You've got a fundamental misunderstanding about asynchronous calls. This JavaScript will fire off a request, then continue into the next iteration of the loop before getting a response. The complete function will be called whenever an asynchronous call is completed, whenever that happens and in whatever order it happens in. This means that new calls are arriving faster than the application can return results, so it's working on more than one request at a time. Perfectly normal!
If these calls need to be made in a specific order, why not lump them all into one request? Pass a list of items into the data property and do a loop in C#.
If this has to be a loop in JavaScript, you'll need to wait for a response from the asynchronous call (basically make it synchronous) If you want to do this calls synchronously, as in call GetPrice, get a response, do something with the response, then go to the next iteration and repeat, you'll need to change the approach.
var $prices = $('.prices').map(function() {
return new { Id: id, Exterior : exterior, Type : type }
});
getPrice($prices, 0);
function getPrice(prices, index) {
$.ajax({
type: "GET",
url: "/Skin/GetPrice",
data: prices[index] // if this were the whole prices list, you could do the loop in C# instead
cache: false,
dataType: "json",
}).complete(function (data) {
// do all that processing stuff
index++;
// if there are more prices to compute, call the function again
if (prices.length - 1 >= index) {
getPrice(prices, index);
}
});
}

Related

How to pass docx file from Controller into View using AJAX?

Hi Stack Overflow community, first post, work your magic!
The problem I'm having is that I can generate my docx file, but when I try to return it the data is coming back in a format that I've never seen before? Does anyone know what this is?
Screenshot of unidentifiable code - it looks like Wingdings
Starting with the razor view
//Start action
$('#returnDeductionSheets').click(function () {
//Retrieve date options for user to select
$.ajax({
async: true,
type: 'POST',
url: 'ReturnDeductionsSheetList',
success: function (data) {
if (data != null) {
var options = data;
//Format JSON dates into read-able date time format
function formatDate(options) {
var dateString = options.substring(6);
var currentTime = new Date(parseInt(dateString));
var month = currentTime.getMonth() + 1;
var day = currentTime.getDate();
var year = currentTime.getFullYear();
var date = day + "/" + month + "/" + year;
return date;
};
//Check if I have more than one date, then return the options via a Bootbox view
if (options.length > 1) {
bootbox.prompt({
title: "Select the Deductions Sheet you would like to print.",
inputType: 'checkbox',
inputOptions: [
{
text: 'Deductions commenced on ' + formatDate(options[3]),
value: options[2],
},
{
text: 'Deductions commenced on ' + formatDate(options[1]),
value: options[0],
}
],
callback: function (result) {
//Pass the selected option into another AJAX method to generate the document else return to the view
if (result != null) {
$.ajax({
type: 'POST',
url: '#Url.Action("DeductionsSheet", "Home")?result=' + result,
contentType: 'application/json; charset=utf-8',
success: function (data) {
if (data.fileName != "") {
//window.location = "#Url.RouteUrl(new { Controller = "Home", Action = "Download" })/?file=" + data.fileName;
window.location = '/Home/ContractSpecificDocuments?file=' + data.fileName;
}
}
});
} else {
return;
};
}
});
}
else {
I'm happy that the Bootbox code works and the value that is passed into the DeductionsSheet ActionResult in the controller so I will jump to this code.
DeductionsSheet method (top of the method)
The value comes into the method as an array which I get through the [0] index.
public ActionResult DeductionsSheet(List<object> result)
{
//BOILER PLATE CODE
/////////////////////////////////////////
XceedDeploymentLicense.SetLicense();
var dateStamp = DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss");
_replacePatterns = new Dictionary<string, string>();
int contractNumber = Convert.ToInt32(Request.Cookies["ContractNumber"].Value);
int contractContibutionHistoryId = Convert.ToInt32(result[0]);
The document is generated
DeductionsSheet method (bottom of the method)
document.SaveAs(DocumentOutputDirectory + #"\RMContributions_" + dateStamp + #".docx");
}
string fileName = "RMContributions_" + dateStamp + #".docx";
return File(new FileStream(DocumentOutputDirectory + #"\RMContributions_" + dateStamp + #".docx", FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose), "application/vnd.openxmlformats-officedocument.wordprocessingml.document", fileName);
The ActionResult finishes and returns back to the AJAX method, shown below here (same code as in Razor View block above).
success: function (data) {
if (data.fileName != "") {
//window.location = "#Url.RouteUrl(new { Controller = "Home", Action = "Download" })/?file=" + data.fileName;
window.location = '/Home/ContractSpecificDocuments?file=' + data.fileName;
}
}
I'm not sure if it's a data-type parse problem, or if something needs serializing, I'm open to all suggestions.
Now I'm not committed to this approach so if anyone can suggest an alternative as long as it works I'm happy. Ideally I would call the AJAX method and pass the data into the controller and then not return to the AJAX method, but I've not found a way of doing this.
I did try a simpler alternative whereby in the Bootbox callback I trigger the DeductionsSheet ActionResult using a jQuery trigger event but with this approach I couldn't get the data to pass into the controller.
Alternative approach using trigger event
callback: function (result) {
//Pass the selected option into another AJAX method to generate the document else return to the view
if (result != null) {
$('#deductionsSheet').trigger('click', [result]);
} else {
return;
};
}
Thanks for your help.

invoke api from ajax asp.net

I have a web Api that allow me to add a multiple Image with with another parameter
(place_Id , is_Main)
I use this code bellow to upload the image
[Route("api/Image")]
[HttpPost]
public async Task<IHttpActionResult> PostImage()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/Images/Places");
var provider = new CustomMultipartFormDataStreamProvider(root);
try
{
// Read the form data.
var task = await Request.Content.ReadAsMultipartAsync(provider).ContinueWith<IEnumerable<FileDesc>>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
var fileInfo = provider.FileData.Select(d =>
{
var info = new FileInfo(d.LocalFileName);
//return new FileDesc(info.Name);
return new FileDesc(info.Name);
});
return fileInfo;
});
int placeId = int.Parse(provider.FormData["placeId"]);
bool isMain = Convert.ToBoolean(provider.FormData["isMain"]);
var listOfAttchments = task.ToList();
string attachmentsPath = Request.RequestUri.Scheme +
System.Uri.SchemeDelimiter +
Request.RequestUri.Host +
(Request.RequestUri.IsDefaultPort ? "" : ":" + Request.RequestUri.Port) +
"/Images/Places/";
Images i = new Images();
if (listOfAttchments.Count > 0)
{
foreach (var item in listOfAttchments)
{
i.FileLocation = item.name;
i.FromUser = true;
i.TableName = "Places";
i.IsMain = isMain;
i.TableId = placeId;
db.Images.Add(i);
}
}
await db.SaveChangesAsync();
return Ok(new
{
result = true,
listAttachmment = listOfAttchments
}
);
}
catch (System.Exception e)
{
return BadRequest(e.StackTrace + "\nTest" + e.Data + "\nTest" + e.InnerException + "\nTest" + e.Message + "\nTest" + e.Source + "\nTest" + e.TargetSite);
}
}
The previous api is in another domain,
and I have a web forms application , that want to upload image from it, using the previous api
var data = new FormData();
jQuery.each(jQuery('#file')[0].files, function (i, file) {
data.append(" placeId: 7, isMain: 1");
data.append('image1' + i, file);
});
$("#btn2").click(function () {
jQuery.ajax({
url: '{url}/api/api/Image',
data: data,
contentType: false,
processData: false,
method: 'POST',
type: 'POST', // For jQuery < 1.9
success: function (data) {
alert(data);
}
});
});
I used the above code to invoke it, but I have a problem,
can you help me
Please ensure that you are not receiving XSS Error message (normally other domains are configured that you will not be able to trigger calls from a different domain addresses).
In the below code, i am not sure why do you have /api/api
url: '{url}/api/api/Image',
Please post us the error message you are receiving

Stop Execution of HTTPPOST ActionMethod in MVC

I have webApplication in MVC Framework..
i have situation where i have to provide user to export some data to csv file
for that i have written following code ..
[HttpPost]
public ActionResult ExportReportToFile(ReportCriteriaViewModels posdata, string name)
{
string strQuery = GetReportQuery(posdata, name);
IEnumerable<REP_MM_DEMOGRAPHIC_CC> lstDemographics = ReportDataAccess.GetReportData<REP_MM_DEMOGRAPHIC_CC>(strQuery);
if (lstDemographics.Count() > 0)
return new CsvActionResult<REP_MM_DEMOGRAPHIC_CC>(lstDemographics.ToList(), "LISDataExport.csv");
else
return view(posdata);
}
Above code works fine... if in listResult Count is Greater than zero then i returns File to download.. but if i dont get any records in lstDemographics then i returns view..
My problem is when i dont get any result in lstDemographics, i dont want to return view coz it refreshes whole view.. so is there any way where we can stop execution of Action Method and browser doesn't refresh the view and stay as it is..
Thanks..
You will have to make an AJAX call to stop page refresh.
To achieve file export, we actually broke the process in two AJAX calls. First call sends a request to server, server prepare a file and store it in temp table. Server return the file name to AJAX call if there is data. If no data or error, it return a JSON result to indicate a failure.
On success, view make another AJAX request to download the file passing file name.
Something like this:
[Audit(ActionName = "ExportDriverFile")]
public ActionResult ExportDriverFile(int searchId, string exportType, string exportFormat)
{
var user = GetUser();
var driverSearchCriteria = driverSearchCriteriaService.GetDriverSearchCriteria(searchId);
var fileName = exportType + "_" + driverSearchCriteria.SearchType + "_" + User.Identity.Name.Split('#')[0] + "." + exportFormat;
//TempData["ExportBytes_" + fileName] = null;
_searchService.DeleteTempStore(searchId);
var exportBytes = exportService.ExportDriverFileStream(driverSearchCriteria, searchId, exportType, exportFormat, user.DownloadCode, user.OrganizationId);
if (exportBytes != null)
{
var tempStore = new TempStore
{
SearchId = searchId,
FileName = fileName,
ExportFormat = exportFormat,
ExportType = exportType,
DataAsBlob = exportBytes
};
var obj = _searchService.AddTempStore(tempStore);
return Json(fileName);
}
else
{
return Json("failed");
}
}
[HttpGet]
public ActionResult DownloadStream(string fileName, int searchId)
{
var tempStore = _searchService.GetTempStore(searchId);
var bytes = tempStore.DataAsBlob;
if (bytes != null)
{
var stream = new MemoryStream(bytes);
// TempData["ExportBytes_" + fileName] = null;
_searchService.DeleteTempStore(searchId);
return File(stream, "application/vnd.ms-excel", fileName);
}
_logger.Log("Export/DownloadStream request failed", LogCategory.Error);
return Json("Failed");
}
At client side, we do something like:
function ExportData(exportType, exportFormat) {
var searchId = document.getElementById('hfSelectedDriverId').value;
var model = { searchId: searchId, exportType: exportType, exportFormat: exportFormat };
//$('div[class=ajax_overlay]').remove();
//alert("The file will be downloaded in few minutes..");
$.ajax({
url: '#Url.Action("ExportDriverFile", "Export")',
contentType: 'application/json; charset=utf-8',
type: 'POST',
dataType: 'html',
data: JSON.stringify(model)
})
.success(function (result) {
result = result.toString().replace(/"/gi, "");
if (result == "" || result == "failed")
{
alert("File download failed. Please try again!");
}
else
{
window.location = '/Export/DownloadStream?fileName=' + result+"&searchId="+searchId;
}
})
.error(function (xhr, status) {
//
//alert(status);
});
//$('div[class=ajax_overlay]').remove();
}
You should create javascript function with $.getJSON method.
On controller side you just have to check, if you get data from database then return file path else return message.
Your JS code should be something like this:
$.getJSON(url)
.done(function (data) {
if (data.filePath) // If get data, fill filePath
window.location = data.filePath;
else
alert(data.msg);
});
And from controller you can create a HTTPGET Action method that return JSON data like:
return Json(new { msg = "No data found" }, JsonRequestBehavior.AllowGet);
Based on condition you can simple change msg with filePath.

Calling Json from MVC View

I am not sure what I am doing wrong here.
I have a list where I am creating hyperlinks. I need to point the user to the right page first though so I do a getJson.
I have the following code in my .cshtm file:
$.getJSON('/Home/GetLocType', { "locId": loc.locId },
function(result) {
result = Json.stringify(result);
if(result == '1')
{
$('<li><a id=' + loc.locId + ' href="/DataEntry" rel="external">' + loc.locName + '</a></li>').appendTo("#btnList");
}
else
{
$('<li><a id=' + loc.locId + ' href="/DataEntry/PForm" rel="external">' + loc.locName + '</a></li>').appendTo("#btnList");
}
});
I have the following code in the controller:
private JsonResult GetLocType(int locId)
{
int locationId = Convert.ToInt32(locId);
var result = db.Locations.Where(a => a.LocID == locationId).Select(a => a.TypeId);
return Json(result);
}
The problem I am facing is that the Json method never gets called.
Your controller method is declared as private - As such it would not be accessible.
Change it to public.
Secondly, you should activate allow get - Details on why this is not set by default see - Why is JsonRequestBehavior needed?
public JsonResult GetLocType(int locId)
{
int locationId = Convert.ToInt32(locId);
var result = db.Locations.Where(a => a.LocID == locationId).Select(a => a.TypeId);
return Json(result, JsonRequestBehavior.AllowGet);
}

404 Error When Accessing URL through AJAX

Not sure why, everything seems formatted fine, but I get the HTTP 404 error when attempting to access a function in my controller. Here's the aspx:
function CheckIfPacked() {
if ($("#OrderNumber").val() != "") {
var url = "/Packing/PackageTracking/CheckIfPacked";
$.ajax({
url: url,
cache: false,
data: "orderNumber=" + $("#OrderNumber").val() + "&actionyes=GetSalesOrder()",
success: function (data) {
var domElement = $(data);
if (data != "") {
$('#MessageDiv').append(domElement);
}
});
}
}
And here's the controller:
public Result CheckIfPacked(string orderNumber) {
var mesEntity = new MESEntities();
var packh = from packhead in mesEntity.Packing_Transaction_Headers
where packhead.Order_No_ == orderNumber
select packhead.Completed_by_Packer;
if (packh.First() == 0)
{
return new Result { Success = true, Message = string.Format("You have not finished packing order {0}, are you sure you want to navigate away from this page?", orderNumber) };
}
else
{
return null;
}
}
I think I've just stared at this too long. Thanks.
your method should be static and you should use webmethod attribute for your function :
[WebMethod]
public static Result CheckIfPacked(string orderNumber) {
var mesEntity = new MESEntities();
var packh = from packhead in mesEntity.Packing_Transaction_Headers
where packhead.Order_No_ == orderNumber
select packhead.Completed_by_Packer;
if (packh.First() == 0)
{
return new Result { Success = true, Message = string.Format("You have not finished packing order {0}, are you sure you want to navigate away from this page?", orderNumber) };
}
else
{
return null;
}
}

Categories