Cannot pass data from JSONResult back to handler using ajax - c#

I would like to have the following workflow:
Ajax call to page model handler to trigger database and creation of some viewmodels and return the result as JSONResult
Pass back the result of step 1 to another handler in order to reload a partial view
Between step 1 and 2 there is some data handling using JS. But this is out of scope, since the data is only read and not changed.
Step 1 is working fine. But step 2 does not. Here is a short code example:
Step 1:
Ajax:
var searchArea = parseFloat(document.getElementById('txtSearchArea').value);
var latitude = parseFloat(document.getElementById('hiddenLat').value);
var longitude = parseFloat(document.getElementById('hiddenLong').value);
var requestData = JSON.stringify({'latitude': latitude, 'longitude': longitude, 'searchArea': searchArea});
$.ajax({
type: "POST",
url: '#Request.Scheme://#Request.Host#Request.Path?handler=GetCompaniesWithinSearchArea',
headers: { "RequestVerificationToken": $('input[name="__RequestVerificationToken"]').val() },
contentType: "application/json; charset=utf-8",
dataType: "json",
data: requestData
}).fail(function (x) {
alert('Error fetching companies in search area.');
}).done(function (result) {
reloadSearchResultPartialView(result);
});
C#:
public async Task<JsonResult> OnPostGetCompaniesWithinSearchAreaAsync([FromBody] LatitudeLongitudeSearchArea latitudeLongitudeSearchArea)
{
var latitude = latitudeLongitudeSearchArea.Latitude;
var longitude = latitudeLongitudeSearchArea.Longitude;
var searchArea = latitudeLongitudeSearchArea.SearchArea;
var idsOfcompaniesInArea = await _companiesService.GetIdsOfCompaniesWithinSearchAreaAsync(latitude, longitude, searchArea));
var companyInSearchAreaVMs = await _companiesInSearchAreaViewModelFactory.Create(idsOfcompaniesInArea);
return new JsonResult(companyInSearchAreaVMs);
}
with _companiesInSearchAreaViewModelFactory.Create returning List<CompanyInSearchAreaViewModel>.
Step 2:
Normally I use the javascript load(...) method to invoke reloading of a partial view.
So I was trying to use this approach again.
JS:
function reloadSearchResultPartialView(searchResult) {
$('#divSearchResult').load('#Request.Scheme://#Request.Host#Request.Path?handler=ReloadCompaniesInAreaList&companyInSearchAreaViewModels=' + searchResult);
}
where searchResult is the response result of step 1. Data have not been changed between step 1 and 2.
C# - this is the handler for reloading the partial view.
public PartialViewResult OnGetReloadCompaniesInAreaList(List<CompanyInSearchAreaViewModel> companyInSearchAreaViewModels)
{
var result = new PartialViewResult
{
ViewName = "_CompaniesInAreaList",
ViewData = new ViewDataDictionary<IList<CompanyInSearchAreaViewModel>>(ViewData, companyInSearchAreaViewModels)
};
return result;
}
The handler for reloading the partial view is called, but the list of viewmodels is always an empty list.
Hopefully someone can point me in the right direction.
Thanks in advance!

Change this:
function reloadSearchResultPartialView(searchResult) {
$('#divSearchResult').load('#Request.Scheme://#Request.Host#Request.Path?handler=ReloadCompaniesInAreaList&companyInSearchAreaViewModels=' + searchResult);
}
to
function reloadSearchResultPartialView(searchResult) {
var arrStr = encodeURIComponent(JSON.stringify(searchResult));
$('#divSearchResult').load('#Request.Scheme://#Request.Host#Request.Path?handler=ReloadCompaniesInAreaList&companyInSearchAreaViewModels='+arrStr);
}
And in PageModel
public PartialViewResult OnGetReloadCompaniesInAreaList(List<CompanyInSearchAreaViewModel> companyInSearchAreaViewModels)
{
var result = new PartialViewResult
{
ViewName = "_CompaniesInAreaList",
ViewData = new ViewDataDictionary<IList<CompanyInSearchAreaViewModel>>(ViewData, companyInSearchAreaViewModels)
};
return result;
}
to
public PartialViewResult OnGetReloadCompaniesInAreaList(string companyInSearchAreaViewModels)
{
List<CompanyInSearchAreaViewModel> data = JsonConvert.DeserializeObject<List<CompanyInSearchAreaViewModel>>(companyInSearchAreaViewModels);
var result = new PartialViewResult
{
ViewName = "_CompaniesInAreaList",
ViewData = new ViewDataDictionary<IList<CompanyInSearchAreaViewModel>>(ViewData, data)
};
return result;
}

