Model Binding HTTP Requests in ASP.Net Core WebAPI - c#

I'm having an issue with an endpoint, it takes an object which isn't binding and consequently returning a 400 bad request.
I've gotten around this issue by passing in the individual properties of the object rather than the object itself, but would prefer the passing of an object.
WebClient webClient = new WebClient();
webClient.QueryString.Add("firstName", "value1");
webClient.QueryString.Add("lastName", "value2");
string result = webClient.DownloadString(url);
[HttpGet]
public async Task<IActionResult> DoSomething(string firstName, string lastName)
{
// this endpoint works perfectly
return Ok();
}
[HttpGet]
public async Task<IActionResult> DoSomething([FromBody]SubmitModel model)
{
// this endpoint returns a 400 bad request
return Ok();
}
public class SubmitModel
{
public string FirstName {get; set;}
public string LastName {get; set;
}

By design, GET request does not contain data in the request body.
So when your Submit method recieves a request, it can't bind the model from the body as the data does not exist and therefore returns an bad request response.
As your method is named Submit, it sounds like you should use a POST request instead. POST request, by design, sends data in the request body and is suited for submitting data to the server.
Try it like this
[HttpPost]
public async Task<IActionResult> Submit([FromBody]SubmitModel model)
{
// this endpoint returns a 400 bad request
return Ok();
}

I don't know if there is a good way to do what you're wanting. To get a little bit closer you can add this attribute to pull directly from the url
[HttpGet]
public ActionResult Get([FromUri]SubmitModel model)
{
// this endpoint returns a 400 bad request
return Ok();
}
Another thing you can do if need be is create an extension method that reflects over the model and adds all the properties/values to the query string. Some good examples here How do I serialize an object into query-string format?

Related

POST request from Postman fails model validation

Inside my ASP.NET WebApi program, I have an Author model:
public class Author
{
public int Id { get; set; }
[Required] public string Name { get; set; }
}
I also have an AuthorsController, with a PostAuthor(Author author) method:
// POST: api/Authors
[ResponseType(typeof(Author))]
public async Task<IHttpActionResult> PostAuthor(Author author)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// etc.
}
When I send a POST request programmatically inside my unit tests, HTTP Status Code 201 Created is returned:
However, when I send a POST request using Postman, I receive HTTP Status Code 400 Bad Request instead:
As you can see, when I send a POST request using Postman, the argument passed into the PostAuthor(Author author) method is null, and model validation fails as a result:
What should I do to ensure that POST requests from Postman can be processed?
Couple of changes: define it as HttpPost and use FromBody like
// POST: api/Authors
[HttpPost]
[ResponseType(typeof(Author))]
public async Task<IHttpActionResult> PostAuthor([FromBody] Author author)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// etc.
}
Replace = with : in postman body, its a JSON after all.
If you send in application/json and your API wait as INBOUND JSON, so try to send in JSON format, something like
{
"Id":"6",
"Name":"P.G. Wodehouse"
}

Web API HttpPost method always receives GET

