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?
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 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.
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.
I built a WCF service and there's a section that looks like this:
[ServiceContract]
public class Service {
[OperationContract]
public SomethingElse[] Method(Code a, params Something[] b) { ... }
}
[DataContract]
public class Something {
[DataMember]
public string Stuff {get;set;}
[DataMember]
public Status MyStatus {get;set;}
public string ServerSideField {get;set;}
}
[DataContract]
public class SomethingElse {
[DataMember]
public Status MyStatus {get;set;}
}
[DataContract]
public enum Status {
[EnumMember] WorksFine,
[EnumMember] NotWorking
}
[DataContract]
public enum Code {
[EnumMember] TypeA,
[EnumMember] TypeB
}
Now I am using it as a service reference for a C# client. For some reason, whenever I call Method, the MyStatus property inside the b parameter is always set to WorksFine, even if I set it to NotWorking. On the other hand, whenever I pass Code.TypeA or Code.TypeB for the a argument, the service always deserializes it correctly.
For the sake of due diligence, other posts on the subject of passing enums to WCF services refer to DataContract, EnumMember(Value="TypeA"), and ServiceKnownType so I gave all of those a shot. However, even when I use ServiceKnownType (like below), I am still encountering the same problem.
[ServiceContract]
[ServiceKnownType(typeof(Something)]
[ServiceKnownType(typeof(Status)]
public class Service {
[OperationContract]
public SomethingElse[] Method(Code a, params Something[] b) { ... }
}
This issue seems unusually obscure for something so basic. I tested passing back Status.NotWorking from the service as well and the client is able to see it, so this appears to be a one-way issue. Any suggestions?
EDIT 1:
Similar issue: WCF not deserializing value types. Mysterious behaviour
EDIT 2:
Judging from the lack of immediate response, I am going to include some more information in case some of it sticks.
I'm experiencing this problem on both .NET 4.5 and 4.0.
The service is hosted on IIS, has SSL, and custom authentication scheme.
There is also a FaultContract attribute on Method but I excluded it to make the example simpler.
Event Viewer says zilch. So does IIS logs.
Auto-generated service reference code in Reference.cs looks like this:
The enum:
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.18408")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.datacontract.org/2004/07/Service")]
public enum Status{ TypeA, TypeB }
The method:
// CODEGEN: Parameter 'MethodResult' requires additional schema information that cannot be captured using the parameter mode. The specific attribute is 'System.Xml.Serialization.XmlArrayAttribute'.
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/Service/Method", ReplyAction="http://tempuri.org/Service/MethodResponse")]
[System.ServiceModel.FaultContractAttribute(typeof(MyClientProject.Service.MyFault), Action="http://tempuri.org/Service/MethodMyFaultFault", Name="MyFault", Namespace="http://schemas.datacontract.org/2004/07/Service.MyFault")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
MyClientProject.Service.Method Method(MyClientProject.Service.MethodRequest request);
EDIT 3:
I built another web service consisting of just the code above, but it does NOT reproduce the behavior that I am seeing. My conjecture is that either some other code is zilching the DataContractSerializer, OR there is some relevant IIS/WCF setting, OR some unresolved data contract issues.
I also built another web client that connects to both webservices, and it is receiving the same results as the first.
EDIT 4
Intercepted request with Fiddler and it looked like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Method xmlns="http://tempuri.org/">
<a>TypeA</a>
<b><Something xmlns="http://schemas.datacontract.org/2004/07/TestService.Something">
<Stuff>mystuffvalue</Stuff>
</Something></b>
</Method>
</s:Body>
</s:Envelope>
So the enum is never being passed after all! How do I fix this contract mismatch?
EDIT 5
Forgot to mention that the web service had a reference to an ASMX service and was itself using XML serializer to communicate with that external service.
The key is here:
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
The XML Serializer is being used to generate the proxy instead of the DataContractSerializer. Did you accidentally specify the XmlSerializer? Are you trying to use an .asmx service?
Once you figure out what is causing the code to be generated using the XmlSerializer you'll have your answer, but it's not immediately evident from what you have posted.
For some unknown reason, the SOAP request made by the client was omitting the enum values that I needed, and consequently the server serialized those enums to their default values (the first enum definition on the list).
I fixed this issue by making omitted parameters required for the request body. This is the code that fixed it:
[DataContract]
public class Something {
[DataMember]
public string Stuff {get;set;}
[DataMember(IsRequired=true)] // just this 1 simple change!
public Status MyStatus {get;set;}
public string ServerSideField {get;set;}
}
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 );