Why it happens
but the list of viewmodels is always an empty list.
That's because your server expects a querystring like this:
handler=ReloadCompaniesInAreaList
&[0].prop1= 11
&[0].prop2=12
...
&[1].prop1=21
&[1].prop2=22
...
However, by using
$('#divSearchResult').load('#Request.Scheme://#Request.Host#Request.Path?handler=ReloadCompaniesInAreaList&companyInSearchAreaViewModels=' + searchResult);
You're sending the querystring like:
handler=ReloadCompaniesInAreaList&companyInSearchAreaViewModels=[object
How to Fix
There're several ways to fix that problem.
The easiest way is to change the json array to a form-urlencoded string, for example, change your reloadSearchResultPartialView()function as below:
function reloadSearchResultPartialView(searchResult) {
//$('#divSearchResult').load('#Request.Scheme://#Request.Host#Request.Path?handler=ReloadCompaniesInAreaList&companyInSearchAreaViewModels=' + searchResult);
$('#divSearchResult').load('#Request.Path?handler=ReloadCompaniesInAreaList', $.param(createFormUrlEncodedPayload("",searchResult)));
}
// my helper function that recursively convert a plain js obj to a form-urlencoded string
function createFormUrlEncodedPayload(name,o){
var payload = {};
function _objectNotNull(value){ return value !== null && typeof value === "object"; }
function _create(prefix,obj) {
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var key =prefix?
(isNaN(prop)? key = prefix + "." + prop : key = prefix + ".[" + prop + "]") :
(isNaN(prop)? key = prop : key = "[" + prop + "]");
var value = obj[prop];
if(_objectNotNull(value))
_create(key, value);
else
payload[key]=value;
}
}
};
_create(name,o);
return payload;
}
Or as an alternative, you can make your server to receive HTTP POST request instead GET,
public PartialViewResult OnPostReloadCompaniesInAreaList([FormBody]List<CompanyInSearchAreaViewModel> companyInSearchAreaViewModels)
{
...
}
and then replace the $.load() to :
$.ajax({
url: '#Request.Path?handler=ReloadCompaniesInAreaList',
method:'POST',
contentType:"application/json",
data: searchResult,
success: function(resp){
$('#divSearchResult').html(resp)
},
});

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.

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.

MVC Parameter Binding from JSON into multiple parameters

I'm receiving JSON data from this method:
function CallAPI(controllerName, functionName, sendData, callback) {
var ajax = new XMLHttpRequest();
ajax.onreadystatechange = function () {
if (ajax.readyState == 4) {
if (ajax.status == 200) {
var response = JSON.parse(ajax.responseText);
callback(response);
}
else {
callback();
}
}
}
ajax.open("POST", "http://" + window.location.host + "/API/" + controllerName + "/" + functionName, true);
ajax.setRequestHeader('Content-Type', 'application/json');
ajax.send(JSON.stringify(sendData));
}
function someRequest() {
var data = {};
data.userID = 1;
data.someValue = 20;
CallAPI("Home", "Add", data, function (response) {
if(response) {
alert(response.Message); //This property always exist
}
else {
alert("internet/connection error.");
}
});
}
And my Controller is:
[System.Web.Http.HttpPost]
public HttpResponseMessage Add(int userID, int someValue)
{
//Here!
return Request.CreateResponse(HttpStatusCode.OK, new GenericResponseAPI() { Status = false, Message = "Generic Message." });
}
I can create a model and MVC would bind it accordingly, but I have so many of these requests that I don't want to create various models (or a big one containing all of the properties), but instead have these simple functions with basic type parameters like this.
What do I need to do to be able to work with the controller function above, without changing the content-type of the incoming message?
Edit: I found a workaround with this:
[System.Web.Http.HttpPost]
public HttpResponseMessage Add(JObject jObject)
{
int userID = (int)jObject.getValue("userID").ToObject(typeof(Int32));
int someValue = (int)jObject.getValue("someValue").ToObject(typeof(Int32));
return Request.CreateResponse(HttpStatusCode.OK);
}
I imagine the reason is that your function signature is:
function CallAPI(controllerName, functionName, sendData, callback)
and you're calling
CallAPI("Home", "Add", function (response) { ... })
So you're never actually sending data in the format MVC is expecting.
I'd double check this by using something like Fiddler and by debugging your JavaScript code using your browsers dev-tools.

Calling C# method from javascript in MVC4