I have a web api controller with a method
[HttpPost]
[Authorize]
[Route("[action]")]
public async Task<IActionResult> Authenticate(HttpRequestMessage msg)
{
//msg.Method is always GET
// msg.Content is null
}
I call it from my desktop application like this:
HttpClient client = new HttpClient(new HttpClientHandler(){AllowAutoRedirect = false});
foreach (var header in headers)
{
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
await client.PostAsync(requestUrl, content);
But in the web api method the property msg.Method is always GET and msg.Content is null
Is it sort of redirect or another intentional behavior that every request turns into GET?
I tried to change it to
[Authorize]
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> C2dSend([FromBody]string request)
{
return Ok();
}
but got 'BadRequest' after call.
The only thing that works is to replace string with dynamic:
[Authorize]
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> C2dSend([FromBody]dynamic request)
{
return Ok();
}
Only POST requests are routed to your action. You just looking at the wrong place - you are trying to bind body of your request to HttpRequestMessage instance. Obviously, you are passing something else here.
If you are looking for request details, use the Request property of controller. It is populated from contoller context which is passed to the controller during activation:
[Authorize]
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> C2dSend()
{
// Request.Method is POST here
return Ok();
}
But you don't need to use it to get request content. Let Asp.Net do this work for you - declare a model class which has the same properties as the serialized object which you are sending and model binder will do deserialization work for you:
[Authorize]
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> C2dSend(YourModel blah)
{
// model will be populated automatically from request body
return Ok();
}
Note: There is an easier way to send json requests. You can use PostAsJsonAsync extension from System.Net.Http.Formatting.Extension

How is x-requestid populated?

Please see the code below, which is similar to some code I am looking at:
[HttpGet]
public IActionResult Create([FromHeader(Name = "x-requestid")] string requestId)
{
return View();
}
[HttpPost]
public IActionResult Create(Person person, [FromHeader(Name = "x-requestid")] string requestId)
{
if (ModelState.IsValid)
{
string test = "got here";
}
return View();
}
requestId is always null. How is requestId populated?
I have read lots of questions over the last two hours e.g. this one: What is the X-REQUEST-ID http header?. An answerer to another question suggesting installing the following Nuget package:
Microsoft.ApplicationInsights.ASPNetCore
However, this made no difference.
In general the "x-*" headers are non-standard ones.
That particular one is probably coincidentally used within Application Insights to uniquely identify a request, but it's unlikely to be sending requests to your server including it.
Whatever client is sending requests to your server has to be explicitly adding that header for you to receive it there; it's not part of any standard HTTP request.
You are bound to request header with name "x-requestid", but it should be called "X-Request-ID". Just try:
[HttpGet]
public IActionResult Get([FromHeader(Name = "x-request-id")] string requestId)
{
return View();
}

How to fix - The requested resource does not support http method 'POST'

Below is WebAPI action. On googling about the below error:-
The requested resource does not support http method 'POST'
I got number of links & updated my api accordingly but still I am getting the same error.
Web api not supporting POST method
ASP.NET Web Api: The requested resource does not support http method 'GET'
[AcceptVerbs("POST")]
[HttpPost]
[Route("rename/{userId}/{type}/{title}/")]
public IHttpActionResult Rename([FromBody] int userId, [FromBody] string type, [FromBody] string title)
{
//my api stuff
}
But still when calling the above via post man throws the error.
How do I get rid of this error??
Also is it possible to fix this without using [FromBody] attribute in the method parameters list?
Any help/suggestion highly appreciated.
Thanks.
You have declared route which requires url parameters
[Route("rename/{userId}/{type}/{title}/")]
So when you send request to api/customer/rename it does not match this method. You should remove parameters which you are passing in request body from route parameters
[Route("rename")]
Make sure that you have appropriate RoutePrefix("api/customer") attribute on your controller.
Second problem is multiple [FromBody] parameters. You will get can't bind multiple parameters error. There is limitation - you can mark only one parameter as FromBody. See Sending Simple Types notes:
Web API reads the request body at most once, so only one parameter of
an action can come from the request body. If you need to get multiple
values from the request body, define a complex type.
You should create complex type which will hold all parameters
public class RenameModel
{
public int UserId { get; set; }
public string Type { get; set; }
public string Title { get; set; }
}
And change method signature to
[HttpPost]
[Route("rename")]
public IHttpActionResult Rename(RenameModel model)
And send request data as application/x-www-form-urlencoded
[Route("rename/{userId}/{type}/{title}/")]
public IHttpActionResult Rename([FromBody] int userId, [FromBody] string type, [FromBody] string title)
The last answer is correct, you're asking for these parameters in the route, but saying that you expect them in the post body. Also, usually the route would begin with a noun rather than a verb. What is it you're renaming? (i.e. [Route("users/rename/{userId}/{type}/{title}")]
Based on your initial post, try this instead:
[HttpPost]
[Route("rename/{userId}/{type}/{title}" Name = "RenameUser"]
public IHttpActionResult Rename(int userId, string type, string title)
{
_myServiceMethod.Rename(userId, type, title);
return new StatusCodeResult(HttpStatusCode.Created, this);
}
Or, if you wanted to do a post with the info in the body:
Declare your data contract:
public class User
{
public string Type { get; set; }
public string Title { get; set; }
}
Then on the endpoint:
[HttpPost]
[Route("rename/{userId}", Name = "RenameUserPost")]
public IHttpActionResult RenameUserPost(int userId, [FromBody] User userData)
{
return new StatusCodeResult(HttpStatusCode.Created, this);
}
Note that in both returns 'this' refers to your controller class that inherits from ApiController. Verified both of these in swagger, and they accept POSTs and return status codes.
Hope this helps.
I had this error for wrong string in Route string on top of my action.
[Route("api/TestReaderPercentStudyHomework/AddOrUpdate")]

Retrieving params in a WEB API POST method

In my web api I am primarily using POST methods so the client can send function parameters using JSON. However, I noticed that having a model (dto) for these params on both the client and server doesn't really make sense. Or does it?
Since I am using POST is creating a model object for binding required? Sometimes I am actually dealing with an entity like Customer and it makes complete sense, but when I am dealing with 3 random parameters do I really have to create a model (dto) so I can retrieve the data into the POST function?
Here is my POST function in my WEB API:
[HttpPost, Route("GetAccountInformation")]
public IActionResult RetrieveAccountInformation(GetAccountInformationParamsObj myParams)
{
var retVal = _repository.GetAccountInformation(myParams.StartDate, myParams.EndDate, myParams.Count)
return Ok(retVal);
}
Here is an example of what the client will send in the POST body:
{
"StartDate":"10-25-2015",
"EndDate":"11-25-2015",
"Count":20
}
It would be great if the following would work but the param values end up being empty when making the request:
[HttpPost, Route("GetAccountInformation")]
public IActionResult RetrieveAccountInformation([FromBody] DateTime startDate, DateTime endDate, int count = 0)
{
}
You dont need to do a POST, what you trying to do doesn't justify a POST, just try to get the data from uri using the Attr [FromUri] like this:
[HttpGet, Route("GetAccountInformation")]
public IActionResult Get([FromUri]GetAccountInformationParamsObj #params)
{
var retVal = _repository.GetAccountInformation(#params.StartDate, #params.EndDate, #params.Count)
return Ok(retVal);
}
and make the request like this:
/GetAccountInformation?StartDate=10-25-2015&EndDate=11-25-2015&Count=20

Categories

Resources