Binding single value from the body of a POST with MVC - c#

I am implementing a protocol in ASP.NET MVC, and I need to be able to bind data from a request made like this:
curl -H "Content-Type: application/json" -d 'true' http://example.com/operation/some_id
I have tried using the [FromBody] attribute on the parameter in my controller, like this:
public ActionResult Operation(string id, [FromBody] bool setSomething)
The above code does not work, as it throws an exception when MVC attempts to set setSomething to null. If I change setSomething to a string, it always gets set to null, so I am unable to parse it to a bool in the action.
I don't have the luxury of changing 'true' to '=true' as I have read elsewhere when similar questions were asked. The POST generated from the curl command above is set in stone. What I need is some way of taking the value true (which is valid json, even without a key) from the body of the POST and assigning it to setSomething. I also need to do this in a way that doesn't prevent me from assigning some_id to id, as I already have working with a custom route.
Does anyone know how this can be accomplished in MVC or Web API?
Any help is appreciated.

I found a solution, but it's more of a workaround.
When the body of a POST is just true or false but nothing more, there is no key to bind this value to. Therefore, MVC doesn't really have anything it can do other than run the value through a JSON deserializer, which succeeds without setting any parameters in the action.
In the end, I had to read the value directly from Request, as described in MVC controller : get JSON object from HTTP body?.
I ended up using similar code:
public ActionResult Operation(string id)
{
var req = Request.InputStream;
req.Seek(0, SeekOrigin.Begin);
string rawBody = new StreamReader(req).ReadToEnd();
bool setSomething = false;
if(bool.TryParse(rawBody, out setSomething))
{
// Do something with 'setSomething'
return Json(new { id = id, status = setSomething });
}
throw new ArgumentException(string.Format("{0} is not a valid boolean value", rawBody));
}
As you can see, I removed setSomething from the parameter list entirely, and rely on reading the raw input stream of the request in order to get the value. This is in no way elegant, and does not make use of all the goodies we get from the MVC framework, but it works.

Related

FromBody attribute

I have a method as described below which get user as parameter.
To send the user parameter values, I am using postman request/response tool.
My question is, if the request already have user parameter in body request (see at the postman print screen ) why do I need to directly specify [FromBody] in action controller? When this attribute removed, parameter send with null values.
Thanks
[HttpPost("register")]
public IActionResult Register([FromBody]User user)
{
//.. code here
}
public class User
{
public string Name { get; set; }
public string Password { get; set; }
}
The [FromBody] directive tells the Register action to look for the User parameter in the Body of the request, rather than somewhere else, like from the URL. So, removing that confuses your method, and that's why you see null values as it's not sure where to look for the User parameters.
See: https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api for more information/learning.
For Model Binding in ASP.NET Core, MVC and Web APi uses the same model binding pattern.
There are default model bindings like Form Values, Route values and Query Strings. If these bindings fails, it will not throw an error, and return null.
If you do not want to add [FromBody], you could try Form Values binding and send request from Postman like below:
[FromBody] will override default data source and specify the model binder's data source from the request body.
You could refer Model Binding in ASP.NET Core for detail information
the attribute [FromBody] ensures the API reads the JSON payload in the postman body. For more info, read
https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

How to retrieve HttpPost parameters in c#

So I know that this works:
[HttpPost]
public string functionthatiuse()
{
string id = "";//does nothing
return relevantinfo;
}
Then I use this Chrome POST extension shown below and I have a break point in the function which is how I know it reaches it. It's literally an empty post request basically.
But when I try to post with parameters I'm having trouble. Ideally I want to do something like this:
[HttpPost]
public string functionthatiuse(string idx)
{
string id = ""; //does nothing and is different from idx
return relevantData;
}
but when I try to use it I get an error back. I'm pretty sure it's because I'm not formatting the content body correctly and I've tried putting other stuff in the content body, but nothing has really worked. Does anyone know how I can send POST parameters to this function using this extension? The format of what I'm doing in code should be basically the same (part of a requirement).
Edit:
Here's a picture of the error:
According to microsoft here: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
You have to add "[FromBody]" in the parameter list. You can only have one of these types of parameters.
Also in the chrome post extension under headers you need to enter:
Name: Content-Type
Value: application/json

