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
Related
I've trying to do a POST in Angular, which makes a call to my C# backend. One API call works fine, but the other one doesn't exactly. I'm having a hard time figuring out what's going on, but I see that when I open the Network window in my browser's DevTools, the request payload has the JSON populated just fine. But in C#/the backend, it receives a null object, and I get a 200 code/null response from the call.
I've got the following code in Angular:
item.service.ts
private readonly _api = '...'
private postOptions = { headers: { 'Content-Type': 'application/json; charset=utf-8' }};
public addItem(formGroup: formGroup, isInactive: boolean): Observable<Item> {
let api = ``;
// These require different API calls depending on the flag
if (isInactive)
api = `${this._api}/AddInactiveItem`;
else
api = `${this._api}/AddItem`; // This one is the one having issues
const body = ItemPost.parse(formGroup.value);
return this.http.post<Item>(api, body, this.postOptions).pipe(
this.handle(
"POST successful",
"Error with POST"
)
);
}
item-post.ts
export class ItemPost {
Name: string;
Inactive: string;
...
public static parse(obj: any): ItemPost {
return !obj ? undefined : {
Name: obj.name,
Inactive: obj.inactive,
...
};
}
}
My backend/POST code is in C#. Both of my POST methods are built the exact same, but with different SQL calls.
[HttpPost]
public JsonResult AddInactiveItem([FromBody] ItemBody item)
{
if (!ModelState.IsValid)
return Json(null);
// Do SQL call for POST here, return JSON
}
[HttpPost]
public JsonResult AddItem([FromBody] ItemBody item) // This is where I have a breakpoint and it's passing in null
{
if (!ModelState.IsValid)
return Json(null); // And this is where I find myself
// Do SQL call for POST here, return JSON
}
JSON Payload (sorry, unable to get a screenshot but this is what I'm seeing):
{"Name":"Test", ..., "Inactive":"N"}
I think you are having problem with:
`$(this.api}/AddInactiveItem`
The right syntax for template strings is:
`${this.api}/AddInactiveItem`
I figured it out...and I feel dumb. Really dumb.
An int variable in Item was considered an optional int/number in the front-end for when an Item was considered active (not inactive), but in the backend, ItemBody didn't reflect that and was considered as just an int instead of int?. I had to dig through my ModelState errors through the debugger and it hinted this, but it's late at night and my mind didn't process it.
Gotta make sure all of the variable types are reflected properly in the Body object.
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.
In my api controller, I need to pass two parameters: an integer + a string. This string is a json variable so it's a very long string, I can't pass as part of the URL.
I tried passing it as POST parameter (in the data attribute of the ajax call), but I get an error: only the first parameter is read, so the URL passed is not the right one.
How should I do this?
EDIT
JS code:
function SavingFloor(FloorId, Json) {
$.ajax({
beforeSend: function (xhr) {
xhr.setRequestHeader('verifySession', verifySession());
xhr.setRequestHeader('Authorization', '#HttpContext.Current.Session["BaseAuth"]');
},
url: "/api/Floor/SaveFloor?FloorID=" + FloorId, //api: error in decoding json (the json can't be passed as parameter)
type: "POST",
data: {
jsonstring: Json
},
dataType: 'text/html',
success: function (data) {
alert('success');
}
});
}
Controller:
[HttpPost]
public void SaveFloor(int floorID, string jsonstring)
{
Floor floor = db.FloorSet.Find(floorID);
JavaScriptSerializer ser = new JavaScriptSerializer();
Dictionary<string, object> dict = ser.Deserialize<Dictionary<string, object>>(jsonstring);
floor.SavedJson = jsonstring;
floorRepository.Update(floor);
floorRepository.Save();
}
You could try adding [FromUri] and [FromBody] attributes to the SaveFloor method. You can send floorID in regular GET and send the long jsonstring as the POST body. Currently, Web API can only read a single parameter from a POST body. You either need to encapsulate both variables in a single object, or try this:
public void SaveFloor([FromUri]int floorID, string jsonstring)
You don't need to specify [FromBody] to jsonstring as it's the default one in a POST request.
After this, don't forget to change your client-side code to send the floorID in the query string too.
I have a controller class that I get data from database and return it in a function, Now I want to call this function in a js and set the data in variables to show in a page:
My code looks like: exampleController.cs
namespace iSee.WebApiHse.Controllers
{
public class expController : StandardController
{
public expController(
first myService,
ISystemSettings systemSettings,
IService myExpService)
{
_myService = MyService;
_systemSettings = systemSettings;
_myExpService = myExpService;
}
// GET data
public ActionResult Myexample(int id)
{
var elementIds = _systemSettings.ExpIds;
var myElements = CacheService.AllVisibleElements
.Where(x => elementIds.Contains(x.Id)).ToList();
var container = _kpiContainerService.Find(id);
var result = _myService.MonthByContainer(myElements, container);
return AsJson(result);
}
}
}
This works and I get the data. Now I have myExp.js that I need to use these data in it. How can I do that?
Thanks
You need to execute $ajax(..) (jquery syntax) request to your controller to pass and get compute information from the server.
For this your controller method, that you're going to call, has to be exposed for HTTP access.
More details on :
How to call controller method from javascript
Do you want work with your controller in View by JavaScript? It isn't good idea. You should pass Model to View and work with it or ajax and recieve json-data
An example that uses jQuery-ajax to call your C# method:
// javascript
var id = 987;
$.ajax({
url : '/expController/Myexample/',
type : 'GET',
data { id : id },
dataType : 'json', // <-- tell jQuery to decode the JSON automatically
success : function(response){
console.log(response);
}
});
This would call your method, passing in the value of id, then it would decode the JSON into the response object.
For plain Javascript ajax (no jQuery) see MDN Ajax.
You need to make a request to the Action Myexample. Usually this is done via AJAX:
In your view you could have:
function makeAJaxCall(idToSend) {
$.ajax({
url: '#Url.Action("exp", "Myexample")',
data: { id : idToSend },
type: "POST",
success: function(data) {
$("#HTMLElement").val(data.YourData);
}
});
}
Your response will come back in the data variable if the AJAX call succeeds. I have provided an example to show you how to change the value of an HTML element with the ID HTMLElement.
To invoke the function you can do:
makeAjaxCall(100);
You may load the JSON data in a var and pass it to the function in expjs.js, as:
var data = data_from_ajax();
exp_js_function(data);
data_from_ajax() would receive JSON data from your controller and action method.
Please consider this as a starting point and not as a copy-paste solution
I have the following complex object in JavaScript which contains filter options
var filter={caseIdentifiter:'GFT1',userID:'2'};
which I want to pass to an ASP.NET MVC4 WebApi controller GET
[HttpGet]
public IEnumerable<JHS.Repository.ViewModels.CaseList> Get([FromBody]Repository.InputModels.CaseListFilter filter)
{
try
{
return Case.List(filter);
}
catch (Exception exc)
{
//Handle exception here...
return null;
}
}
using an jQuery ajax call
var request = $.ajax({
url: http://mydomain.com/case,
type: 'GET',
data: JSON.stringify(filter),
contentType: 'application/json; charset=utf-8',
cache: false,
dataType: 'json'
});
The "filter" object in the ASP.NET controller method is "null". If I change it to a POST the filter object is passed correctly. Is there a way to pass a complex object to a GET?
I do not want to separate out the parameters to the URL as there will be a number of them which would make it inefficient, it would be hard to have optional parameters, and this way the method signature stays constant even if new parameters are added.
After finding this StackOverflow question/answer
Complex type is getting null in a ApiController parameter
the [FromBody] attribute on the controller method needs to be [FromUri] since a GET does not have a body. After this change the "filter" complex object is passed correctly.
If you append json data to query string, and parse it later in web api side. you can parse complex object. It's useful rather than post json object style. This is my solution.
//javascript file
var data = { UserID: "10", UserName: "Long", AppInstanceID: "100", ProcessGUID: "BF1CC2EB-D9BD-45FD-BF87-939DD8FF9071" };
var request = JSON.stringify(data);
request = encodeURIComponent(request);
doAjaxGet("/ProductWebApi/api/Workflow/StartProcess?data=", request, function (result) {
window.console.log(result);
});
//webapi file:
[HttpGet]
public ResponseResult StartProcess()
{
dynamic queryJson = ParseHttpGetJson(Request.RequestUri.Query);
int appInstanceID = int.Parse(queryJson.AppInstanceID.Value);
Guid processGUID = Guid.Parse(queryJson.ProcessGUID.Value);
int userID = int.Parse(queryJson.UserID.Value);
string userName = queryJson.UserName.Value;
}
//utility function:
public static dynamic ParseHttpGetJson(string query)
{
if (!string.IsNullOrEmpty(query))
{
try
{
var json = query.Substring(7, query.Length - 7); //seperate ?data= characters
json = System.Web.HttpUtility.UrlDecode(json);
dynamic queryJson = JsonConvert.DeserializeObject<dynamic>(json);
return queryJson;
}
catch (System.Exception e)
{
throw new ApplicationException("can't deserialize object as wrong string content!", e);
}
}
else
{
return null;
}
}