I have run into an issue attempting to pass an array of objects to an MVC3 controller/action. I've found several discussions on the web (including here at SO) but none have solved my problem. Here is what I'm working with:
public class Person
{
public Guid TempId { get; set; }
public bool ReadOnly { get; set; }
[Required(ErrorMessage = "Required")]
public string Name { get; set; }
}
Here is what my controller action (currently, I've tried many variations) looks like:
[HttpGet]
public ActionResult AddPerson(List<Person> people)
{
if (null != people)
{
foreach (var person in people)
{
Debug.WriteLine(person );
}
}
return View();
}
I have tried numerous ways of structuring my jquery in order to call to action, but so far the only one I can get to work is if I manually encode the array of objects into the URL string like so:
var url = "AddPerson?people%5B0%5D.TempId=9FBC6EF8-67DB-4AB4-8FCE-5DFC0F2A69F9&people%5B0%5D.ReadOnly=true&people%5B0%5D.Name=Bob+Jones&people%5B1%5D.TempId=9FBC6EF8-67DB-4AB4-8FCE-5DFC0F2A6333&people%5B1%5D.ReadOnly=false&people%5B1%5D.Name=Mary+Jones #peopleDiv";
$("#peopleDiv").load(url, updatePeopleDiv);
The people%5B1%5D.ReadOnly=false is the encoded version of people[1].ReadOnly=false
I've tried other things such as:
var url = "AddPerson #peopleDiv";
var people = [];
people.push({'[0].TempId': '<valid guid>', '[0].ReadOnly': true, '[0].Name': 'Bob Jones' });
$("#peopleDiv").load(url, people, updatePeopleDiv); // nope
$("#peopleDiv").load(url, $.param(people, false), updatePeopleDiv); // nada
$("#peopleDiv").load(url, $.param(people, true), updatePeopleDiv); // nyet
I have also tried modifying the above by wrapping that people[] inside an object: var data = { arr: people } and then trying to send that data object various ways. All the ways (other than the one I got to work by manually encoding) result in one of three outcomes:
No controller action called (mismatch between how it's defined and how I called it)
Controller action called but param is null
Controller action called, and correct number of object elements in the array BUT none of the actual values from those objects is transferred (so I have an array of objects all with default values).
Any suggestions?
Just set contentType to 'application/json' and use JSON.stringify().
$.ajax({
contentType: 'application/json',
type: 'POST',
url: '/Controller/AddPerson',
data: JSON.stringify({
people: [
{
ReadOnly: false,
Name: 'Cristi'
},
{
ReadOnly: true,
Name: 'Matt'
}
]
}),
success: function (data) {
$("#peopleDiv").html(data);
},
error: function () {
console.trace();
}
});
Also, add/edit/delete requests should be done via POST.
[HttpPost]
public ActionResult AddPerson(List<Person> people)
{
if (null != people)
{
foreach (var person in people)
{
Debug.WriteLine(person );
}
}
return PartialView("viewname");
}
Related
i'm sending an array of Guids to an ASP.NET Core Web app using aurelia-fetch-client, however on the server side the model binder doesn't pick it up and the list of notificationIds is null. However when i make the request through Swagger, or CURL it binds just fine.
I changed the signature of my controller method to accept a list of strings just in case there was something wrong with the GUID formatting, but same issue.
JS
var body = {notificationIds : this.notifications.map(x => x.notificationId) };
console.log("Dismissing All notifications");
await this.httpClient.fetch('http://localhost:5000/api/notifications/clear',
{
method: 'POST',
body: json(body),
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'Fetch'
},
mode: 'cors'
}).then(response => {
if(response.status == 204){
//Success! Remove Notifications from VM
}
else{
console.log(response.status)
}
})
Controller Method
// POST: api/Notifications
[HttpPost]
[Route("clear")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Post([FromBody]List<string> notificationIds)
{
if (notificationIds.IsNullOrEmpty())
{
return BadRequest("No notifications requested to be cleared");
}
var name = User.Claims.ElementAt(1);
await _notificationRepository.Acknowledge(notificationIds, name.Value);
return NoContent();
}
Interesting thing is that Chrome (V62) shows nothing posted.
But Fiddler does
The shape of the object you are passing from JavaScript isn't the same shape of an object you are telling the ASP.NET framework to expect.
There are two ways that you could fix this issue:
Option 1:
In your JavaScript, change your body to var body = this.notifications.map(x => x.notificationId);
Option 2:
Create an object in c# that reflects what you are passing from your JavaScript.
namespace Foo
{
public class Bar
{
public List<string> NotificationIds { get; set; }
}
}
and then update your controller method to the following:
// POST: api/Notifications
[HttpPost]
[Route("clear")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Post([FromBody]Bar bar)
{
if (bar.NotificationIds.IsNullOrEmpty())
{
return BadRequest("No notifications requested to be cleared");
}
var name = User.Claims.ElementAt(1);
await _notificationRepository.Acknowledge(bar.NotificationIds, name.Value);
return NoContent();
}
The problem here is that you're not sending a list of GUIDs you are sending an object with a property that contains a list of GUIDs. Either create and use a view model (as described by peinearydevelopment) or accept a dynamic parameter that refers to the json object.
public async Task<IActionResult> Post([FromBody] dynamic json)
{
var notificationIds = json.notifcationIds;
...
I have an example which sends a JSON post request to MVC controller. This example works in ASP.NET 4.5 but won’t work in the newest ASP.NET 5 release. Do I miss anything in this example?
I created a model, but I didn’t bind it to database. It will be just the object created in the memory.
public class SalesOrder
{
public int SalesOrderId { get; set; }
public string CustomerName { get; set; }
public string PONumber { get; set; }
}
I use Visual Studio 2015 to create that model based controller and its associated CRUD views. However, in this example, I will only run the “Create” view.
Inside the controller, I hard coded a SalesOrder object list and it only contains an item. I will use it to work with the CRUD views.
private List<SalesOrder> salesOrderList =
new List<SalesOrder> (
new SalesOrder[]
{
new SalesOrder()
{
SalesOrderId = 1,
CustomerName = "David",
PONumber = "123"
}
});
Inside the controller, I also create a new function to process the “Save” request. The request will just change the CustomerName property of the model then bounce back with the JSON result.
[HttpPost]
public JsonResult Save(SalesOrder salesOrderViewModel)
{
salesOrderViewModel.CustomerName = "David returns";
return Json(new { salesOrderViewModel });
}
In the Create.cshtml, I created a button and attach JQuery script to its click event.
<p><button id="saveButton">Save</button></p>
This is the JQueryScript.
$(function () {
var clickFunc = function () {
var salesOrderViewModel = {
"salesOrderViewModel": {
"SalesOrderId": 123,
"CustomerName": "ab",
"PONumber": "2",
}
}
$.ajax({
url: "/SalesOrders/Save/",
type: "POST",
cache: false,
data: JSON.stringify(salesOrderViewModel),
contentType: "application/json; charset=utf-8"
});
}
$('#saveButton').click(clickFunc)
})
I click the "Save" button in Create.cshtml to trigger the post request.
I set the debug break point in the controller and verify the coming post request. In ASP.NET 4.5, the JSON deserialization is working, and it shows all the values.
However, in ASP.NET 5, an empty object is returned.
In ASP.NET 5 case, if press F12 to start the debugger in Microsoft Edge, then it shows the Post request does have the correct values, but for some reasons, they are not passed to MVC controller.
Please see these screen shots:
http://i63.tinypic.com/68vwnb.jpg
Do I miss anything?
Thanks for helping…
I don't think you need to specify the name of the parameter. Try:
var salesOrderViewModel = {
"SalesOrderId": 123,
"CustomerName": "ab",
"PONumber": "2",
}
I think you need to especify fromBody in the Controller.
[HttpPost]
public JsonResult Save([FromBody]SalesOrder salesOrderViewModel)
{
salesOrderViewModel.CustomerName = "David returns";
return Json(new { salesOrderViewModel });
}
Thanks for DCruz22 and Stephen Muecke.
The working version of the script is:
$(function () {
var clickFunc = function () {
var salesOrderViewModel = {
"salesOrderViewModel": {
"SalesOrderId": 0,
"CustomerName": "ab",
"PONumber": "2",
"MessageToClient": "POST to server"
}
}
$.ajax({
url: "/SalesOrders/Save/",
type: "POST",
cache: false,
data: salesOrderViewModel
});
}
$('#saveButton').click(clickFunc)
})
I found the parameter name "salesOrderViewModel" is not a problem. With it, the script is still working.
Actually, this question is a simplified version of my original problem. My originally problem is starting from the knockoutJs. Below is the script doing the data binding. After user clicks the "Save" button, the self.save function() is called and the script will send the post request including the data model to the MVC controller.
SalesOrderViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.save = function () {
$.ajax({
url: "/SalesOrders/Save/",
type: "POST",
cache: false,
data: ko.toJSON(self),
success: function (data) {
}
});
}
}
The problem is "ko.toJSON(self)" includes some other information. Below is the request body I captured from the Microsoft Edge debugger.
{"SalesOrderId":0,
"CustomerName":"chiang123",
"PONumber":"123",
"MessageToClient":null,
"__ko_mapping__":{
"ignore":[],
"include":["_destroy"],
"copy":[],
"mappedProperties":{
"SalesOrderId":true,
"CustomerName":true,
"PONumber":true,
"MessageToClient":true}}}
You can see all data starts from "ko_mapping" is KnockoutJs specific. Do I need to manually trim those data in order to make this work? Per information, obviously the same implementation should work in ASP.NET 4.5 but I just haven't try it yet. Many thanks...
I have the following controller action using the web api on a .net 4.0 web forms web application.
public dynamic Post(Guid id, string adminNotes = "", string artNotes = "")
{
dynamic response = new ExpandoObject();
response.Success = false;
Udac udac = CustomerUdacs.Where(x => x.EditKey == id).FirstOrDefault();
if (udac != null)
{
udac.AdminNotes = adminNotes.Trim();
udac.ArtNotes = artNotes.Trim();
response.Success = true;
}
return response;
}
I'm using jQuery ajax() to post the data through. I am using a Guid as my id (which is stored in ajax_edit_key in my javascript). When I use this:
$.ajax({
type: "POST",
url: "/api/notes/"+ajax_edit_key,
data: { adminNotes: $("#tbEditNotesAdmin").val(), artNotes: $("#tbEditNotesArt").val() }
}).done(function (data) {
if (data.Success) {
$("#tbEditNotesAdmin").val(''); //td.id-notes-admin
$("#tbEditNotesArt").val(''); //td.id-notes-art
ajax_edit_key = '';
$dialog.dialog("close");
}
}).fail(function () {
alert('fail');
});
it calls the action but my adminNotes and artNotes are passing through from the browser as empty strings.
If I change the url to include the parameters in the querystring it works fine.
url: "/api/notes/"+ajax_edit_key+"?"+ "adminNotes="+$('#tbEditNotesAdmin').val()+"&artNotes="+$('#tbEditNotesArt').val()
//data: { adminNotes: $("#tbEditNotesAdmin").val(), artNotes: $("#tbEditNotesArt").val() }
Why is this? How can I get it to use the data parameter
The problem here is the way how WebAPI engine parses multiple parameters. It can parse only one parameter, which comes from body request by default, and you have two.
The solution would be:
To create your own model binder (this article would help you: http://blogs.msdn.com/b/jmstall/archive/2012/04/18/mvc-style-parameter-binding-for-webapi.aspx)
To create a separate model class that will contain all your passing parameters (which is easier to me).
For the second option you need simply to create the model:
public class NotesModel
{
public string AdminNotes { get; set; }
public string ArtNotes { get; set; }
}
And change signature of your action to:
public dynamic Post(Guid id, NotesModel model)
And you don't need to change your JS.
i'm making a request do a asp.net webapi Post Method, and i'm not beeing able to get a request variable.
Request
jQuery.ajax({ url: sURL, type: 'POST', data: {var1:"mytext"}, async: false, dataType: 'json', contentType: 'application/x-www-form-urlencoded; charset=UTF-8' })
.done(function (data) {
...
});
WEB API Fnx
[AcceptVerbs("POST")]
[ActionName("myActionName")]
public void DoSomeStuff([FromBody]dynamic value)
{
//first way
var x = value.var1;
//Second way
var y = Request("var1");
}
i Cannot obtain the var1 content in both ways... (unless i create a class for that)
how should i do that?
First way:
public void Post([FromBody]dynamic value)
{
var x = value.var1.Value; // JToken
}
Note that value.Property actually returns a JToken instance so to get it's value you need to call value.Property.Value.
Second way:
public async Task Post()
{
dynamic obj = await Request.Content.ReadAsAsync<JObject>();
var y = obj.var1;
}
Both of the above work using Fiddler. If the first option isn't working for you, try setting the content type to application/json to ensure that the JsonMediaTypeFormatter is used to deserialize the content.
After banging my head around for a while on this and trying many different things I ended up putting some breakpoints on the API server and found the key value pairs stuffed down in the request. After I knew where they were, it was easy to access them. However, I have only found this method to work with WebClient.UploadString. However, it does work easily enough and allows you to load up as many parameters as you like and very easily access them server side. Note that I am targeting .net 4.5.
CLIENT SIDE
// Client request to POST the parameters and capture the response
public string webClientPostQuery(string user, string pass, string controller)
{
string response = "";
string parameters = "u=" + user + "&p=" + pass; // Add all parameters here.
// POST parameters could also easily be passed as a string through the method.
Uri uri = new Uri("http://localhost:50000/api/" + controller);
// This was written to work for many authorized controllers.
using (WebClient wc = new WebClient())
{
try
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
response = wc.UploadString(uri, login);
}
catch (WebException myexp)
{
// Do something with this exception.
// I wrote a specific error handler that runs on the response elsewhere so,
// I just swallow it, not best practice, but I didn't think of a better way
}
}
return response;
}
SERVER SIDE
// In the Controller method which handles the POST request, call this helper:
string someKeyValue = getFormKeyValue("someKey");
// This value can now be used anywhere in the Controller.
// Do note that it could be blank or whitespace.
// This method just gets the first value that matches the key.
// Most key's you are sending only have one value. This checks that assumption.
// More logic could be added to deal with multiple values easily enough.
public string getFormKeyValue(string key)
{
string[] values;
string value = "";
try
{
values = HttpContext.Current.Request.Form.GetValues(key);
if (values.Length >= 1)
value = values[0];
}
catch (Exception exp) { /* do something with this */ }
return value;
}
For more info on how to handle multi-value Request.Form Key/Value pairs, see:
http://msdn.microsoft.com/en-us/library/6c3yckfw(v=vs.110).aspx
I searched all morning to find an answer that depicted both client and server code, then finally figured it out.
Brief intro - The UI is an MVC 4.5 project that implements a standard view. The server side is an MVC 4.5 WebApi. The objective was to POST the model as JSON and subsequently update a database. It was my responsibility to code both the UI and backend. Below is the code. This worked for me.
Model
public class Team
{
public int Ident { get; set; }
public string Tricode { get; set; }
public string TeamName { get; set; }
public string DisplayName { get; set; }
public string Division { get; set; }
public string LogoPath { get; set; }
}
Client Side (UI Controller)
private string UpdateTeam(Team team)
{
dynamic json = JsonConvert.SerializeObject(team);
string uri = #"http://localhost/MyWebApi/api/PlayerChart/PostUpdateTeam";
try
{
WebRequest request = WebRequest.Create(uri);
request.Method = "POST";
request.ContentType = "application/json; charset=utf-8";
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(json);
streamWriter.Flush();
streamWriter.Close();
}
WebResponse response = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(response.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}
}
catch (Exception e)
{
msg = e.Message;
}
}
Server Side (WebApi Controller)
[Route("api/PlayerChart/PostUpdateTeam")]
[HttpPost]
public string PostUpdateTeam(HttpRequestMessage context)
{
var contentResult = context.Content.ReadAsStringAsync();
string result = contentResult.Result;
Team team = JsonConvert.DeserializeObject<Team>(result);
//(proceed and update database)
}
WebApiConfig (route)
config.Routes.MapHttpRoute(
name: "PostUpdateTeam",
routeTemplate: "api/PlayerChart/PostUpdateTeam/{context}",
defaults: new { context = RouteParameter.Optional }
);
Try this.
public string Post(FormDataCollection form) {
string par1 = form.Get("par1");
// ...
}
try using following way
[AcceptVerbs("POST")]
[ActionName("myActionName")]
public static void DoSomeStuff(var value)
{
//first way
var x = value;
}
I am trying to submit JSON to a MVC action. What I want is to take the JSON object and then access it's data. The number of JSON fields will vary each time so I need a solution that will handle all cases.
This is my POST to my action, address could have 3 fields or 20 it will vary on each post.
Update: I'll go into a little more detail. I'm trying to use the LinkedIn API, I'll be sent a JSON which will look like the JSON at the end of this page : link. I need to create an Action which will accept this JSON which will vary for every person.
var address =
{
Address: "123 rd",
City: "Far Away",
State: "Over There"
};
$.ajaxSetup({ cache: false });
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "/Account/GetDetails/",
data: JSON.stringify(address),
dataType: "json",
success: function () {
alert("Success from JS");
}
});
This is my action in MVC, I need to be apply to take whatever JSON object is passed and access its fields.
[HttpPost]
public ActionResult GetDetails(object address)
{
//address object comes in as null
#ViewBag.Successs = true;
return View();
}
I don't believe nobody saying this so I will try. Have your code like this
public class Personal
{
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
//other properties here
}
[HttpPost]
public ActionResult GetDetails(Personal address)
{
//Should be able to get it.
#ViewBag.Successs = true;
return View();
}
In general, you can add those possible properties into the class Personal (or whatever you can name it). But according to linkedin API, you will need a tool to generate the data class due to its complexity. I think xsd.exe can help if you can get xsd file for that (or you can even generate xsd file from xml)
Remove data: JSON.stringify(address) with data: address
Action method
[HttpPost]
public ActionResult GetDetails(string Address, string City, string State, string PropName)
{
//access variable here
}
As you have said your data object may contain 20 props, to avoid creating 20 parameters, you can use formscollection like below
[HttpPost]
public ActionResult GetDetails(FormCollection address)
{
string city= address["city"] ;
string anotherPro=address["prop"];
}
Not sure if this will work (never done this myself), but you could try the following signature:
public ActionResult LinkedIn(dynamic address)
I'm actually quite interested myself to see what will happen then. Or, as suggested in the comment by Kristof Claes, use a FormCollection.
Second, when things like this happen, always check whether the browser actually sends the data you expected to the server. IE9 and Chrome support this out of the box, otherwise you can use a tool like Fiddler.
EDIT: Just tried for myself with a dynamic parameter and that doesn't work.. The runtime type of the parameter is object so everything you submitted is lost. You'd better
use FormCollection.
I believe you can use a FormCollection for this.
public ActionResult LinkedIn(FormCollection address)
{
var street = address["street"];
...
return View();
}
Might just be a typo, but you are calling GetDetails ActionResult, but yet your code is LinkedIn ?
You need to wrap your JSON object in the name of parameter you are expecting in your action method. Something like this:
var data = { address: { address: '123 Test Way', city: 'Parts Unknown', state: 'TX' } };
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "/Account/GetDetails/",
data: JSON.stringify(data),
dataType: "json",
success: function () {
alert("Success from JS");
}
Then in your action method, do this:
public ActionResult GetDetails(dynamic address) {}
My take is all the answers are sort of right.
As your don't know the number of things you're sending, use formcollection on the server. But also remove the stringfy from the ajax call. These means the data will be sent using www-encoding.
If you wnat to send n address objects, change the mvc action parameter to an array of address objects and use stringfy.
This is how I do it. I have a MyProject.Model.Entities and I serialize them in by using [ParamSerializationFilter] attribute on the given action method.
Full code here: https://gist.github.com/3b18a58922fdd8d5a963
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!Enum.GetNames(typeof(AllowedMethods)).Any(n => n == filterContext.HttpContext.Request.HttpMethod))
throw new InvalidOperationException("Invalid Request: HttpMethod");
foreach (var param in filterContext.ActionDescriptor.GetParameters())
{
if (ModelTypes.Contains(param.ParameterType))
{
if ((filterContext.HttpContext.Request.ContentType ?? string.Empty) == ("application/json"))
{
filterContext.ActionParameters[param.ParameterName] =
JsonSerializer.DeserializeFromStream(param.ParameterType, filterContext.HttpContext.Request.InputStream);
}
else if (filterContext.HttpContext.Request.ContentType.Contains("xml"))
{
filterContext.ActionParameters[param.ParameterName] =
XmlSerializer.DeserializeFromStream(param.ParameterType, filterContext.HttpContext.Request.InputStream);
}
}
}
}
You're doing too much work! You can pass the JSON Literal directly to the server and access the elements via the action method parameters, like this:
var address =
{
Address: "123 rd",
City: "Far Away",
State: "Over There"
};
$.ajaxSetup({ cache: false });
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "/Account/GetDetails/",
data: address,
dataType: "json",
success: function () {
alert("Success from JS");
}
});
[HttpPost]
public ActionResult LinkedIn(string Address, string City, string State)
{
// now you have everything you want as params
#ViewBag.Successs = true;
return View();
}
Note: This will only work if your action params are named the exact same as your JSON Literal attributes.