How can I POST form data to ASP.NET WebAPI 2?

I'm having trouble accessing parameters submitted via POST in my ASP.NET WebAPI 2 application. I hav a very trivial controller and an action, which simply captures the parameter and returns 200.
public IHttpActionResult Post([FromBody] string foo)
{
return Ok();
}
And I'm making the request via cURL
curl http://localhost:24196/api/checkpoints -XPOST -d foo=bar
The parameter ends up being null. The same happens when I try this with jQuery.
$.post("http://localhost:24196/api/checkpoints", { foo: "bar" })
I've found a post that seems to describe this issue, but I'm not sure if that's really the correct way to fix this (using the = encoding).
What is the correct/standard way of talking to a WebAPI service, and how does the parameter binding actually work in these cases?
As documented in the link, FromBody expects data in a certain way =foo - re: there is no "key". So:
Curl:
curl -X POST -d "=bar" http://localhost/controller
Jquery:
var _data = { "": "bar" };
Update:
Reference for this behavior: Sending simple types
By default, Web API tries to get simple types from the request URI. The FromBody attribute tells Web API to read the value from the request body.
...If you need to get multiple values from the request body, define a complex type.
Second, the client needs to send the value with the following format:
=value
Hth...

Why does ASP.NET Web Api model binding uses the parameter type to determine the source of the value?

Since a few days I'm trying to create my own web api controller. Duo to the rest conventions I need to use a post request to create an object. To get concrete, Im having this controller with this action:
public class ReservationController : ApiController
{
[HttpPost]
public void Create(int roomId, DateTime arrivalDate)
{
//do something with both parameters
}
}
This code is not working when I fire a post request at it, I'm receiving a 404 exception something like this:
No action was found on the controller 'Some' that matches the request.
The reason for it is that simple types are read from the query string, complex types from the body, according to this aricle. The web api uses the parameters to match the action to a request and can't therefore map my action to the request.
I do know that I can use the [frombody] tag, but you can only apply that to one parameter and I have 2. I also know that I can create a wrapper object which have both the parameters, but I'm not willing to use wrappers for all my calls.
So I do know that I can work around this by these methods. I also think that this is caused by the fact that the body of the post request can only be read once. But my actual question is:
Why is the source of a parameter determined by it's type and not by it's availability, especially when the conventions state that you should make for example a post request for creation? In MVC this is the case, why isn't it in the web api?
Best regards,
BHD
FINAL UPDATE
Since I'm getting some upvotes, problably more people are facing the same question. In the end it comes to this: Web-Api != MVC. It's simply not the same thing and the web api team made different design decisions than the mvc team I guess.
It seems that you have a fundamental misunderstanding of how Web API actually works.
Web API routing is driven off of verbiage, not the method names. "SomeMethod" actually translates to zero useful information for Web API. As a result, if I post
api/some/some?id=1
OR
api/some/somemethod?id=1
OR EVEN
api/some/?id=1
and the SomeMethod endpoint is the ONLY available POST, it will hit that endpoint.
As such, first of all, make sure you have only one POST on that api controller. If you do, POSTing to it from any test client using either of the query strings above will work just fine.
You can use the [FromBody] attribute on the parameter to force it to read from the body of the HTTP POST instead of the Uri. This is opposed to the [FromUri] attribute which does the opposite.
[HttpPost]
public void SomeAction([FromBody] int id)
{
//do something with id
}
Are you sure you're actually putting the id in the body? It could also be a routing issue. If this still doesn't work then maybe you should use Fiddler and copy the RAW output of your HTTP message here.
If you're packing multiple values into the body such as with JSON then you should use a model which should automatically be deserialized to:
public class PostModel
{
public int ID { get; set; }
public int SomeOtherID { get; set; }
}
[HttpPost]
public void SomeAction(PostModel postModel)
{
//do something with postModel.ID and postModel.SomeOtherID
}
You can actually do this straight out of the box in WebAPI, at least in 2.2 (.Net version 4.5.2). Your controller is correct. Using your controller, if you call it with a HTTP POST like this (tested through Fiddler):
http://localhost:58397/api/Reservation?roomId=123&arrivalDate=2015-12-17
You'll get the correct values of roomId = 123 and arrivalDate = 17.12.2015.
I suspect there's something wrong in your call to the WebAPI. Maybe post that call if you're still not getting it to work.

