I have a C# Web API, which has a method on a controller that takes a custom object. When calling this endpoint from my react application, I'm getting a 400 response from the API, with the error message below.
The categoryConfiguration field is required.
I understand that the API is failing to deserialize the request payload, but I have no idea why.
[HttpPost]
public async Task<IActionResult> CreateConfiguration(CategoryConfiguration categoryConfiguration)
{
return Ok(); // Never hits this breakpoint
}
I suspect it may have something to do with the custom object inheriting from an abstract class, although I have read similar posts indicating that it should work.
public class CategoryConfiguration : SystemConfiguration
{
public Guid CategoryId { get; set; }
public Category? Category { get; set; }
}
public abstract class SystemConfiguration
{
// Loads of properties.
}
The correct handling would be for the method to be executed, but currently, it doesn't actually reach the breakpoint in the controller.
This API has many other controllers which all work, in the same way, the only difference is the use of the abstract class. I can see that the object is being sent up through the browser.
I have tried using the from [FromBody] attribute which had no effect.
Any help would be appreciated.
Thanks.
I found out the issue was down to some backend validation rules (Fluent validation), basically, the timeout field was required to be greater than one of the nullable fields which caused the error code of
'Timeout must be greater than or equal to ''.
This resulted in a failed validation and rejected the request. I was able to find these error messages using Postman.
Related
I've created a WebAPI project in VS 2022, using .NET 6
I've annotated my model's Customer class CustomerId property with [MaxLength(5)] and [MinLength(5)] attributes.
[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase
{
private readonly NorthwindContext _ctx;
// ...
[HttpPost]
public Customer Insert(Customer customer)
{
// Method never called. Somewhere the pipeline instantly
// sends the HTTP 400 response with the correct error message.
if (!ModelState.IsValid)
{
}
If I call the API with invalid data, say 4 or 6 lenght CustomerId, then the Insert method never called. Somewhere the pipeline instantly sends the HTTP 400 response with the correct validation error message
Question
It is not clear, then when will be the ModelState.IsValid false? Can I configure the pipeline to allow the method to be called?
It is not clear, then when will be the ModelState.IsValid false?
Well, in a nutshell, in MVC controller; if you haven't set any annotation on property by default its required means not null. So if you don't pass the value your ModelState.IsValid will always be false. Usually Model state represents errors that come from two subsystems.
For instances, model binding and model validation which means request firstly goes to the model validator once it meet the condition then it route to the specific controller other than, controller doesn't get hitted.
Thus, your ModelState.IsValid would always be false when any property you would set required and that's value wouldn't be passed while you submitting request for your scenario Insert. So, yes any property without nullable ? would require to pass during request submission, other than your ModelState.IsValid would always be false.
Let's consider, your Customer Class you have set few validation constrains on CustomerId if this is not null; it will firstly reach to model binding and if it doesn't meet your condition it will throw validation message from there and it wouldn't hit controller.
So if you don't pass the value for non nullable property or your property violate any condition it will restrict you from submitting request.
Why then your ModelState.IsValid doesn't reach?
As you are using Web API controller and in Web API controllers don't have to check ModelState.IsValid if they have the ApiController attribute. In that case, an automatic HTTP 400 response containing error details is returned when model state is invalid.
Can I configure the pipeline to allow the method to be called?
Of course you can. In that scenario, you have to disable the automatic 400 behavior. You might know ConfigureApiBehaviorOptions within builder.Services.AddControllers and you will get a property called SuppressModelStateInvalidFilter we have to set this property to true. Please have a look on following code snippet:
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Output:
Note:
If you would like to know more details on Model state Validation and automatic 400 response you could check our official document here.
With the nullability in .Net 6/.Net 7, the data you send to the Web API has to match the model or this will happen.
public class MyModel
{
public int SomeId { get; set; }
public int? ForeignId { get; set; }
public string? SomeName { get; set; }
public string RequiredName{ get; set; }
}
MyModel myData = new()
{
SomeId = 5,
ForeignId = 9,
SomeName = "Test",
RequiredName = null
}
var myResult = MyWebAPICall(myData);
Since the RequiredName is defined as not nullable, the Web API will reject it and send back the 400.
You have to go through the models / DTO's and make sure what you defined matches your data source (database, etc.) so it will save properly and focus on what fields need to allow nulls. The nullability really forces you to go through everything.
I have a class with an enum and this class is used as an argument for an endpoint.
Class with Enum
public enum ChangeTime
{
Immediate, Later
}
public class Request
{
public ChangeTime ChangeTime {get; set; }
}
The Endpoint
[HttpPost("test")]
public async Task<ActionResult> Post([FromBody]Request request)
The Request
{
"currency": "2"
}
When I send this request rather than returning an error as the enum for that value does not exist it sets the ChangeTime variable as Immediate. I have searched the web for this and seen various documents stating that this has been resolved in version 5.0 of .net which rejects enums with undefined values, but version 3.1 model binding succeeds. A work around is I could convert changetime to a string type and then do the conversion, check and throw error manually later on in a service or whatever as part of validation but will incur unnecessary performance penalties on a request that will rejected anyway. Is there a cleaner way around this?
I have implemented a logic for Response in my Web API projects, and response object looks like this:
public class Response
{
public bool IsSuccess { get; set; }
public object Data { get; set; }
public string Message { get; set; }
}
IsSuccess indicates whether the call is success or failure.
Data to be used for further processing when IsSuccess is true
Message to be used for further processing when IsSuccess is false
I have few questions in mind. Please help me with this.
Is it ok to use custom objects over the status codes?
What disadvantages this approach will it have?
Should I switch to status codes / ControllerBase return methods instead?
HTTP has standardized the structure for representing request and response. To that extent, a response has 3 parts - Status, Headers & Body. Please refer here. Each part has a definite purpose. Since the question is on status codes, I'll restrict myself to it.
The primary purpose of status codes is to indicate whether the request has been processed correctly or not. The automation systems and scripts depend on it for branching their decisions.
It is important to remember that the model defined will be part of the response body. This means that the framework API is built on will still include a default response code - usually, it's a 200 OK. If the IsStatus attribute is supposed to act as a replacement for the Status code, if proper care is not taken, the status code and IsStatus may show different values when the API errors out.
Finally, I think you are better off representing an ErrorResponse instead. Something in the lines of -
public class ErrorResponse{
// Application or Service specific code
// to identify the error
public string Code {get; set;}
// A link to detailed description of the
// of the error
public Uri Info {get; set;}
// A high level friendly message about the
// error
public string Message {get; set;}
}
HTTP status codes are a standard. See e.g. this docu. Nobody is expecting to get a 200 OK with IsSuccess set to false, because 200 OK is a success. 3xx are redirects, 4xx are client errors, 5xx are server errors. Stick to that, do not reinvent the wheel, you'll confuse the clients of your API.
However, you can and it's a good practice to include more custom information into your response. Define your response e.g. like this:
public class ErrorDetails
{
public string Message { get; set; }
}
Than set the response code directly on the response object of .net, not on your own:
var error = new ErrorDetails { ... };
context.Response.StatusCode = 4xx / 5xx; // and not 200, it's an error! context is HttpContext
await context.Response.WriteAsync(JsonConvert.SerializeObject(error));
Controller methods already have build-in methods for this, so no need to do it the hard way as in the example above either. E.g.:
this.BadRequest(error);
This will set a 404 on the response object and pass your error object in the payload as additional data. There is this.Ok() and a bunch of other methods for each situation.
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.
Assuming a ApiController with an action having multiple arguments:
[HttpPost]
public void AddAddress( Person person, Address address, int simple )
Or something like that.
Now I try to send a post request with content type application/json and json like this:
{
person: {...}, address: {..}, simple: 1
}
just assume it's valid json ;)
or in a json array like this:
[person, address, simple]
But WebApi won't recognize this and tell me it can't find the action, as it doesn't support multiple parameters in a json request.
I'm doing this from a C# client using HttpClient and we'd like to do post requests using json. I wanted to use the WebApi.Client package, which provides several useful extensions to the HttpClient, but we have a portable library project(which webapi client doesn't support).
How would I go about supporting this feature?
I tried to use an custom ActionFilter, which never get's to run as it can't find the action beforehand. I tried MediaTypeFormatter which doesn't know the context and the arguments, and also I tried ModelBinder, which also seems only on a per argument basis and doesn't always get executed.
Thanks!
The built-in parameter binding strategy in ASP.NET Web API with formatters only supports a single complex argument mapped to the request body. In your case, you are trying to map at least two complex arguments. The simplest solution without extending this infrastructure would be to create a new model class containing those arguments as properties
public class AddressModel
{
public Person Person { get; set; }
public Address Address { get; set; }
public int Simple { get; set; }
}
And use that model in your controller action
public void AddAddress( AddressModel model );