i'm trying to call a c# method from javascript but nothing works. What this have to do is that i call the javascript after user press enter, then i call the method in c#, the API try to find the city, then i show the temperature of the city that he typed. But i tried a lot of thing, but nothing worked.
the code:
My JavaScript:
function enterpressalert(e, textarea) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 13) { //Enter keycode
var tb = $('#search-bar').val();
$.ajax({
url: '#Url.Action("getWeather", "IndexController")',
type: 'GET',
dataType: 'text',
// we set cache: false because GET requests are often cached by browsers
// IE is particularly aggressive in that respect
cache: false,
data: { city: tb },
success: function () {
$('#search-bar').val() = 'funcionou';
}
});
}
}
My c# method that i'm trying to call:
[WebMethod]
public LocalWeather getWeather(string city)
{
LocalWeatherInput input = new LocalWeatherInput();
input.query = city;
input.num_of_days = "5";
input.format = "JSON";
FreeAPI api = new FreeAPI();
LocalWeather localWeather = api.GetLocalWeather(input);
return localWeather;
}
thanks.
Place a break point in getWeather() function and try this:
[HttpGet]
public LocalWeather getWeather(string city)
{
LocalWeatherInput input = new LocalWeatherInput();
input.query = city;
input.num_of_days = "5";
input.format = "JSON";
FreeAPI api = new FreeAPI();
LocalWeather localWeather = api.GetLocalWeather(input);
return localWeather;
}
and also check whether your are using url routing in global.asax

Return a value from a WebMethod based on conditions provided in the WebMethod

I have a jQuery Ajax WebMethod call as shown below:
<script type="text/javascript">
$(document).ready(function () {
$("#btnsubmit").click(function () {
var arr = new Array();
arr.push($("#control1").val()); arr.push($("#control2").val()); arr.push($("#control13 option:selected").val()); arr.push($("#control4 option:selected").val()); arr.push($("#control15 option:selected").val());
var requestedData = JSON.stringify(arr);
requestedData = "{'details':'" + requestedData + "'}";
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "EmployeeDemotion.aspx/Save",
data: requestedData,
dataType: "json",
success: function (result) {
//check the value returned from the WebMethod and provide alerts accordingly
},
error: function (result) {
alert("Error");
}
});
});
});
</script>
and, the WebMethod is as below:
[WebMethod(EnableSession = true)]
public static InsertDetails[] Save(string details)
{
DataTable dt = new DataTable(); DataTable dts = new DataTable();
List<InsertDetails> data = new List<InsertDetails>();
JavaScriptSerializer js = new JavaScriptSerializer();
string[] Tags = js.Deserialize<string[]>(details);
object[] obj = new object[8];
obj[0] = Tags[0].ToString(); obj[1] = Tags[1].ToString(); obj[2] = Tags[2].ToString(); obj[3] = Tags[3].ToString();
obj[4] = Tags[4].ToString();
int a = //say condition 1
int b = //say condition 2
if (a< b)
{
//insert into database and set a value which says the insertion has succeeded
}
else
{
//alert that data cannot be inserted
}
return data.ToArray();
}
Now I require a value of any possible type (boolean,array,string,integer or other) to be returned to the ajax method so that the success function in the ajax method alerts the status of insertion (as commented in te snippet) i.e; a value should be returned along with the last statement "return data.ToArray();" to the ajax method.
I do not require the element 'data' to be returned, the value which validates the insertion should be returned either along with 'data' or in any other form.
not sure what you want. Whether you want both the data and the flag to be returned to the client function or simply the flag.
Case 1 : all that you want is to return a message about the action that took place inside save
just change your save method like this
[WebMethod(EnableSession = true)]
public static string Save(string details)
{
string message =string.Empty;
/// rest of the usual code of yours
///
if (a< b)
{
//rest of the code
message = "Insertion Successful";
}
else
{
//rest of the code
message = "Error Occured";
}
}
and in your client side inside the ajax success, simple do this:
success: function (result) {
alert(result.d);
}
Case 2: you want to send data also in case insertion is successful
make a wrapper and append the data and the flag both to it. Serialize it and
then send it to the client side function. i.e
//wrapper class
public class ServiceResponse
{
public bool IsSuccess {get;set;}
public string Message {get;set;}
}
now inside your save do this:
[WebMethod(EnableSession = true)]
public static string Save(string details)
{
ServiceResponse serviceResponse =new ServiceResponse();
/// rest of the usual code of yours
///
if (a< b)
{
//rest of the code
serviceResponse.IsSuccess= true;
serviceResponse.Message = String.Join(",",data.ToArray());
}
else
{
//rest of the code
serviceResponse.IsSuccess = false;
}
return new JavaScriptSerializer().Serialize(serviceResponse);
}
and use it inside your client method like :
success: function (result) {
var jsonData = $.parseJSON(result.d);
if(jsonData.IsSuccess){
alert('success');
grid.data(jsonData.Message);
}
else{
alert('failure');
}
}

Categories