Ok, so I have the following Ajax get request going to a [HttpPost] controller method in an ASP.NET MVC 5 application.
The javascript function shown here successfully posts the values to the server side:
<script>
function get_row() {
var one = document.getElementById("data_table").rows[1].cells[0].innerHTML;
var two = document.getElementById("data_table").rows[1].cells[1].innerHTML;
var result = one + "," + two;
//var result = {};
//result.one = document.getElementById("data_table").rows[1].cells[0].innerHTML;
//result.two = document.getElementById("data_table").rows[1].cells[1].innerHTML;
if (result != null) {
$.ajax({
type: 'get',
url: "/Manage/CreateGroupRoleRestriction",
//url: '#Url.RouteUrl(new{ action= "CreateGroupRoleRestriction", controller= "Manage", one = "one", two = "two"})',,
data: { one, two },
//params: { one, two }
/*dataType: String,*/
//success: alert(result)
});
}
else {
alert("Error");
}
}
</script>
However, the issue is that the string values will not post to the Action Result, see below.
The values "one" and "two" are null.
[Authorize(Roles = "Admin")]
[HttpPost]
[Route("/Manage/CreateGroupRoleRestriction?{one,two}")]
[ValidateAntiForgeryToken]
public ActionResult CreateGroupRoleRestriction(FormCollection formCollection, string message2, string one, string two)
{
UserDataBusinessLayer userDataBusinessLayer = new UserDataBusinessLayer();
userDataBusinessLayer.Restrict1(message2);
UserDataBusinessLayer userDataBusinessLayer2 = new UserDataBusinessLayer();
userDataBusinessLayer2.Restrict2();
try
{
UserData userData = new UserData();
TryUpdateModel(userData);
if (ModelState.IsValid)
{
userData.RoleName = formCollection["RoleName"];
UserDataBusinessLayer userDataBusinessLayer3 = new UserDataBusinessLayer();
userDataBusinessLayer3.CreateGroupRestriction(userData, message2, one.ToString(), two.ToString());
return RedirectToAction("CreateGroupRoleRestriction");
}
else
{
userData.RoleName = formCollection["RoleName"];
UserDataBusinessLayer userDataBusinessLayer4 = new UserDataBusinessLayer();
userDataBusinessLayer4.CreateGroupRestriction(userData, message2, one.ToString(), two.ToString());
return RedirectToAction("CreateGroupRoleRestriction");
}
}
catch (Exception ex)
{
Logger.Log(ex);
return RedirectToAction("CreateGroupRoleRestriction");
}
}
Please try changing 'type' in ajax to 'post'.
type: 'post'
My Ajax method is calling action method in controller, and succeeding in the call return. However the object I am passing is always null.
I have read many, maybe not all as there are quite a few, similar questions. I have tried different things, such as different variations of removing dataType and contentType from the ajax function. I have set break points in the action and set alerts in scripts to verify the object is not null before sending to the JsonResult Action. I have verified that data from the Action method is reaching the succeeded section of the ajax function.
So Here is the scenario: I have an MVC Core 2.2 index page. I added a search textbox. everything works correctly If I block JS in the browser, So I know the HTML is correct. But I wanted to give an Ajax option for a "more pleasant" user experience. I actually did get the ajax to work on simple hard coded strings. But now for some reason the passed in object is null.
Lets start with the view's script:
//This is the Object I want passed through Ajax
//class pageValues {
// constructor(){
// this.sortColumn = $("#inpSortColumn").val();
// this.sortOrder = $("#inpSortOrder").val();
// this.filter = $("#Filter").val();
// this.message = "";
// this.currentPage = $("#inpCurrentPage").val();
// this.recordsPerPage = $("#inpPageSize").val();
// this.recordCount = 0;
// }
//}
// I also tried as a simple variable without a constructor and added
// default values incase undefined values were causing issues
var pageValues = {
sortColumn: ($("#inpSortColumn").val() == undefined ) ? "LastName" : $("#inpSortColumn").val(),
sortOrder: ($("#inpSortOrder").val() == undefined ) ? "ASC" : $("#inpSortOrder").val(),
filter: ($("#Filter").val() == undefined ) ? "" : $("#Filter").val(),
message: ($("#inpMessage").val() == undefined ) ? "" : $("#inpMessage").val(),
currentPage: ($("#inpCurrentPage").val() == undefined) ? 1: $("#inpCurrentPage").val(),
recordsPerPage: ($("#inpPageSize").val() == undefined) ? 5 : $("#inpPageSize").val(),
totalRecords: ($("#inpTotalRecords").val() == undefined ) ? 0 : $("#inpTotalRecords").val()
};
$(document).ready(function () {
// If we are here, the browser allows JS
// So, replace the submit buttons with Ajax functions
ReplaceHtml();
});
function ReplaceHtml() {
// Search Button
var divSearch = $("#divSearchBtn");
divSearch.hide();
divSearch.empty();
divSearch.append('<button id="btnAjaxSearch" type="button" ' +
'class="" onclick="RequestRecords();">Search</button>');
divSearch.show();
}
// Here we call the Ajax function passing the data object and the callback function
function RequestRecords() {
alert($("#Filter").val()); // This is just to Verify value is present
AjaxCallForRecords(pageValues, ReturnedData);
}
// This is the callback function
function ReturnedData(data) {
// This verifies we hit the callback
alert("inside ajax callback");
// The Verification that the Object returned is valid.
// The problem appeared here,
// The firstname was always the same no matter the Search Filter.
// Telling me the object on the server side receiving the 'pageValues'
// had been recreated due to being null.
alert(data.users[0].firstName);
}
// Of course, here is the ajax function
// I have played around with data and content settings
// When I changed those I got 'Response Errors' but could never get the ResponseText
function AjaxCallForRecords(dataToSend, callback) {
console.log(dataToSend); // This prove Data is here
$.ajax({
type: "GET",
url: '#Url.Action("Index_Ajax","ApplicationUsers")',
data: JSON.stringify(dataToSend),
dataType: "json",
contentType: "application/json",
success: function (data) { callback(data); },
error: function (data) { alert("Error. ResponseText: " + data.responseText); }
});
}
</script>
Ok, Now to the Controller:
public JsonResult Index_Ajax([FromBody] UsersCodeAndClasses.PageValues pageValues)
{
// A break point here reveals 'pageValues' is always null - this is the problem.....
// In the GetFilteredData function I do create a new 'pageValues' object if null
// So my Search 'Filter' will always be empty, and I will always get all the records.
// Get Records
List<InputUser> users = _usersCode.GetFilteredData(pageValues);
// The next block of code assembles the data to return to the view
// Again the 'pageValues' is null because that is what gets passed in, or rather, never assigned
//Build Return Data
UsersCodeAndClasses.AjaxReturnData data = new UsersCodeAndClasses.AjaxReturnData()
{
pageValues = pageValues,
users = users
};
return Json(data);
}
And Finally, The Server side 'pageValues' declaration:
public class PageValues
{
// Class used to pass page and sorting information to Ajax Call
public string sortColumn { get; set; } = "LastName";
public string sortOrder { get; set; } = "ASC";
public string filter { get; set; } = "";
public string message { get; set; } = "";
public int currentPage { get; set; } = 1;
public int recordsPerPage { get; set; } = 5;
public int recordCount { get; set; }
}
public class AjaxReturnData
{
// Class is used to pass multiple data to the Ajax Call
public PageValues pageValues { get; set; }
public List<InputUser> users { get; set; }
}
So, I am expecting data to be passed, I just do not know why the server is not assigning the data. I am new at this and could use an experienced eye.
Thanks
Simply change your type from GET to POST in Ajax call.
I spent some more time researching everything about ajax return values and classes.
Ultimately, my class was malformed, once I changed that it started working. I also changed the type to POST, I did not want to use POST just to read records. But I am sending a lot of data keeping up with search, pagination and sorting.
The below code works though I feel like it is very verbose and some parts may be unnecessary. Hope it helps someone, and please feel free to comment and help me out on things that could help others.
<script>
// Class to use for ajax data
class pageValues {
constructor(){
this.sortColumn = ($("#inpSortColumn").val() == undefined) ? "LastName" : $("#inpSortColumn").val();
this.sortOrder = ($("#inpSortOrder").val() == undefined) ? "ASC" : $("#inpSortOrder").val();
this.filter = ($("#Filter").val() == undefined) ? "" : $("#Filter").val();
this.message = ($("#inpMessage").val() == undefined) ? "" : $("#inpMessage").val();
this.currentPage = ($("#inpCurrentPage").val() == undefined) ? 1 : $("#inpCurrentPage").val();
this.recordsPerPage = ($("#inpPageSize").val() == undefined) ? 5 : $("#inpPageSize").val();
this.totalRecords= ($("#inpTotalRecords").val() == undefined) ? 0 : $("#inpTotalRecords").val();
}
get SortColumn() { return this.sortColumn; }
set SortColumn(value) { this.sortColumn = value; }
get SortOrder() { return this.sortOrder; }
set SortOrder(value) { this.sortOrder = value;}
get Filter() { return this.filter; }
set Filter(value) { this.filter = value; }
get Message() { return this.message; }
set Message(value) { this.message = value; }
get CurrentPage() { return this.currentPage; }
set CurrentPage(value) { this.currentPage = value; }
get RecordsPerPage() { return this.recordsPerPage; }
set RecordsPerPage(value) { this.recordsPerPage = value; }
get TotalRecords() { return this.totalRecords; }
set TotalRecords(value) { this.totalRecords = value; }
}
$(document).ready(function () {
// If we are here, the browser allows JS
// So, replace the submit buttons with Ajax functions
ReplaceHtml();
});
function ReplaceHtml() {
// Search Button
var divSearch = $("#divSearchBtn");
divSearch.hide();
divSearch.empty();
divSearch.append('<button id="btnAjaxSearch" type="button" ' +
'class="" onclick="RequestRecords();">Search</button>');
divSearch.show();
}
// Here we call the Ajax function passing the data object and the callback function
function RequestRecords() {
alert($("#Filter").val()); // This is just to Verify value is present
AjaxCallForRecords(new pageValues(), ReturnedData);
}
// This is the callback funtion
function ReturnedData(data) {
// The verification we hit the callback
alert("inside ajax callback");
alert(data.users[0].firstName);
}
// Ajax function
function AjaxCallForRecords(dataToSend, callback) {
console.log(dataToSend);
$.ajax({
type: "POST",
url: '#Url.Action("Index_Ajax","ApplicationUsers")',
data: JSON.stringify(dataToSend),
dataType: "json",
contentType: "application/json",
success: function (data) { callback(data); },
error: function (data) { alert("Error. ResponseText: " + data.responseText); }
});
}
</script>
A public action method 'AddPromoCode' was not found on controller
'Flazingo.Controllers.PositionController'. at
System.Web.Mvc.Controller.HandleUnknownAction(String actionName) at
System.Web.Mvc.Controller.ExecuteCore() at
System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
at
System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.b__5()
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass1.b__0()
at System.Web.Mvc.MvcHandler.<>c__DisplayClasse.b__d() at
System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step,
Boolean& completedSynchronously)
Here is the ajax call:
$.ajax({
url: '/Position/AddPromoCode',
type: 'POST',
dataType: "json",
contentType: "application/json; charset:utf-8",
data: ko.toJSON(viewModel),
success: function(result){
if(result.TypeId == 1){
viewModel.promoOff(viewModel.grandTotal() * (result.Value / 100));
viewModel.PromoCodes.push(promoCode + ": "+ result.Value + "% off");
}else{
viewModel.PromoCodes.push(promoCode + ": "+ result.Value + "days free");
}
},
error: function (xmlHttpRequest, textStatus, errorThrown) {
var errorData = jQuery.parseJSON(xmlHttpRequest.responseText);
var errorMessages = [];
//this ugly loop is because List<> is serialized to an object instead of an array
for (var key in errorData)
{
errorMessages.push(errorData[key]);
}
toastr.error(errorMessages.join("<br />"), 'Uh oh');
}
});
EDIT: POST METHOD
[HttpPost]
public ActionResult AddPromoCode(PaymentViewModel model)
{
List<string> errors = new List<string>();
try
{
var position = db.Positions.SingleOrDefault(x => x.PositionId == model.PositionId);
if (position != null)
{
var promo = db.Promotions.SingleOrDefault(x => x.Code.ToLower() == model.PromoCode.ToLower() && x.IsUserEntered);
if (promo != null)
{
var promoUsage = db.PromoCodeUsages.SingleOrDefault(x => x.PromotionId == promo.PromotionId && x.ClientId == position.Client.Id);
int used = 0;
if (promoUsage != null)
{
used = promoUsage.Used;
}
if (used < promo.QuantityUsage)
{
if (DateTime.Today >= promo.StartDate && DateTime.Today <= promo.EndDate)
{
position.PromoCodes.Add(new PositionPromoCode
{
PromotionId = promo.PromotionId
});
var clientPC = position.Client.PromoCodes.SingleOrDefault(x => x.PromotionId == promo.PromotionId);
if (clientPC != null)
{
clientPC.Used = used + 1;
}
else
{
clientPC = new PromoCodeUsage()
{
PromotionId = promo.PromotionId,
Used = used + 1
};
position.Client.PromoCodes.Add(clientPC);
}
db.SaveChanges();
Response.StatusCode = (int)HttpStatusCode.OK;
return Json(new { Value = promo.Value, TypeId = promo.PromotionTypeId });
}
else
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
errors.Add("Sorry seems like this promotion code has expired");
db.SaveChanges();
return Json(errors);
}
}
else
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
errors.Add("Sorry seems like you have already used this code, or its not applicable anymore!");
db.SaveChanges();
return Json(errors);
}
}
else
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
errors.Add(string.Format("Sorry we don't have '{0}' promocode in our system!", model.PromoCode));
db.SaveChanges();
return Json(errors);
}
}
Response.StatusCode = (int)HttpStatusCode.BadRequest;
errors.Add("We coudn't find this position in our system!");
db.SaveChanges();
return Json(errors);
}
catch (Exception ex)
{
logger.Log(LogLevel.Error, string.Format("{0} \n {1}", ex.Message, ex.StackTrace));
Response.StatusCode = (int)HttpStatusCode.BadRequest;
errors.Add("Sorry there was internal errors, flazingo.com has been notified.");
return Json(errors);
}
}
JSON Structure:
{"PositionTitle":"Testing B Syntax Error","PromoCode":"FREECB","FirstName":null,"LastName":null,"Address":null,"SuiteNumber":null,"PhoneNumber":null,"City":null,"State":null,"ZipCode":null,"CreditCardNumber":null,"ExperationMonth":null,"ExperationYear":null,"CCV":null,"ClientId":2,"CustomerProfileId":64277420,"PositionId":78,"EmailAddress":"jmogera#gmail.com","Years":[{"Selected":false,"Text":"2013","Value":"2013"},{"Selected":false,"Text":"2014","Value":"2014"},{"Selected":false,"Text":"2015","Value":"2015"},{"Selected":false,"Text":"2016","Value":"2016"},{"Selected":false,"Text":"2017","Value":"2017"},{"Selected":false,"Text":"2018","Value":"2018"},{"Selected":false,"Text":"2019","Value":"2019"},{"Selected":false,"Text":"2020","Value":"2020"},{"Selected":false,"Text":"2021","Value":"2021"},{"Selected":false,"Text":"2022","Value":"2022"}],"MonthList":[{"Selected":false,"Text":"Jan","Value":"01"},{"Selected":false,"Text":"Feb","Value":"02"},{"Selected":false,"Text":"Mar","Value":"03"},{"Selected":false,"Text":"Apr","Value":"04"},{"Selected":false,"Text":"May","Value":"05"},{"Selected":false,"Text":"Jun","Value":"06"},{"Selected":false,"Text":"Jul","Value":"07"},{"Selected":false,"Text":"Aug","Value":"08"},{"Selected":false,"Text":"Sep","Value":"09"},{"Selected":false,"Text":"Oct","Value":"10"},{"Selected":false,"Text":"Nov","Value":"11"},{"Selected":false,"Text":"Dec","Value":"12"}],"IsAddingNewCard":false,"HaveCardOnFile":true,"AddOns":[{"PositionId":78,"ProductId":2,"Description":"The heart and soul, we take you through the hiring process, start to finish and give you every tool you need to make a great hire along the way.","Price":39,"HasAdded":true,"AutoRenew":true,"Name":"Complete Hiring System","AddOnId":122}],"CreditCards":[{"CreditCardId":16,"LastFour":"1060","HolderName":"Barrett Kuethen","ExpDate":"/Date(1422766800000)/","IsDefault":true}],"CardOnFile":{"CreditCardId":16,"LastFour":"1060","HolderName":"Barrett Kuethen","ExpDate":"/Date(1422766800000)/","IsDefault":true},"PromoCodes":[],"__ko_mapping__":{"CardOnFile":{},"ignore":[],"include":["_destroy"],"copy":[],"observe":[],"mappedProperties":{"PositionTitle":true,"PromoCode":true,"FirstName":true,"LastName":true,"Address":true,"SuiteNumber":true,"PhoneNumber":true,"City":true,"State":true,"ZipCode":true,"CreditCardNumber":true,"ExperationMonth":true,"ExperationYear":true,"CCV":true,"ClientId":true,"CustomerProfileId":true,"PositionId":true,"EmailAddress":true,"Years[0].Selected":true,"Years[0].Text":true,"Years[0].Value":true,"Years[1].Selected":true,"Years[1].Text":true,"Years[1].Value":true,"Years[2].Selected":true,"Years[2].Text":true,"Years[2].Value":true,"Years[3].Selected":true,"Years[3].Text":true,"Years[3].Value":true,"Years[4].Selected":true,"Years[4].Text":true,"Years[4].Value":true,"Years[5].Selected":true,"Years[5].Text":true,"Years[5].Value":true,"Years[6].Selected":true,"Years[6].Text":true,"Years[6].Value":true,"Years[7].Selected":true,"Years[7].Text":true,"Years[7].Value":true,"Years[8].Selected":true,"Years[8].Text":true,"Years[8].Value":true,"Years[9].Selected":true,"Years[9].Text":true,"Years[9].Value":true,"Years":true,"MonthList[0].Selected":true,"MonthList[0].Text":true,"MonthList[0].Value":true,"MonthList[1].Selected":true,"MonthList[1].Text":true,"MonthList[1].Value":true,"MonthList[2].Selected":true,"MonthList[2].Text":true,"MonthList[2].Value":true,"MonthList[3].Selected":true,"MonthList[3].Text":true,"MonthList[3].Value":true,"MonthList[4].Selected":true,"MonthList[4].Text":true,"MonthList[4].Value":true,"MonthList[5].Selected":true,"MonthList[5].Text":true,"MonthList[5].Value":true,"MonthList[6].Selected":true,"MonthList[6].Text":true,"MonthList[6].Value":true,"MonthList[7].Selected":true,"MonthList[7].Text":true,"MonthList[7].Value":true,"MonthList[8].Selected":true,"MonthList[8].Text":true,"MonthList[8].Value":true,"MonthList[9].Selected":true,"MonthList[9].Text":true,"MonthList[9].Value":true,"MonthList[10].Selected":true,"MonthList[10].Text":true,"MonthList[10].Value":true,"MonthList[11].Selected":true,"MonthList[11].Text":true,"MonthList[11].Value":true,"MonthList":true,"IsAddingNewCard":true,"HaveCardOnFile":true,"AddOns[0].PositionId":true,"AddOns[0].ProductId":true,"AddOns[0].Description":true,"AddOns[0].Price":true,"AddOns[0].HasAdded":true,"AddOns[0].AutoRenew":true,"AddOns[0].Name":true,"AddOns[0].AddOnId":true,"AddOns":true,"CreditCards[0].CreditCardId":true,"CreditCards[0].LastFour":true,"CreditCards[0].HolderName":true,"CreditCards[0].ExpDate":true,"CreditCards[0].IsDefault":true,"CreditCards":true,"CardOnFile":true,"PromoCodes":true},"copiedProperties":{}},"addNewCreditCardValidationGroup":{"FirstName":null,"LastName":null,"Address":null,"City":null,"State":null,"CreditCardNumber":null,"ExperationMonth":null,"ExperationYear":null,"CCV":null,"errors":[]},"promoOff":0,"grandTotal":39}
Note: the call is made within a knockout click function.
Note: I have created another issue, thinking it was client side issue. This is related.
Uncaught SyntaxError: Unexpected token B on live but not local server
It can't find AddPromoCode so, there seems a url problem. You should add the application name at ajax code in server like url: '/ApplicationName/Position/AddPromoCode'.
You can also do this with url.action html helper, like:
// In layout or view:
#Html.Hidden("urlPrefix", Url.Action("", ""))
// In Javascript
var baseUrl = $("input#urlPrefix").val();
$.ajax({ url: baseUrl + '/Position/AddPromoCode', ...
The part Url.Action("", "") creates '/ApplicationName' part dynamically.