MVC - Passing Data with RedirectToAction()

I'd like to take data entered in an MVC user form and display it in a different view.
The class has the following private variable:
IList<string> _pagecontent = new List<string>();
The following action accepts a FormCollection object, validates it, and passes it on to the "Preview" view as a List:
[Authorize(Roles = "Admins")]
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateContent(FormCollection collection)
{
if (ModelState.IsValid)
{
string PageToInsert = collection["PageToInsert"];
string PageHeader = collection["PageHeader"];
string PageBody = collection["PageBody"];
//validate, excluded...
_pagecontent.Add(PageToInsert);
_pagecontent.Add(PageHeader);
_pagecontent.Add(PageBody);
}
return RedirectToAction("Preview", _pagecontent);
}
The Preview view has the following Page Directive for passing a strongly typed object List:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Template.Master" Inherits="System.Web.Mvc.ViewPage<List<string>>" %>
I would expect to be able to use the Model object to get my data, but alas I cannot. At the following line, I get an error index out of bounds exception, stating the index must be non-negative and less than the size of the collection:
<% if (Model[0].ToString() == "0") { %>
And some strange parameters have been added to the URL, as it resolves to
http://localhost:1894/Admin/Preview?Capacity=4&Count=3
So I have two questions:
When I call RedirectToAction and pass it my List, why is it inaccessible in the view's Model object?
What is the proper way to go about doing what I'm trying to do, namely pass a collection of strings to a view for display there?
Try using TempData. It is like a single-shot session object. You put values you want into TempData, immediately redirect and get them out. There is a good writeup here: http://blogs.teamb.com/craigstuntz/2009/01/23/37947/
Be careful when using TempData. It works great in a single server environment but in a cloud environment it may not work as expected since you cannot guarantee that the request will hit the same machine. This happens because TempData rely on the asp.net session. But if you are using other session manager like SQL or AppFabric Cache it will work fine.
The second parameter to RedirectAction is routeValues, not model.
protected internal RedirectToRouteResult RedirectToAction(string actionName, object routeValues);
Try using TempData for the model. Its for persisting data between redirects.
The problem with RedirectToAction is it's returning a HTTP 302 and the browser is then on it's own going and doing a brand new HTTP request. You may want to consider using a cookie and/or session object to persist the data between requests.
This is not working because RedirectToAction is actually sending back a Http 302 to the browser. When the browser receives this 302, it does a new request to the server asking for the new page. New request, new temp variables.
You will also face this problem when you try to save/edit/delete something and for some reason you deny it and you have to return the old form again.
So, instead of:
return RedirectToAction("Preview", _pagecontent);
Put the Preview logic in a separate method and just call it:
return PreviewLogic(_pagecontent);
You can also use the TempData[] dic to persist data for the next request like others have said, but then you will not avoid the 302 additional round trip to the server.
It sounds like you're trying to do:
public ActionResult UpdateContent(FormCollection form) {
...
return View("Preview", _pagecontent);
}
Note that a redirection is supposed to be a "clean slate" for the browser (except for things like the auth cookie). You don't get to tell the browser to pass information along to the next request, since the next request should be able to stand on its own. All you get to do is tell the browser what URL to request next. In ASP.NET MVC, when you pass an arguments-object to RedirectToAction, the public properties of that object are appended as query-string parameters to the generated URL.
Can't you just make 2 action results with the same name and mark 1 of them with HttpPost?
public ActionResult UpdateContent(FormCollection preview = null)
{
return View(preview);
}
[HttpPost]
public ActionResult UpdateContent(FormCollection collection = null, bool preview = false)
{
if (preview)
return UpdateContent(collection);
else
return UpdateContent(null);
}
It looks like you are looking for the UpdateModel command:
Check out ScottGu's blog post on the topic:
Improved UpdateModel and TryUpdateModel methods

Categories

Resources