Question: Can we bind without the use of a model and just purely based on the parameter names of the IActionResult method. From the below I am posting a single string to the end point, however it is wrapped in a object with a property of reviewNotes. I have a work around at the moment, which is explained below.
I have setup the end point llike so.
[HttpPost("updateassetnotes/{id}")]
public IActionResult UpdateAssetNotes(int id, [FromBody]string reviewNotes)
{
_dataMapper.UpdateAssetNotes(id, reviewNotes);
return ApiSuccess("Updated Review Notes");
}
And the client side is posting like this.
var url = "/api/cataloguing/updateassetnotes/" + id;
var data = { reviewNotes: self.assetReviewNotes() };
$.ajax({
type: 'POST',
url: url,
data: JSON.stringify(data),
contentType: 'application/json'
}).done(function (resp) {
}).fail(function (resp) {
}).always(function () {
});
However if I change the data to:
var data = self.assetReviewNotes();
Then the review notes string is actually populated. As mentioned this is the work around im using at the moment. As previously mentioned I could create a simple model like the below and use this in the end point to bind the string. Im just wondering if there is a way to bind directly to the primitive types.
public class SimpleModel {
public string ReivewNotes {get;set;}
}
Obviously, as you already noted in your question
var data = { reviewNotes: "foo" };
is different from
var data = "foo";
as the latter is a string and the former is an object with a string property.
The most straightforward solution to your code comes to this line
data: JSON.stringify(data),
You could solve this by "unwrapping" the string out of the object by doing something like
data: JSON.stringify(data.reviewNotes),
The alternative is to avoid using JSON.stringify and just deal with the mapping the raw data instead. However, if you go this route, you may need to watch out for the mapping, and the [FromBody] decorator may need some tweaking, along with the content-type.
Related
I'm using .NET Core 5 with C#. I sometimes get an error for a null parameter called lang; but I don't see problems in the url, and when I get to the action I can see these values in the HttpContext:
QueryString = '?lang=es&BrandURLName=hp'
Querykeys = 'lang,BrandURLName'
QueryValues = '[lang, en],[BrandURLName, hp]'
This is the action body:
[HttpPost]
public async Task<IActionResult> category_onfilterchange(string lang, string sectionurlname, string categoryurlname, vmCategory model)
{
// code
}
This is the Ajax call:
var obj = $("#form-main").serialize();
$.ajax({
type: "POST",
async: true,
processData: false,
cache: false,
url: "/category/category_onfilterchange?lang=#Model.cmCurrentLanguageISOCode§ionurlname=#Model.SectionURLName&categoryurlname=#Model.CategoryURLName",
data: obj,
success: function (response) {
},
error: function (jqxhr, status, error) {
}
});
For some reason, the param lang cannot be bound, but this just happen sometimes
Framework don't know how to map those values down to your action. Looking at your code there are two different data sources where you put your values.
Query string url: "/category/category_onfilterchange?lang=#Model.cmCurrentLanguageISOCode§ionurlname=#Model.SectionURLName&categoryurlname=#Model.CategoryURLName",
Body data: obj,
In which you assumed the framework will magically map it to your action parameters. Unfortunately, it won't map. There are action level attributes you can use to instruct framework how to map these values.
However, I'm not sure if you can combine [FromQuery] and [FromBody] in one action without creating custom model binder. I personally haven't tried it before so please read the link and proceed at your discretion.
My recommendation is to create one object to combine properties of obj, lang, sectionurlname, and categoryurlname. Pass it as data: CombinedObjectHere and remove query string because you no longer need it. Then change your action into this category_onfilterchange([FromBody]CombinedObjectHere model). Obviously, you need to create POCO class of CombinedObjectHere.
I just want to know why it's necessary for .NET to match parameter name with the JSON object's key name?
Quick code preview here...
var json = {
"service": "COMMON",
"method": "MENU_SUBLIST",
"UID": "1000007",
"ULID": "stackoverflow",
"UNM": "queston",
"SITE": "1",
"DEPT": "2",
"LANG": "ko",
"MENUID": "0000",
"STEPMENU": "",
"ACTIONNAME": ""
}
Okay, Let's call an action in a controller through Ajax.
$.ajax({
type: "POST",
url: "DATACRUD.json",
data: JSON.stringify(json),
contentType: "application/json; charset=utf-8",
dataType: "json",
async: false, //_async,
success: function (result) {
}
});
And my c# action code here..
[HttpPost]
public ActionResult DATACRUD(string jsondata)
{
return Json(new{ fromMVC = jsondata});
}
// Just example.
jsondata is here null because I didn't match the key name.
For DATACRUD to get the JSON data, I have to do like this.
{ jsondata : {
"service":"COMMON",
"method":"MENU_SUBLIST",
"UID":"1000007",
"ULID":"stackoverflow",
"UNM":"queston",
"SITE":"1",
"DEPT":"2",
"LANG":"ko",
"MENUID":"0000",
"STEPMENU":"",
"ACTIONNAME":""
}
}
Here question No.1 Why do I have to match the key name with the param name?
It just does? there's gotta be a reason, and I want to know why.
And what I want to do is...
{
"service":"COMMON",
"method":"MENU_SUBLIST",
"UID":"1000007",
"ULID":"stackoverflow",
"UNM":"queston",
"SITE":"1",
"DEPT":"2",
"LANG":"ko",
"MENUID":"0000",
"STEPMENU":"",
"ACTIONNAME":""
}
to pass this JSON data into the action, DATACRUD I specified above
I want DATACRUD action to take the JSON data and consume it whatever the key name is.
There's another answer for this. The answer is to create a model for JSON data and receive it as a model type, and get the model as string.
But defining models cannot be possible in my apps. It could cause a hundred of model creation.
So receiving the JSON data after making a model is the last thing I need.
In this case, how am I supposed to do?
No key name matching is allowed.
No generating model is allowed.
No third party framework is allowed.
I think the possible answers narrow down to a few....
What I have to do?
The MVC routing engine dictates that the parameter names must match, as that is how it knows what to populate since everything comes through as strings to the server. The MVC plumbing will be searching through the query portion of the URL, and even searching fields in a form on a POST to populate all of your parameters.
Having a hundred models is not that bad for a complex project. However, it can be a pain if you have to go back and retrofit your entire application.
No matter what you do, you'll need to make sure that your JavaScript variable names match those of your Action method parameters, which shouldn't be a problem since you're writing both sides.
Base on post MVC controller : get JSON object from HTTP body?
You action should be:
[HttpPost]
public ActionResult DATACRUD()
{
Stream req = Request.InputStream;
req.Seek(0, System.IO.SeekOrigin.Begin);
string json = new StreamReader(req).ReadToEnd();
return Json(new { fromMVC = json });
}
I have attempted to modify one of my api controller to allow for the creation of multiple reservations by allowing one of the parameters to be passed in as a pipe delimited string. The method and class can be seen here:
public class ReservationsController : ApiController
{
public HttpResponseMessage PostReservation(string eRaiderUserName, string SpaceNumbers)
{
char[] delimiter = { '|' };
string[] spaces = SpaceNumbers.Split(delimiter);
bool saved = true;
foreach(string space in spaces)
{
var reservation = new Reservation { eRaiderUserName=eRaiderUserName, SpaceNumber=Convert.ToInt32(space) };
if (true)
{
reservation.Game = db.Games.FirstOrDefault(g => g.ID == AppSettings.CurrentGameID);
db.Reservations.Add(reservation);
db.SaveChanges();
//HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, reservation);
//response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = reservation.ID }));
//return response;
}
else
{
saved = false;
//return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
db.SaveChanges();
if (saved)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = 1 }));
return response;
} else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
}
I have a form that posts what I think should be the right information, but I keep getting this error:
{"$id":"1","Message":"No HTTP resource was found that matches the request URI 'http://localhost:58463/api/Reservations'.","MessageDetail":"No action was found on the controller 'Reservations' that matches the request."}
The (modified) save method in the api is still definitely a work in progress. But what is keeping this from finding the web api controller? Here is the firebug output:
As pointed out, the problem is that a POST action can only transfer the data posted in the body to a single object (for technical reasons).
That means that you can get data from the route data, from the querystring, and from the body, with the following limitations:
data from querystring or route data must be single values (i.e. they cannnot be classes), in any number
you can have only one parameter of the action with data coming from the request body, but this can be a complex class
you can make any combination of this, i.e. a single or complex param coming from the body, and any number of single parameters coming from the route data or the querystring.
So, the most generic way to solve your problem (i.e. that can be easyly applied to other classes where you need to pass complex data, even more complex than this case) is this:
First, make a class which has properties for all the needed data,in your case:
public class ReservationData
{
public string eRaiderUserName { get; set; }
public string SpaceNumbers { get; set; }
}
Second, use this class as the type of the received parameter in your action:
public HttpResponseMessage PostReservation(ReservationData reservationData)
With this code the formatter can map all the data in the request body to a single parameter in the action. You can use JSON or formdata formats, like the generated by jQuery.
NOTE: the property names must case-sensitively match the name of the posted parameters.
This is because you send x-www-form-urlencoded data to controller, to handle this data you must use [FromBody] before parameter like
public HttpResponseMessage Post([FromBody] string name) { ... }
but this approach has a lot of limitation:
1) There can be only one parameter marked [FromBody] attribute (it can be complex type)
2) The data must be encoded as =value not as key=value .
You can read it here description and how make it work here example .
If it possible i recommend you to send Json data to controller, without this limitation.
Web API has limited support to map POST form variables to simple parameters of a Web API method. Web API does not deal with multiple posted content values, you can only post a single content value to a Web API Action method.
public HttpResponseMessage PostReservation(string eRaiderUserName, string SpaceNumbers)
{ //...}
and you are trying to call using jQuery:
$.ajax({ url: 'api/reservations', type: 'POST', data: { ... }, dataType: 'json', success: function (data) {alert(data);} });
Unfortunately, Web API can’t handle this request and you’ll get error. But if you pass parameters using query string, It’ll work:
$.ajax({ url: 'api/reservations?eRaiderUserName=2012&SpaceNumbers=test', type: 'POST', dataType: 'json', success: function (data) { alert(data); } });
But it’s not good solution and not applicable for complex objects. So here are the different ways to do it.
Using Model Binding (Preferred)
Using Custom Parameter Binding
FormDataCollection
Query String
My Web API Put method is called properly from my jQuery Ajax but the C# code does not save the update. The json object does not have all the properties of the MEMBERS Entity. The context does not save any changes to the database.
public void Update(string id, MEMBERS obj)
{
var memToUpdate = context.MEMBERS.Find(obj.MEMBERID);
if (memToUpdate != null)
{
context.Entry(memToUpdate).CurrentValues.SetValues(obj);
int result = context.SaveChanges();
System.Diagnostics.Debug.WriteLine("save result:" + result);
}
}
This code will work but How do I make context update all the properties of the MEMBERS Entity that are in the JSON Object without specifying like this?
public void Update(string id, MEMBERS obj)
{
MEMBERS memToUpdate = context.MEMBERS.Find(obj.MEMBERID);
if (memToUpdate != null)
{
//context.Entry(memToUpdate).CurrentValues.SetValues(obj);
memToUpdate.FIRSTNAME = obj.FIRSTNAME;
memToUpdate.LASTNAME = obj.LASTNAME;
int result = context.SaveChanges();
System.Diagnostics.Debug.WriteLine("save result:" + result);
}
}
Jquery:
var data = {
MEMBERID: "B745",
FIRSTNAME: "TESTPUT",
LASTNAME: "UPDATED WEBAPI"
};
var json = JSON.stringify(data)
$.ajax({
url: 'api/Members/' + data.MEMBERID,
type: 'PUT',
contentType: "application/json; charset=utf-8",
data: json,
success: function (results) {
alert("Success");
}
})
My question is more about how to do Web API Put partial update. I read about the Patch method using Delta<> type from oData. I installed Web API oData from nuget but VS complains type or namespace Delta does not exist. Has anyone run into this problem?
You can do partial updates using WebApi without using Odata (you do need to install the libary but the Delta object seems to work fine without the oData stuff). I would use PATCH and not PUT as from my understanding PUT is there to replace the whole resource, PATCH is the partial HTTP Verb.
You need the odata library (but just for the Delta library, i hope they move this out of here and make it more general as it will be useful) http://www.nuget.org/packages/Microsoft.AspNet.WebApi.OData
Some code, not sure if this will 100% work, i have a slightly different setup. I do use something similar to your original code to facilitate my PUT, but only because the Delta.Put method throws loads of errors, which i think is to do with my dates, anyhoo, not related.
public void Update(string id, Delta<MEMBERS> obj)
{
var memToUpdate = context.MEMBERS.Find(obj.MEMBERID);
if (memToUpdate != null)
{
obj.Patch(memToUpdate);
int result = context.SaveChanges();
System.Diagnostics.Debug.WriteLine("save result:" + result);
}
}
It looks like WEB API cannot do partial update unless using oData. I ended up having to call the GET method to get back the whole object with all properties then changed the value of some of the properties I want and update via PUT method.
$(document).ready(function () {
$.getJSON('api/Members/B745', function (data) {
data.FirstName = 'Test Put Success';
data.LastName = 'My Lastname Is';
var json = JSON.stringify(data)
$.ajax({
url: 'api/Members/' + data.MEMBERID,
type: 'PUT',
contentType: "application/json; charset=utf-8",
data: json,
success: function (results) {
alert("Success");
}
})
});
});
I'm trying to call a page method belonging to a MVC Controller from another site, by means of:
$.ajax({
type: "GET",
url: "http://localhost:54953/Home/ola",
data: "",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
console.log(data.Name);
}
});
the method code is as follows, really simple, just to test:
public ActionResult ola()
{
return Json(new ActionInfo()
{
Name = "ola"
},JsonRequestBehavior.AllowGet);
}
I've seen this aproach being suggested here, and I actually like it a lot, should it work...
When I run this, firebug gets a 200 OK, but the data received is null.
I've tried a lot of different approaches, like having the data in text (wish grants me "(an empty string)" instead of just "null") or returning string in the server method...
Can you tell me what am I doing wrong?
Thank you in advance,
João
Have you tried returning your JSON like so...
public ActionResult ola()
{
return Json(new { Name = "ola" }, JsonRequestBehavior.AllowGet);
}
Controller:
public ActionResult Ola()
{
// No need to use a custom ActionInfo type here, an anonymous type
// will be just fine:
return Json(new { Name = "ola" }, JsonRequestBehavior.AllowGet);
}
View:
$(function {
$.getJSON('/home/ola', function(json) {
alert(json.Name);
});
});
You could try returning JsonResult from the controller action method. Method signature would then be public JsonResult ola(). Hope it helps.
Thanks for all the feedback. I found out that everything I was doing was right and wrong at the same time.
the requests were all functional, but the request was being made to a different domain, wich is automatically blocked by the browsers (except IE). It's a question of security.
But since the request was meant to work on a mobile device, when i tested it there, it worked perfectly.
Once again, thanks to everyone who answered, i'll adopt some of the ideas shown here :)
if you are making an ajax call cross domain. Have you tried setting your data type to
dataType: jsonp
jquery ajax cross domain