I am sending name, which is a string, and table data which is a 2d array
And I get the name just fine in th controller, but I can't seem to get the tableData.
This is how I create the table data
TableData
var tableData = new Array();
$('table tbody tr').each(function () {
var tableRow = new Array();
tableRow.push({ 'id: $(this).data('id') });
tableRow.push({ 'item': $(this).data('item') });
tableData.push(tableRow);
});
JavaScript
var data = { Name: name, TableData: tableData };
$.ajax({
type: 'POST',
url: url,
data: data
});
Controller
[HttpPost]
public void Add(string Name, string[][] TableData)
{
// Stuff
}
As always in an ASP.NET MVC application you could start by defining a view model that will reflect to your view logic:
public class MyViewModel
{
public string Name { get; set; }
public Ilist<MyViewModelItem> TableData { get; set; }
}
public class MyViewModelItem
{
public string Id { get; set; }
public string Item { get; set; }
}
then fix the way you are constructing your data on the client:
var tableData = [];
$('table tbody tr').each(function () {
tableData.push({
id: $(this).data('id'),
item: $(this).data('item')
});
});
and then the way you are sending this information to the controller (send it as a JSON payload to ensure that complex objects will be properly bound on the server as long as your view model respect the proper format):
$.ajax({
type: 'POST',
url: url,
data: JSON.stringify({ name: name, tableData: tableData }),
contentType: 'application/json'
});
and the last piece of the puzzle - the signature of your controller action:
[HttpPost]
public void Add(MyViewModel model)
{
// Stuff
}
Related
I'm using knockout mapping to help map a serverside object into JSON. I have an object with numerous collections in it so I don't want to have to recreate and map each piece manually in javascript. So, I'm using knockout-mapping to do this for me.
I was having issues, so I decided to try it with a simple example, so here is what I have for an ASP.NET MVC application:
C# Model:
public class Vaccinations
{
public string Vaccination { get; set; }
public System.DateTime VaccinationDate { get; set; }
}
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
public Dog()
{
this.Vaccinations = new System.Collections.Generic.List<Vaccinations>();
}
public System.Collections.Generic.List<Vacinations> Vacinations { get; set; }
}
As you can see, each Dog has a list of vaccinations they may or may not have.
In my controller, I create and return a pre-populated Dog object:
public ActionResult Load()
{
Dog rambo = new Dog
{
Name = "Rambo",
Age = 5
};
rambo.Vacinations = new System.Collections.Generic.List<Vacinations> {
new Vacinations { Vacination = "Rabies", VacinationDate = DateTime.Now.AddYears(-1) },
new Vacinations { Vacination = "Mumps", VacinationDate = DateTime.Now.AddYears(-2) }
};
return Json(rambo, JsonRequestBehavior.AllowGet);
}
In my view (Index.cshtml), I have it set up to show the dog and a list of it's vaccinations. I want to allow the user to click on an Add Vaccination button to add a new line to the collection and allow them to enter the data.
Again, I'm using knockout.mapping to do the mapping for me. In the javascript section, this is what I have:
var ViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.isValid = ko.computed(function () {
return self.Name().length > 3;
});
// Operations
self.save = function () {
$.ajax({
url: "Dog/Save",
type: "post",
contentType: "application/json",
data: ko.mapping.toJSON(self),
success: function (response) {
alert(response.Status);
}
});
};
self.addVaccination = function () {
self.Vaccinations.push(new self.Vaccination()); // <--- This doesn't work and I know why, so how do I do this?
}
};
$(function () {
$.getJSON("Dog/Load", null, function (data) {
ko.applyBindings(new ViewModel(data));
});
});
My question revolves around the "addVaccination" function that I've added to the ViewModel object. How do I specify a new "Vaccination" object without having to "code" one in Javascript? That was the entire reason for me using knockout mapping so I don't have to do that. But, I don't see any other way around it.
Is there a way to access the base Vaccination object from the Vaccinations observable array so I can create a new one?
And then the final question is, how to I pass this back to my controller? I'm not sure if this will work or not.
You can't directly. But what you can do is define a Vaccination instance at the server side and return it as a the default instance.
So, you need to return the old data and the default instance.
public ActionResult Load()
{
...
var data = new {
defaultVacination = new Vacination(),
rambo = rambo,
};
return Json(data , JsonRequestBehavior.AllowGet);
}
And on the client side you receive the same data and the default instance.
var ViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data.rambo, {}, self);
var defaultInstance = data.defaultVacination;
...
self.addVaccination = function () {
// clone the default instance.
self.Vaccinations.push(ko.utils.extend({}, defaultInstance));
}
I hope it helps.
I've got two models, there are.
public class CreateAssignmentViewModel {
...
public List<CreateAssignmentSelectedItem> SelectedItems { get; set; }
}
public class CreateAssignmentSelectedItem {
...
}
Now I've got a view where contains CreateAssignmentViewModel, as you can see above this class contains a property where is a List<CreateAssignmentSelectedItem>
#model Contoso.MvcApplication.Models.Assignment.CreateAssignmentViewModel
#{
ViewBag.Title = "Create Assignment";
...
}
#using (Html.BeginForm()) {
...
}
Inside of the Html.BeginForm, I've got a partial view. And in it I've got a button using ajax where updates the partial view.
Look the following events. Where says data: I do not know what to enter to access only the property SelectedItems
var addQuestionToAssignmentContent = function (questionId)
{
$.ajax({
url: "/Assignment/AddItemToAssignmentContent",
type: "post",
data: { model: $(this).serialize() /* HERE I DON'T KNOW TO ACCESS THE */, itemId: questionId },
success: function (response) {
var $target = $("#assignmentContent");
var $newHtml = response;
$target.replaceWith($newHtml);
}
});
};
public ActionResult AddItemToAssignmentContent(List<CreateAssignmentSelectedItem> model, string itemId)
{
...
PartialView(..);
}
How can I do to pass only the object in the method?
First, give your form an ID:
#using (Html.BeginForm("actionName", "controllerName", FormMethod.Post, new{id = "frmUpdate"})) {
Second, change your code to be like this:
var f = $("#frmUpdate");
$.ajax({
url: f.attr('action'),
type: f.attr('method'),
data: f.serialize(),
//etc..
I use this in most cases and it works just nice. The data should automatically be bound to the model you have in your update action. So, for example... if you have a #model of type MyModel then in the update action it should look something like this:
[HttpPost]
public ActionResult Update(MyModel updatedModel)
Sometimes I work with a front end guy and he might not adhere to pass in the correct model, he might change the form fields or whatever. In this case I just let him serialize the form and pass it to the action an any way he wants.
I then use the FormCollection object to get the data I need.
Your json call
var addQuestionToAssignmentContent = function (questionId)
{
$.ajax({
url: "/Assignment/AddItemToAssignmentContent",
type: "post",
data: { model: $(this).serialize() /* HERE I DON'T KNOW TO ACCESS THE */, itemId: questionId },
success: function (response) {
var $target = $("#assignmentContent");
var $newHtml = response;
$target.replaceWith($newHtml);
}
});
};
Get a form collection object
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddItemToAssignmentContent(FormCollection collection)
{
vars someValue = collection.GetValue("somefield").AttemptedValue;
}
But if I would have control of front-end as you do then as Matt suggested you should use an pass a model;
I have a viewmodel that has 2 properties, first property is a Model object and the second property is a List . In my View i have 2 parts.
First part populates the data for the first object, Firstname,lastname,email and some other stuff.
The second part of my view is a webgrid that a user adds multiple address.
Now what is my problem, i have a JSON action on my controller that gets the data from the form, adds them to the viewmodel List property, but nothing happens.
I checked that the data is coming from the view, added to the viewmodel but each time the viewmodel is empty.
[Authorize]
public JsonResult addAddress(Address addr, CustomerViewModel model)
{
if (model.CAddress== null)
model.CAddress= new List<Address>();
model.CAddress.Add(addr);
return Json(model, JsonRequestBehavior.AllowGet);
}
I am using Javascript :
function AddAddress()
{
var addID = $("#lstID option:selected").val();
var addName = $("#lstAddName option:selected").text();
var Address =
{
addID : addID.toString(),
addName : addName.toString()
};
$.ajax({
type: "POST",
url: "#Url.Action("addAddress","Customer")",
dataType: "json", contentType: "application/json; charset=utf-8",
data: JSON.stringify(Address),
success: function (data) {} }); };
Alright, start by writing a real view model, not some hybrids:
public class CustomerViewModel
{
public List<AddressViewModel> Addresses { get; set; }
... some other properties you already had
}
public class AddressViewModel
{
public string Id { get; set; }
public string Name { get; set; }
}
then adapt your controller action to take your real view model, and not some hybrids and mixtures between view models and domain models:
[Authorize]
[HttpPost]
public ActionResult AddAddress(CustomerViewModel model)
{
return Json(model);
}
and finally adapt your AJAX call:
function AddAddress() {
$.ajax({
url: '#Url.Action("AddAddress", "Customer")',
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({
addresses: [
{
id : $('#lstID').val(),
name : $('#lstAddName option:selected').text()
}
],
someOtherPropertyOnYourViewModel: 'some other value'
}),
success: function (data) {
}
});
}
Edit: I've got the solution and have described it a bit more at the end of the post
Using: MVC 3, C#
Problem: A key/value obj array sent to controller via $.post/$.ajax results in a 500 internal server error at the controller (because the value passed to the method in the C# controller is null)
I have an array that's in the format:
{
"q_1": {
"qid": "1",
"tmr": 0
},
"q_2": {
"qid": "2",
"tmr": 0
}
}
I get this via $("#myid").data() - and this is all fine.
I need to send this to my controller, and tried both post and $.ajax
var d = $("#q_data").data();
$.post("/run/submit", d, function(data) { alert(data);}, "application/json");
and
$.ajax({
url: '/run/submit',
data: d,
contentType: 'application/json',
dataType: 'json',
success: function (data) { alert(data); }
});
The method on the C# side is
public ActionResult Submit(List<PerfObj> dict)
{
int x = dict.Count;
return PartialView("_DummyPartial");
}
Where PerfObj is my model
public class PerfObj
{
public string id { get; set; }
Perfvar perfVar;
}
public class PerfVar
{
public string qid { get; set; }
/* note I've tried both int and string for the tmr param */
public string tmr { get; set; }
}
When I execute this, the call goes to the controller correctly - i.e. it hits the submit method. However, the method parameter dict, in
List<PerfObj> dict
is null.
Why? It seems to be something with my model, can't figure out how else to design it so it extracts the values correctly to the method parameter.
When I print the JSON.Stringify on the console, it shows the key/value pair correctly so I'm thinking it's going correctly to the server but the server/MVC3 doesn't like it for some reason or can't map it to the List of PerfObjs.
EDIT: Solution
Maciej's answer to my post was how I solved it. What I did eventually was to create a arrays of perfObj at the client side
$("#q_data").data(e,{key: e, perfVar: { qid: e, tmr: 0 }})
(ps - ignore redundant usage of 'e', I've got other plans, this is a dummy case)
And then I mapped it to a JSON friendly array
var arr = [];
$.each($('#q_data').data(), function (i, e) {
var p = $(this).data(i);
var obj = { key: i, perfVar: { id: e.perfVar.qid, tmr: e.perfVar.tmr}};
arr.push(obj);
});
Then stringified it
var q = JSON.stringify(arr);
$.ajax'd it as described in Maciej's post.
I redefined my classes properly
public class PerfObj
{
public string key { get; set; }
public PerfVar perfVar { get; set; }
}
public class PerfVar
{
public string id { get; set; }
public int tmr { get; set; }
}
and changed the signature of my controller method
[HttpPost]
public ActionResult Submit(PerfObj[] dict)
{
return PartialView("_DummyPartial");
}
This now works perfectly and I can extend my classes fairly easily to do what I want.
Thank you all!
There are 3 things wrong with your code:
A. The property PerfVar must be made public and there must be a get and set on it:
public class PerfObj
{
public string id { get; set; }
public Perfvar perfVar { get; set; }
}
B. Your JSON representation of the list is incorrect. It should be:
var e = [
{ "id": "foo", "perfVar": { "qid": "a", "tmr": "b"}},
{ "id": "foo", "perfVar": { "qid": "a", "tmr": "b"}}
];
C. You have to stringify the array and specify type: 'POST' to pass it to your MVC controller via ajax:
$.ajax({
url: '/run/submit',
data: JSON.stringify(e),
contentType: 'application/json, charset=utf-8',
dataType: 'json',
type: 'POST',
success: function (data) { alert(data); }
});
You can't directly map a key/value pair to a flat sequence. MVC has no idea how to do that.
You either need a custom model binder, or a better/easier option would be to change how you create the JSON on the client-side, so it actually matches up to your model.
Something like this:
var perfObjs =
{
{ id: 1, perfVar: { qId: 1, tmr: 0 }},
{ id: 2, perfVar: { qId: 2, tmr: 0 }},
}
$.post("/run/submit", perfObjs, function(data) { alert(data);}, "application/json");
Because the controller does not recognize the json value as List.
Why not just pass the raw string to your controller and let your controller convert the json string to object? that will be much more easier.
I have a link as follows.
#Html.ActionLink("Create Report", "Screenreport", "Reports", null, new { #class = "subNavA AddBorderTop", id = "screenReport", title = "Create Report" })
Once the link is clicked, I have a the following jQuery code which creates a JSON object and post the information.
$().ready(function () {
// Create Report fron the screen data
$("#screenReport").live("click", function (event) { GenerateScreenReport(this, event); });
}) /* end document.ready() */
function GenerateScreenReport(clikedtag, event) {
var table = $(".EvrakTable").html();
var screendata = tableParser(table);
var Screentable = { Screenlist: screendata };
var myurl = $(clikedtag).attr("href");
var title = $(clikedtag).attr("title");
$.ajax({
url: myurl,
type: 'POST',
data: JSON.stringify(Screentable),
dataType: 'json',
contentType: 'application/json',
success: function () { alert("Got it"); }
});
};
To Handle JSON I have the following two classes. Realize two classes in the same namespace
namespace MyProject.ViewModels
{
public class Screenrecord
{
public string Fname{ get; set; }
public string LName { get; set; }
public string Age { get; set; }
public string DOB { get; set; }
}
public class Screentable
{
public List<Screenrecord> Screenlist { get; set; }
}
}
ANd in my controller, I have the following code:
[HttpPost]
public FileStreamResult Screenreport(Screentable screendata)
{
MemoryStream outputStream = new MemoryStream();
MemoryStream workStream = new MemoryStream();
Document document = new Document();
PdfWriter.GetInstance(document, workStream);
document.Open();
document.Add(new Paragraph("Hello World"));
document.Add(new Paragraph(DateTime.Now.ToString()));
document.Close();
byte[] byteInfo = workStream.ToArray();
outputStream.Write(byteInfo, 0, byteInfo.Length);
outputStream.Position = 0;
return new FileStreamResult(outputStream, "application/pdf");
}
This code is supposed to gerate PDF.
if I leave [HttpPost] as it is, it does NOT generate PDF and it goes to /Screenreport page, however I see my JSON is passed to the controller properly.
(screendata is populated properly - in controller)
But if I comment out [HttpPost], it DOES generate a PDF but screendata (in controller) is null.
Can someone please explain whats's going on and help me figure it out. Thanksin advance.
You cannot use AJAX to download files, because javascript doesn't allow you to save the downloaded content.
To workaround this you need to take 2 steps.
First: make the HTTP Post request, and in the controller action we would store the File content in a Memory stream.Second: on success make another call by setting the window.location to the Download Action method
In your Controller create this 2 actions:
public ActionResult GenerateFile()
{
MemoryStream fileStream = new MemoryStream { Position = 0 };
//position = 0 is important
var fName = string.Format("File-{0}.xlsx", DateTime.Now.ToString("s"));
Session[fName] = fileStream;
return Json(new { success = true, fName }, JsonRequestBehavior.AllowGet);
}
public ActionResult DownloadFile(string fName)
{
var ms = Session[fName] as MemoryStream;
if (ms == null)
return new EmptyResult();
Session[fName] = null;
return File(ms, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fName);
}
In your javascript:
$('#Donwload-button').click(function () {
data = JSON.stringify(YOURDATA);
$.ajax({
contentType: 'application/json; charset=utf-8',
dataType: 'json',
type: 'POST',
url: "/YOURCONTROLLER/GenerateFile",
data: data,
success: function (d) {
if (d.success) {
window.location = "/YOURCONTROLLER/DownloadFile" + "?fName=" + d.fName;
}
},
error: function () {
alert("Error");
}
});
});
I feel obligated to post my answer since I didn't hear from anyone. I ended up creating a form that includes a hidden input, then saved my json object in the hidden input and then submit the form. This time I will get input as an string not a json or xml.
var $hidInput = $("#dataToReport");
$hidInput.val(JSON.stringify(Screentable));
$('#frmScreenreport').submit();
Thanks all anyways.