FromBody annotation is not able to access field from request body - c#

I created a sample .NET Core Web API and provide an endpoint to create a new task for yourself. So the endpoint would be
POST ...baseUrl.../me/tasks
The method for this is
[HttpPost("tasks")]
public async Task<ActionResult<Task>> CreateUserTaskAsync([FromBody] CreateUserTaskBodyDto createUserTaskBodyDto)
{
// ...
}
As you can see the parameter is a DTO which deals with the request validation for the body. This DTO currently has one field but there might be more fields later on
public class CreateUserTaskBodyDto
{
[Range(1, 30)]
public string Name { get; set; }
}
When calling the url with the following body
{
"name": "abc"
}
I get a 400 with the error
"errors": {
"Name": [
"The field Name must be between 1 and 30."
]
}
(I also tried it by renaming "name" to "Name"). I'm testing the API with Postman, this screenshot shows my request setup
Does someone know what's wrong or missing here?

If you remove the
[Range(1, 30)]
attribute it should work
Also if you want to validate the length of the name property you should use StringLenghtAttribute
[StringLength(30, MinimumLength = 1, ErrorMessage = "Name must be between 3 and 50 character in length.")]

According to Microsoft docs Range Attribute
Range attribute specifies the numeric range constraints for the value of a data field.
If you want to specify max and min string length. See MinLengthAttribute
[MinLength(1)]
[MaxLength(30)]
public string Name { get; set; }

Related

C# REST API - how to extend the model state error with an error code

I'm creating an ASP.net Core Web API (.net 5) to serve data to a Single Page Application (SPA). I'm using DataAnnotations (System.ComponentModel.DataAnnotations) to carry out model state validation in a very standard way in my Controller. I want to ensure my error responses come back in a very consistent manner (and are translatable in the front end!).
MVC Controller
Example controller is as follows:
[ApiController, Route("[controller]")]
public class AgencyController : Controller
{
private readonly IOptions<ApiBehaviorOptions> _apiBehaviorOptions;
public AgencyController(IOptions<ApiBehaviorOptions> apiBehaviorOptions)
{
_apiBehaviorOptions = apiBehaviorOptions;
}
[HttpPost]
[ProducesResponseType(typeof(void), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)]
public IActionResult Create([FromBody] ExampleCreateModel createModel)
{
// If the email already exists, add the custom error.
var emailExists = EmailAlreadyExists(createModel.Email);
if (emailExists)
{
ModelState.AddModelError("Email", "Email already exists");
return _apiBehaviorOptions.Value.InvalidModelStateResponseFactory(ControllerContext);
}
// Return some relevant status...
return Ok();
}
// Real verification implementation would go here....
private bool EmailAlreadyExists(string email) => true;
}
This sample code above demonstrates a controller that takes takes the POCO model (shown below) and if it passes the attribute validation, carries out additional inline validation to ensure the email address doesn't already exist (pseudo code for the email exists check).
Sample Model
public class ExampleCreateModel
{
[Required]
public string Name { get; set; } = "";
[Required]
[EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; } = "";
}
Error Response Format
Validation failure on the model shown above yields the standard error object from the MVC app in this format (sample):
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-6b30709ed71b7347afd41e0ed58e1ccb-e3ede2ff7591a24d-00",
"errors": {
"Email": [
"Email already exists"
]
}
}
this format is consistent for both regular attribute validation and the custom inline validation in the code above
My Question
The advantage of using the built in errors is consistency (and follows a standard pattern). BUT one down side for me is that I'd like to return error codes as well as text as part of the error object, so the UI can translate them but it doesn't seem to easily support that i.e. an "Email already exists" error could be a 3001 error and the UI could show a 3001 in various languages.
Is there any standard way to use the existing DataAnnotation attributes to include additional information? Such that the POCO model would become something like this:
public class ExampleCreateModel
{
[Required(ErrorCode = 3000)]
public string Name { get; set; } = "";
[Required]
[EmailAddress(ErrorMessage = "Invalid email format", ErrorCode = 3001)]
public string Email { get; set; } = "";
}
Resulting in an error object similar to this:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-6b30709ed71b7347afd41e0ed58e1ccb-e3ede2ff7591a24d-00",
"errors": {
"Email": [
{ "message": "Email already exists", "errorCode": 3002 }
]
}
}
Just to restate for clarity - my aim here is to give the UI an opportunity to easily translate error code, while achieving error consistency using the out-of-the-box error response.
Thanks in advance for any pointers!
Is there any standard way to use the existing DataAnnotation attributes to include additional information?
No.
I think that the simplest (but pretty hacky) approach could be to include the error code in the message string itself and then extract it client-side:
public class ExampleCreateModel
{
// ...
[Required]
[EmailAddress(ErrorMessage = "ErrorCode:3001 - Invalid email format")]
public string Email { get; set; } = "";
}
You could even go one step further and put a serialized JSON object in there to make it more structured (but pay attention to escaping). Of course you should centralize the logic of creating such 'custom' error messages into some kind of static utilty class in order to make it consistent throughout your application.

