I've the following object which I am passing into an MVC Controller:
this.JsonData = {
"__RequestVerificationToken": $('input[name=__RequestVerificationToken]').val(),
"searchMode": {
"mode": Number(mode.val()),
"pageSize": Number(pagesize.val()) || 5, "pageNumber": Number(pagenumber.val()) || 1,
"sortField": sortfield.val() || "Ref",
"sortDirection": sortdirection.val() || "desc"
},
"searchData": {
"Compare": Number(StdComparison.val()),
"SearchTextFrom": searchText.val(),
"SearchTextTo": searchTextTo.val()
}
This works ok, but I recent requirement has arisen whereby I am looking to encode this object for use with the javascript function Window.location
Suggestions I have used:
how-to-pass-complex-json-object-in-url-using-javascript
window.location + "?SearchCriteria=" + JSON.Stringify(this.JsonData);
Creates the following request:
Controller/Action?SearchCriteria={
"__RequestVerificationToken": "tokenvalue",
"searchMode": {
"mode": 2,
"pageSize": 5,
"pageNumber": 1,
"sortField": "Ref",
"sortDirection": "desc"
},
"searchData": {
"Compare": 1,
"SearchTextFrom": "From A",
"SearchTextTo": "To Z"
}
}
Whereas
window.location + "?SearchCriteria=" + this.JsonData;
Produced the following:
Controller/Action?SearchCriteria=[object%20Object]
With a page not found error on both of the above.
UPDATE:
I have moved forward in the quest for an answer.
OK as per request from helpers, I have included more source code.
I have Three Classes.
public class MainSearch
{
public MainSearch()
{
SearchData searchData = new SearchData();
SearchMode searchMode = new SearchMode();
}
public SearchData searchData { get; set; }
public SearchMode searchMode { get; set; }
public int? page { get; set; }
public object ToPagedListParameters(int pagenumber)
{
searchMode.pageNumber = pagenumber;
return page;
}
public IList<string> ValidationErrorMessages { get; set; }
}
public class SearchData
{
// Fields used for the ticket number search
public int? ticketNumberCompare { get; set; }
public string ticketSearchTextFrom { get; set; }
public string ticketSearchTextTo { get; set; }
}
public class SearchMode
{
public int? mode { get; set; }
public int? pageNumber { get; set; }
public int? pageSize { get; set; }
public string sortDirection { get; set; }
public string sortField { get; set; }
public string userURN { get; set; }
public string __RequestVerificationToken { get; set; }
}
These classes hold the criteria used for searching (SearchData has been truncated)
The following is my controller code:
[HttpGet]
public ActionResult DownloadFileCSV(MainSearch search)
{
string fileName = Server.MapPath("~/Content/Pdf/") + "somefile.pdf";
byte[] fileContents = System.IO.File.ReadAllBytes(fileName);
return File(fileContents, "application/pdf", "result.pdf");
}
And finally, the Ajax call that is made from the cshtml file.
$("#DownloadAttachmentCSV").click(function () {
$.ajax(
{
url: '#Url.Action("DownloadFileCSV", "Home")',
contentType: 'application/json; charset=utf-8',
datatype: 'json',
data: JsonData,
type: "GET",
success: function () {
window.location = '#Url.Action("DownloadFileCSV", "Home")' + '?' + JsonData;
},
error: function (xhr, ajaxOptions, thrownError) {
alert(xhr.status);
alert(thrownError);
}
});
});
Oddly enough, the code above does actually work and the file is downloaded, but here is the problem. The JSON data is not populating the MainSearch variable.
So far the only way I can get the JSON data to populate the c# classes is to change the method to a POST.
Must this be the case?
You cannot pass complex javascript objects to a GET method like that. A GET has not body and to bind to a model, your query string name/value pairs must match the properties of the object your binding to. For example to bind to the mode property of the SearchMode property of MainSearch, you query string would need to include
....&SearchMode.mode=2....
Change the code your using to generate the javascript object to
var data = {
'searchMode.mode': mode.val(),
'searchMode.pageSize': pagesize.val() || 5,
'searchMode.pageNumber': pagenumber.val() || 1,
'searchMode.sortField': sortfield.val() || "Ref",
'searchMode.sortDirection': sortdirection.val() || "desc",
'searchData.SearchTextFrom': StdComparison.val(),
'searchData.Compare': searchText.val(),
'searchData.SearchTextTo': searchTextTo.val(),
}
and the ajax code to
$.ajax(
{
url: '#Url.Action("DownloadFileCSV", "Home")' + '?' + $.param(data),
type: "GET",
success: function () {
Note also the following
There is no point using Number(..) to convert the values to a
number - its all sent across the wire as text
A GET has no body so setting the ajax contentType option is
pointless
Your method does not return json so datatype: 'json' does not make
sense.
Passing the antiforgery token to a GET method is not necessary
Having said that, its unclear what your wanting to do here. The code in your MainSearch search() method never uses any values of the model. And it returns a FileResult which cannot work with an ajax call (but it can work with window.location, it which case the code in the success callback would need to be
var baseUrl = '#Url.Action("DownloadFileCSV", "Home")';
var queryString = $.param(data);
window.location = baseUrl + '?' + queryString;
but then its unclear why you making 2 calls to the method - the ajax call, followed by the redirect (the ajax call does nothing at all).
Anything you use as a query parameter should be properly URI encoded, especially with something like JSON:
window.location + "?SearchCriteria=" + encodeURIComponent(JSON.stringify(this.JsonData));
I'm not sure that the model binder is designed to convert JSON in a GET request into a class instance, but the attempts you've made above are definitely not the way to go.
Related
I´m having issues passing an array of JSON objects to an MVC controller in ASP.Net core 3. The JSON object is built from a CSV file and passed through an AJAX call. The correct action is called but the object received is always null.
JS:
async function updateData(){
var obj = await getData();
$.ajax({
type: "POST",
contentType: "application/json",
url: "/admin/metric/Update",
data: obj,
dataType: 'json',
success: function (data) {
console.log("SUCCESS : ", data);
},
error: function (e) {
console.log("ERROR : ", e);
}
});
}
async function getData(){
var metric = {};
var metrics = [];
var response = await fetch('https://bloburi.net/source/file.csv');
var data = await response.text();
var table = data.split('\n').slice(1);
table.forEach(row => {
var columns = row.split(',');
metric["FirstName"] = columns[0];
metric["LastName"] = columns[1];
metric["Email"] = columns[2];
metric["AverageHandleTime"] = columns[3];
metric["AverageFollowUp"] = columns[4];
metric["AverageHold"] = columns[5];
metric["Date"] = columns[6];
metrics.push(JSON.stringify(metric));
});
console.log(metrics);
return metrics;
}
Models:
public class MetricBase
{
[Required]
public string Date { get; set; }
public double AverageHandleTime { get; set; }
public double AverageHold { get; set; }
public double AverageFollowUp { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class MetricModel
{
public IEnumerable<MetricBase> MetricBase { get; set; }
}
Controller:
[HttpPost]
public IActionResult Update([FromBody]MetricModel obj)
{
return Json(new { obj });
}
I think the issue may be on how I'm deffining the class that is receiving the JSON, but I've tried multiple things like deffining MetricBase as a List<> or straight up as an array in MetricModel, but it doesn't seem to work.
Any help is appreciated!
You adding the stringify item inside array via code
metrics.push(JSON.stringify(metric));
instead of stringify the whole array. Change the line to
metrics.push(metric);
and data: obj, to
data: JSON.stringify(obj),.
With the mentioned change, the $.ajax sends the whole array. Also change the action to
public IActionResult Update([FromBody]IEnumerable<MetricBase> obj)
I have two classes: Person and account. Im working in asp net.
This is my controller:
public List<Person> GetPeople()
{
using (var db = new PeopleContext())
{
return db.People.ToList();
}
}
Person has the following attributes:
public class Person
{
public int PersonId{ get; set; }
public string Name{ get; set; }
public List<Accounts> Accounts{ get; set; }
}
Account class contains the following attributes:
public class Account
{
public int AccoundId{ get; set; }
public string AccountName{ get; set; }
public Person Person{ get; set; }
}
This is in my view
function getQuestions() {
$.ajax({
url: '/api/Person',
type: 'GET',
//dataType: 'json',
traditional: true,
success: function (data) {
$.each(data, function (i, p) {
string += '<h1>Name: '+p.Name+'</h1>';
$.each(p.Accounts, function (j, a) {
string += '<p>'+a.AccountName+'</p>';
});
});
},
});
How do i loop through account list in javascript and display it in my view? I have been trying to do this, but the list returns null. When i try to console log data it displays Accounts as null. How do i fix this?
I believe you would need to show your /api/Faq webservice response.
I would also suggest opening F12 developer tools and putting the word debugger in after you get the name:
string += '<h1>Name: '+p.Name+'</h1>';
debugger;
Then you can look at the p.Accounts object and see what it is.
If you Accounts object is empty you may need to add to your Person object's constructor:
public Person()
{
Accounts= new HashSet<Account>();
}
I using .net core. Have this class:
public class SentEmailItem
{
public SentEmailItem()
{
Attachments = new List<AttachmentItem>();
}
public long PKID { get; set; }
public string EmailTo { get; set; }
public string EmailFrom { get; set; }
public string Cc { get; set; }
public string Bcc { get; set; }
public string Body { get; set; }
public string Subject { get; set; }
public List<string> EmailToList => EmailTo.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList();
public List<string> CcList => (Cc ?? string.Empty).Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList();
public List<string> BccList => (Bcc ?? string.Empty).Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList();
public List<AttachmentItem> Attachments { get; set; }
}
And this method in controller:
[HttpPost]
public JsonResult SendEmail(SentEmailItem item)
{
EmailHelper.SendEmailAsync(item);
return SuccessJsonResult();
}
But when called this method from ajax by this code:
$.ajax({
type: "POST",
url: self.sendEmailUrl,
contentType: "application/json",
dataType: "json",
data: JSON.stringify({ item: emaiItem })
}).done(function (json) {
App.unblockUI();
if (json.HasErrors) {
var errorsHtml = $.map(json.Errors, function (x) { return "" + x.ErrorMessage + "<br/>"; }).join("");
UIToastr.ShowMessage('error', 'Error', errorsHtml);
return;
}
self.sendEmailCompleted(json);
});
I can't get data in my controller method. All data have null value. But in emaiItem placed in js value looks like:
Bcc : "Testmail#mail.com"
Body : "test body"
Cc : "Testmail#mail.com"
EmailFrom : "just3f#mail.com"
EmailTo : "Testmail#mail.com"
Subject : "Test subject"
It seems like emaiItem is already an object like this -
var emaiItem = {Bcc : "Testmail#mail.com",
Body : "test body",
Cc : "Testmail#mail.com",
EmailFrom : "just3f#mail.com",
EmailTo : "Testmail#mail.com",
Subject : "Test subject"};
If so, it could just use JSON.stringify(emaiItem). Otherwise, you can hard-code the above emaiItem, and see those values populated at server-side.
$.ajax({
...
data: JSON.stringify(emaiItem)
}).done(function (json) {
...
});
Solution
Replace with JSON.stringify(emaiItem), and use [FromBody] attribute to force model binder to bind data from the request body.
[HttpPost]
public JsonResult SendEmail([FromBody]SentEmailItem item)
{
}
Have you tried debugging your c# code to see if the back-end api is being hit? That would be a good place to start if not.
I've a problem with sending data form the browser to an API on my server by an ajax request to the server (method is PUT). Here is my JavaScript code:
var json = JSON.stringify({
StemType: {
ID: parseInt(this.dataset.id),
Type: this.dataset.type,
GebruikerID: "#(Model.DeTopic.Gebruiker.Id)"
},
Punten: parseInt(this.dataset.punten),
GestemdeGebruikerID: "#(Model.AangemeldeGebruiker)"
});
$.ajax({
url: "../Stem/Toevoegen",
type: "PUT",
data: json,
success: function (returnData) {
// my code
}
});
This is the json code in the variable json:
{
"StemType": {
"ID": 24731,
"Type": "Topic",
"GebruikerID": "539e6078"
},
"Punten": 1,
"GestemdeGebruikerID": "3aedefab"
}
And here is the C# code on the server.
public class StemController : ApiController
{
[HttpPost]
[Authorize]
[Route("Stem/Toevoegen")]
public void Toevoegen([FromBody]Stem stem)
{
Console.WriteLine(stem.ToString());
}
}
Here is the class Stem:
public class Stem
{
public StemType StemType { get; set; }
public int Punten { get; set; }
public string GestemdeGebruikerID { get; set; }
}
public class StemType
{
public int ID { get; set; }
public Type Type { get; set; }
public string GebruikerID { get; set; }
}
But if I debug my code on the server, I've got this:
Can anyone help me?
I've found two possible ways:
Don't stringify it for send as an object.
Add contentType: "application/json" for Json code.
I have a service call that gets a filtered set of historical records from our database.
I am having some trouble getting my filter to make it to my main mvc call.
[AcceptVerbs("POST")]
public History History( HistoryFilters filters){......}
config.Routes.MapHttpRoute(
name: "DefaultActionRoute",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional}
);
amplify.request.define('post', 'ajax', {
url: '/api/{controller}/{action}',
dataType: 'json',
type: 'POST',
cache: cache
});
//History Filter js
{
DateRange: { Start: ..., End: ...},
BetStatus: "unresolved",
TransactionTypes: "bets"
}
Every time I make my call filters is filled with null values. The JS filter structure matches that of my HistoryFilters class in C#. If I JSON.stringify my filters and change my Api call to string then i get that but it would be nice if it could convert it for me.
Can anyone tell me what i'm missing?
Edit
var amp = function(id, data) {
return $.Deferred(function(dfd) {
amplify.request({
resourceId: id,
data: data,
success: dfd.resolve,
error: dfd.reject
});
}).promise();
};
History: function (filters) {
return amp('post', { controller: 'user', action: 'history', '':filters});
}
public class HistoryFilters
{
public DateFilter DateRange { get; set; }
public string BetStatus { get; set; }
public string TransactionTypes { get; set; }
}
public class DateFilter
{
public string Start { get; set; }
public string End { get; set; }
}
It is my understanding that AmplifyJS has an issue with setting contentType and therefore can not make this type of call. I removed the amplifyJS call for now and am using just jquery.