I've got a new API that I'm building with ASP.NET Core, and I can't get any data POST'ed to an endpoint.
Here's what the endpoint looks like:
[HttpPost]
[Route("StudentResults")]
public async Task<IActionResult> GetStudentResults([FromBody]List<string> userSocs, [FromBody]int collegeId)
{
var college = await _collegeService.GetCollegeByID(collegeId);
// var occupations = await _laborMarketService.GetOccupationProgramsBySocsAndCollege(userSocs, college);
return Ok();
}
And here's what my payload that I'm sending through Postman looks like:
{
"userSocs": [
"291123",
"291171",
"312021",
"291071",
"152031",
"533011"
],
"collegeId": 1
}
I'm making sure that I have postman set as a POST, with Content-Type application/json. What am I doing wrong?
You get always null because you need to encapsulate all your post variables inside only one object. Like this:
public class MyPostModel {
public List<string> userSocs {get; set;}
public int collegeId {get; set;}
}
and then
public async Task<IActionResult> GetStudentResults([FromBody] MyPostModel postModel)
If the model is null, check:
1) Where the data is sent: body, form? and based on that add the decorator to the action. For ex:
[HttpPost]
public JsonResult SaveX([FromBody]MyVM vm) { ... }
2) Check ModelState: if it's invalid the vm will not be bound so it will be null.
if (ModelState.IsValid) { ... }
Another reason for the model binding to fail (always null) is if the data type for a property doesn't match. For example here is a simple model:
public class MyService {
public string JobId { get; set; }
public int ServiceType {get; set;}
}
And here is some json that doesn't match:
{"JobId":1, "ServiceType":1}
I got caught with this when I was retrieving the JobId using jquery's .data function, it was automatically converting it to an int. Fixed it by using .attr function instead.
Also, make sure those variables inside your parameter class are declared as Public, (or they'll just keep returning as null)..
If you want to send two or more models, you should use this example:
[HttpPost]
public async Task<ActionResult> addUsuario([FromBody] Newtonsoft.Json.Linq.JObject datos)
{
Usuarios user = datos["usuario"].ToObject<Usuarios>();
Empresas empresa = datos["empresa"].ToObject<Empresas>();
return Json(await _srv.addUsuario(user, empresa));
}
I know it is not related to your case, still, I am posting my answer here.
It is a silly mistake that I had done in my code. I just copied one of my Get requests and changed it to a Post request, and forgot to decorate the parameter with [FromBody]. If anyone else is having the same problem, please make sure that you are decorating the parameter with [FromBody].
[HttpPost]
public IApiResponse Update([FromBody] User user) {
if (user == null) return new ApiBadRequestResponse(ModelState);
return _userService.Post(user) ? new ApiOkResponse(user) : new ApiResponse(500);
}
Assuming the [FromBody] class is made up of primitive data types;
[FromBody] is public
[FromBody] has an empty constructor ()
[FromBody] is serializable.
Make sure that your data transfer object has "Public" as Access modifier and also your oject properties have getter and setter methods.
For me, all values were coming in as null because my object had an enum that was not being parsed. If you have having this problem, I'd recommend turning on enum parsing at the Startup.cs per this SO
In my case it was using Newtonsoft.Json in my body object with JsonProperty attributes for some reason. Changing in to System.Text.Json.Serialization and JsonPropertyName attribute solved the problem
Related
I have a simple controller which allows a route path constrained by a regex pattern in a 'complex object'. When I try to read the single property from the object it is always null.
The ModelState is showing an errors collection saying:
The ActorId field is required
So it appears to be a model binding issue and not a validation issue.
I feel like I'm missing a pattern in the [HttpGet] block or something.
ActorIdParameter.cs
public sealed class ActorIdParameter
{
[Required]
[RegularExpression(#"^.*nm(\d{5}|\d{7})$")]
public string ActorId { get; set; }
}
ActorsController.cs
[HttpGet("{actorIdParameter}")]
public async Task<IActionResult> GetActorByIdAsync([FromRoute]ActorIdParameter actorIdParameter)
{
_ = actorIdParameter ?? throw new ArgumentNullException(nameof(actorIdParameter));
// validation result
if (!ModelState.IsValid) //<---this is always false
return ValidationProcessor.GetAndLogInvalidActorIdParameter(method, logger);
}
Code is called using this example: http://localhost:4120/api/actors/nm0000206
There are several other posts which deal with the [FromQuery] and [FromBody] but I can't find anything that deals with the route path. It seems like the {actorIdParameter} needs something to say "get the ActorId property within that object" or something.
I feel like I need the complex object for the regex matching. Alternatively I could switch from the ActorIdParameter object to a string and possibly decorate it inline on the GetActorByIdAsync method but I'm curious if anyone has any suggestions?
Code below works for me using
https://localhost:5001/api/actors/nm0000206
and validation fails correctly for this one
https://localhost:5001/api/actors/42
You don't need any custom handling for this.
public class ActorIdParameter
{
[Required]
[RegularExpression(#"^.*nm(\d{5}|\d{7})$")]
[FromRoute]
public string ActorId { get; set; }
}
[Route("api/actors")]
public class ActorsController : ControllerBase
{
[HttpGet("{actorId}")]
public async Task<IActionResult> GetActorByIdAsync(ActorIdParameter model)
{
if (!ModelState.IsValid)
{
return new BadRequestResult();
}
return new OkResult();
}
}
Try to do GET request to those three, change actors to your controller route :
http://url/actors/nm11111
http://url/actors?actorIdParameter=nm11111
http://url/actors/?actorIdParameter=nm11111
Is any worked ?
Check you model errors ( to understand why it is false ) :
var errors = ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => new { x.Key, x.Value.Errors })
.ToArray();
ModelState.IsValid == false, why?
I am very new to ASP.NET Core, C#, and RESTful APIs. I'm trying to create a simple practice application right now to practice the different REST commands. The "database" I am working with is a List of objects that have a Name, Age, and Weight.
Currently I am trying to implement a POST method. I could potentially use a [FromQuery] in order to get the data I need for creating the new object and adding it. However, I think it would be better to access it FromBody, especially if I want to add more fields later on.
I don't quite understand/know how I would be able to put stuff/ask the user (??) for data for this in the body. I think I grasp that, when the URL is called, it parses through whatever is in the body of the page/the returned .json and finds it that way, but I don't know how to populate this in the first place.
So far this is just my code for the POST:
[HttpPost]
public ActionResult<List<Objects>> Post([FromQuery] String NewName, [FromQuery] int NewAge, [FromQuery] double NewWeight)
{
return MyList.AddItem(NewName, NewAge, NewWeight);
}
I would love any descriptions on how this works or how I can make this happen...thanks!
Create a model that holds all the required data
public class NewModel {
public String NewName { get; set; }
public int NewAge { get; set; }
public double NewWeight { get; set; }
}
update the action to expect that data and annotate it with [FromBody]
[HttpPost]
public ActionResult<MyModelType> Post([FromBody] NewModel data) {
if (ModelState.IsValid) {
var model = MyList.AddItem(data.NewName, data.NewAge, data.NewWeight);
return model;
}
return BadRequest(ModelState);
}
Consumers of your service would then post a request with the required data in the body of the request and the model binder should populate the model in the controller action.
After reading https://www.strathweb.com/2017/07/customizing-query-string-parameter-binding-in-asp-net-core-mvc/
Binding on string array with comma-seperated attribute is a must for us. However, this article only describes how to do this on the method, with simple types. I would prefer to put a attribute on the property on some complex model.
Is this possible?
I can't get it to work otherwise then on the method.
---after edit---
I would like to get this
public class GetCatalogVehiclesRequest
{
[commaseparated]
public string[] Sorting { get; set; }
.....
}
instead of (on controller):
public async Task<IActionResult> Get([commaseperated]GetCatalogVehiclesRequest request, CancellationToken cancellationToken){...}
So on my model instead of an attribute on the parameter.
Any advice?
Thanks!
I am new to WebAPI and rest and am trying to do things correctly. By default if I were to access something such as User I would call api/user/5 if I wanted user 5. This would go to my User controller to Get(int num) I think. But I know I will often need other params passed as well. Currently I have Get(JObject data), but that data param is for other parameters. I will need other optional params whether I am sending an ID or wanting a list of everything. How do I go about organizing methods properly with WebAPI? Am I misunderstanding something?
To clarify:
This question is more about REST than dynamic objects, though they play a part:
How do I get a single resource vs a list of resources when I need additional params. I see those concepts as two separate methods, but the additional params complicate it in my mind when routing is involved.
Use attribute routing
For example -
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
or
[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }
if you need to return a list, create a method that returns a list, otherwise return the specific item requested
Look into using JToken or the even more dynamic 'dynamic' (Taken from here)
"
JSON and JavaScript is really dynamic, though, and often it's a hassle to try to "deserialize" really dynamic JSON objects into strongly-typed .NET structures. JSON.NET and ASP.NET Web API's model binding offer a happy medium - a middle ground - called JToken.
public class ContactController : ApiController
{
public JToken Post(JToken contact)
{
return contact;
}
}
Using JToken gives a dynamic container but also a DOM-like navigation model. But if that's not dynamic enough for me, why can't my method's parameter just take a "dynamic."
C# is statically typed, sure, but that doesn't mean I can't statically type something dynamic. ;)
Again, note the watch window.
Using dynamic to catch JSON post payloads
public class ContactController : ApiController
{
public dynamic Post(dynamic contact)
{
return contact;
}
}
"
I think you should make a new object for each WebAPI function that will handle the request. You can make the parameters optional with nullable properties.
[HttpPost]
public void SampleFunction(SampleFunctionModel model)
{
}
where SampleFunctionModel is:
public class SampleFunctionModel
{
public int? Id { get; set; }
public string Name { get; set; }
}
I have a small problem with the WebApi.
Problem:
If I want to post a model using JSON, I can add as many members I want, as long as the members defined in model are present.
Question:
How can I trigger an exception, if an undefined member is present in my Json object. Is this achievable without a custom JsonConverter?
What I'm looking for is a generic solution, not a convertion for every different model.
Example:
Model:
public class Person
{
[Required]
public string Name { get; set; }
}
Api Controller:
public class PersonController : ApiController
{
public HttpResponseMessage Post(Person person)
{
if (person != null)
{
if (ModelState.IsValid)
{
//do some stuff
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
Json posts (body)
{"Name":"Joe"} --> valid
{"Name":"Joe","InvalidMember","test","Name","John"} --> also valid. In this case I want to trigger an Exception. Because if you look at it, it doesn't match my modeldefinition exactly.
One thing you could try is playing around with this setting:
config.Formatters.JsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
It should give you an invalid model state when there are extra properties that aren't recognized in the JSON.