Sending C# Reserved Word as Property of PostData Api

I have some JSON that i am sending over to my C# API and it looks like the following
{
"currency": "BTC",
"amount": "0.00049659",
"type": "bankToExchange"
}
The issue is when the model arrives in my controller, the type property is changed to #type which is making the post request fail.
The API I am trying to connect to uses type, so this cannot be changed. The post works in Postman, so is there a work around for this?
Add the DataMember name property on your type using JsonProperty:
[DataMember(Name = "#type")] //if not using NewtonSoft
[JsonProperty("#type")] //if using NewtonSoft
public string type { get; set; }
Use Data member attribute with property name. You can use by creating class for your json, as follows
[DataContract]
public class Sample{
[DataMember(Name = "#type")]
public string Type{get;set;}
}
You can try with another approach as well, which is elegant and more meaning full if you add comment for appending # before property name :
public class Sample{
public string #type{get;set;}
}
For reference: object to deserialize has a C# keyword

EnumDataType() attribute validation error message not displaying

In my .net core 2.0 Web API I am using EnumDataType() validation attribute on my model property. When the validation fails the custom error message is empty. I am not sure why it is happening -
[EnumDataType(typeof(MyEnum), ErrorMessage = "Custom Error Message")]
public MyEnum MyEnumProp {get; set;}
I checked the other properties where I have [Required], [MinLength] and all are generating custom error messages. Am I doing something wrong? Is there any other approach?
It's a frequent confusion between errors detected during deserialization and validation stages.
Say you have the following enum:
public enum MyEnum
{
None,
Value1,
Value2
}
and the following model:
public class TestModel
{
[Required]
public int? Id { get; set; }
[EnumDataType(typeof(MyEnum), ErrorMessage = "Custom Error Message")]
public MyEnum MyEnumProp { get; set; }
}
When you post the data:
{
"Id": 123,
"MyEnumProp": "UnexistingEnumValue"
}
the error will happen during deserialization stage (in Json.NET for this case). Deserializer just has no way to convert string "UnexistingEnumValue" to some of values from MyEnum.
In this case deserializer will register following model binding error: Requested value 'UnexistingEnumValue' was not found.
ModelState.IsValid will be set to false, however value of MyEnumProp will be left at its default value of MyEnum.None. Validation performed by EnumDataType attribute will not detect any error, because MyEnum.None is a valid value for MyEnum. That is why you will not see "Custom Error Message" in ModelState errors.
Now if you post the following data:
{
"Id": 123,
"MyEnumProp": 5
}
No error will happen during deserialization stage, because following assignment is pretty legal even if it does not make much sense:
MyEnum v = (MyEnum)5;
So deserializer will not detect any errors. However now EnumDataType validation comes into play. And it detects that 5 is not a valid value for MyEnum. ModelState.IsValid is set to false and the error message specified in EnumDataType.ErrorMessage is registered ("Custom Error Message").
If you want to have the same custom message for both deserialization and validation errors, you should go up to the level of deserializer (Json.NET) and use its extensibility points for this purpose.
Change to int the property MyEnumProp
public class TestModel
{
[Required]
public int? Id { get; set; }
[EnumDataType(typeof(MyEnum), ErrorMessage = "Custom Error Message")]
public int MyEnumProp { get; set; }
}

Why getting exception message as null

I have ASP.Net WebAPI based application. Below is my DTO.
public class CustomerTO
{
[Required(ErrorMessage="Name required")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Name invalid")]
public string Name { get; set; }
[Required(ErrorMessage="CountryId required")]
[Range(1,250,ErrorMessage="CountryId invalid")]
public int Country { get; set; }
}
My API Controller.
[HttpPost]
[AllowAnonymous]
public HttpResponseMessage Post([FromBody]CustomerTO model)
{
if (ModelState.IsValid)
{
//my stuff
}
else
{
var msg = ModelState.SelectMany(s => s.Value.Errors).FirstOrDefault().ErrorMessage;
}
}
If user passed any of the required field as Null, it returns the right Error message mentioned in the Data Annotations while if I pass string for CountryId, it enters into else condition(*ModelState.IsValid = false*)
But the ErrorMessage is empty.
While If I debug & put the below statement in quick watch.
msg = ModelState.SelectMany(s => s.Value.Errors).FirstOrDefault().Exception.Message;
It returns - Could not convert string to integer: en. Path 'Country', line 6, position 14.
Why in this scenario, I am not getting the Error message as CountryId Invalid
How do I get this?
Using a RegularExpressionAttribute does not prevent RangeAttribute from throwing an "Could not convert string to integer" exception. So just filter the right one:
var msg = ModelState.SelectMany(s => s.Value.Errors)
.FirstOrDefault(_ => _.Exception == null)
.ErrorMessage;
As far as I know, it is a common problem: SO question 1, SO question 2.
According to code, from any validation attribute there is creating a wrapper, derived from RequestFieldValidatorBase. Each wrapper calls IsValid method of ValidationAttribute. In the method Validate of RequestFieldValidatorBase passing form value for validation.
So, RequiredAttribute does not fails, because form value is not empty and is not null, and RangeAttribute does not fails, because it has problems converting this value to int.
To achieve your desired behaviour it is recommend to create your own validation attribute or use a RegularExpressionAttribute. You can take a look at this answer.
I believe the range validators dont cater for string entry, i.e. they only fire if it's a valid integer. It doesn't enforce the type passed in.
Try changing your annotation to a regex.
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Country code invalid")]
public string Country { get; set; }
With reference to this link Integer validation against non-required attributes in MVC
As dirty patch, I modified my property from int to string & decorated it with Regular Expression.

WebAPI - Array of Objects not deserializing correctly on server side

In the client-side, I am using AngularJS and in the server-side I am using ASP.NET WebAPI.
I have two view models, ProductCriteriaViewModel and SimpleDisplayFieldViewModel:
public class ProductCriteriaViewModel
{
public int ID { get; set; }
public int? UserSearchID { get; set; }
public bool? Enabled { get; set; }
public SimpleDisplayFieldViewModel Property { get; set; }
public string Operator { get; set; }
public string CriteriaValue { get; set; }
}
public class SimpleDisplayFieldViewModel
{
public string Name { get; set; }
public string Value { get; set; }
public string PropertyType { get; set; }
}
In Angular, I submit a POST request to a WebAPI controller action with the following signature:
public IList<...> FindProducts(List<ProductCriteriaViewModel> criteriaVM, bool userFiltering)
{
...
}
In testing, I tried to send an array of Product Criterias, and checked Fiddler to see what the array looked like in the body of the POST request when it was being sent to the server. This is what the array looked like:
[
{"Enabled":true,
"Operator":"Less than",
"Property":
{"$id":"2",
"Name":"Copyright Year",
"Value":"Basic",
"PropertyType":null},
"CriteriaValue":"2013",
"IsNew":true},
{"Enabled":true,
"Operator":"Greater Than",
"Property":
{"$id":"2",
"Name":"Copyright Year",
"Value":"Basic",
"PropertyType":null},
"CriteriaValue":"1988",
"IsNew":true}
]
The above array has the correct values, however the result of deserialization on the server-side is incorrect. This is where it gets strange.
After the server deserializes the array and arrives in the controller action, the first element in criteriaVM is correct, all the values are set properly. However the second element is incorrect, CriteriaValue and Property are nulled out:
This issue only occurs whenever I choose the same search property for more than one criteria (i.e. Copyright < 2013 and Copyright > 1988). However, if I choose different properties (i.e. Copyright < 2013 and Price > 20), then all elements in the resulting criteriaVM are correctly initialized.
I do not understand what could be causing this issue. Why are only CriteriaValue and Property set to null in the second element of the List? Why does this issue only occur when I choose multiples of the same search properties?
Json.NET uses the keywords $id and $ref in order to preserve object references, so you are having troubles with your deserialization because your JSON has "$id" in the "Property" object. See this link for more information about object references.
In order to fix your deserialization issues, you can add the following line in the Register method of your WebApiConfig.cs class
config.Formatters.JsonFormatter.SerializerSettings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;
If your Web Api project does not include a WebApiConfig.cs class, simply add the configuration in your Global.asax:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;
Now your object in the web api method should look like this:

Categories