I am creating an asp.net core web api application.
Where I am try to validate my models using fluent validation.
this is my model and validator.
public class Data
{
public string Name { get; set; }
public int Age { get; set; }
}
public class DataValidator : AbstractValidator<Data>
{
public DataValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(5);
RuleFor(x => x.Age)
.LessThan(80);
}
}
Everything works fine.
Fluent validation returns all the validations together except following case.
When my request contains following JSON, Fluent Validation doesn't get hit.
Asp.net core model validation is take place.
in that case I am getting single validation error.
{
"name": 123,
"Age" : 100
}
I got following validation message.
The JSON value could not be converted to System.String. Path
How to override above default message?
Is there any way to handle above validation in Fluent Validation?
I want both 'name' and 'age' validation messages together.
Let's look at your binding model:
public class Data
{
public string Name { get; set; }
public int Age { get; set; }
}
Here, Name is a string while Age is an int.
Your validator is just fine, otherwise you should get a compilation error when you build the app.
Now, let's look at the JSON:
{
"name": 123,
"Age" : 100
}
Instead of using name, you should use Name. Plus, the value of Name should be a string, i.e. "123" instead of 123. i.e.
{
"Name": "123",
"Age": 100
}
After that, you should be able to get the expected validation errors.
Related
When I am trying to send PUT or PATCH request with JSON in body I am getting object with default values.
Everything is fine with get requests. And PUT request is working if specify data as parameters in URL.
I am using .NET Core and Microsoft.AspNetCore.OData 7.5.0 NuGet package
The example:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
public class OdataModelConfigurations : IModelConfiguration
{
public void Apply(ODataModelBuilder builder, ApiVersion apiVersion)
{
var product = builder.EntitySet<Product>("Products").EntityType;
product.HasKey(p => p.Id);
product.Property(p => p.Name);
}
}
[ODataRoutePrefix("Products")]
public class ProductController : ODataController
{
[ODataRoute]
[HttpPut]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public async Task<IActionResult> Put([FromBody] Product update)
{
// some code omitted
}
}
I'v tried to use different body content and to add different headers (Specify OData-Version for example).
Here is one of body examples that I'v tried to use:
{
"#odata.context": "https://localhost:5001/odata/$metadata#Product",
"Name": "put tested",
"Id":"1"
}
Or another one:
{
"#odata.type": "#ODataAPI.Models.Product",
"Name#odata.type": "String",
"Name": "patch tested"
}
Everything works like a charm without Versioning and Swagger. Even if I am just sending simple body:
{
"Name": "put tested",
"Id":1,
"CategoryId":1
}
Was able to find next one project that shows how it is possible to combine OData and Swagger:
https://github.com/microsoft/aspnet-api-versioning/tree/master/samples/aspnetcore/SwaggerODataSample
I am using .NET Core 2.2 with Web API. I have created one class, i.e., as below:
public class NotificationRequestModel
{
[Required]
public string DeviceId { get; set; }
[Required]
public string FirebaseToken { get; set; }
[Required]
public string OS { get; set; }
public int StoreId { get; set; }
}
Using the above class I have created one method. Now I want to return a custom object, but it's returning its own object.
API method is:
public ActionResult<bool> UpdateFirebaseToken(NotificationRequestModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
var result = _notificationService.InsertOrUpdateFirebaseToken(model);
return Ok(result);
}
Here FormatOutput method is format the output.
protected Base FormatOutput(object input, int code = 0, string message = "", string[] details = null)
{
Base baseResult = new Base();
baseResult.Status = code;
baseResult.Error = message;
baseResult.TimeStamp = CommonHelper.CurrentTimeStamp;
baseResult.Code = code;
baseResult.Details = details;
baseResult.Message = message; //Enum.Parse<APIResponseMessageEnum>(code.ToString(), true); // (enum of code get value from language)
return baseResult;
}
But the issue is it returns:
{
"errors": {
"DeviceId": [
"The DeviceId field is required."
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "80000049-0001-fc00-b63f-84710c7967bb"
}
I want to customize this error with my model. I need error message and details from return output and passed it to my model. How can I do that? I had try to debug my code and found that breakpoint on API method is not calling. So I can't handle my custom method. Is there any solution? What am I doing wrong?
When using a controller with the ApiController attribute applied, ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body. As such, your conditional testing ModelState.IsValid is essentially always false (and therefore not entered) because the only requests that will ever get this far are valid ones.
You could simply remove the ApiController attribute, but that removes a bunch of other beneficial stuff the attributes adds as well. The better option is to use a custom response factory:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new BadRequestObjectResult(actionContext.ModelState);
});
That's essentially what's happening by default, so you'd simply need to change the action provided there accordingly to customize it to your whims.
As Chris analyzed, your issue is caused by Automatic HTTP 400
responses.
For the quick solution, you could suppress this feature by
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
For an efficient way, you could follow the suggestion from Chris, like below:
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
//options.SuppressModelStateInvalidFilter = true;
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
return new BadRequestObjectResult(FormatOutput(modelState));
};
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And, there isn't any need to define the code below any more in your action.
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
I am using .NET Core 2.2 with Web API. I have created one class, i.e., as below:
public class NotificationRequestModel
{
[Required]
public string DeviceId { get; set; }
[Required]
public string FirebaseToken { get; set; }
[Required]
public string OS { get; set; }
public int StoreId { get; set; }
}
Using the above class I have created one method. Now I want to return a custom object, but it's returning its own object.
API method is:
public ActionResult<bool> UpdateFirebaseToken(NotificationRequestModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
var result = _notificationService.InsertOrUpdateFirebaseToken(model);
return Ok(result);
}
Here FormatOutput method is format the output.
protected Base FormatOutput(object input, int code = 0, string message = "", string[] details = null)
{
Base baseResult = new Base();
baseResult.Status = code;
baseResult.Error = message;
baseResult.TimeStamp = CommonHelper.CurrentTimeStamp;
baseResult.Code = code;
baseResult.Details = details;
baseResult.Message = message; //Enum.Parse<APIResponseMessageEnum>(code.ToString(), true); // (enum of code get value from language)
return baseResult;
}
But the issue is it returns:
{
"errors": {
"DeviceId": [
"The DeviceId field is required."
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "80000049-0001-fc00-b63f-84710c7967bb"
}
I want to customize this error with my model. I need error message and details from return output and passed it to my model. How can I do that? I had try to debug my code and found that breakpoint on API method is not calling. So I can't handle my custom method. Is there any solution? What am I doing wrong?
When using a controller with the ApiController attribute applied, ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body. As such, your conditional testing ModelState.IsValid is essentially always false (and therefore not entered) because the only requests that will ever get this far are valid ones.
You could simply remove the ApiController attribute, but that removes a bunch of other beneficial stuff the attributes adds as well. The better option is to use a custom response factory:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new BadRequestObjectResult(actionContext.ModelState);
});
That's essentially what's happening by default, so you'd simply need to change the action provided there accordingly to customize it to your whims.
As Chris analyzed, your issue is caused by Automatic HTTP 400
responses.
For the quick solution, you could suppress this feature by
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
For an efficient way, you could follow the suggestion from Chris, like below:
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
//options.SuppressModelStateInvalidFilter = true;
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
return new BadRequestObjectResult(FormatOutput(modelState));
};
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And, there isn't any need to define the code below any more in your action.
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
I've followed this guide for getting my localization to work with my validation. But either I've done something wrong, or it isn't supported.
I have a Controller:
[HttpPost]
public IActionResult Post([FromBody]Customer value)
{
if (ModelState.IsValid)
{
return Ok("Good job!");
}
return BadRequest(ModelState);
}
I register my localizations like so:
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
And my model:
public class Customer
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
Here's my resx and my directory (all resx look alike):
What I want is to let the resource-file determine what message is passed for the propertyname when it's invalid.
Making this POST:
{
"firstname":"John"
}
Will currently return:
{
"LastName": [
"The LastName field is required."
]
}
Since I created a Resource for the Customer model I want the result to return this:
{
"LastName": [
"The Surname field is required."
]
}
I know I can use Display like this:
[Display(ResourceType = typeof(Models_Customer), Name = "FirstName")]
...but I thought that the entire point of naming the resource-files based on the model was to avoid it.
I have the same problem. But I guess, it works only with resx filnames like this:
Models.Customer.en-GB.resx
I tried it like you but this file name works only with culture region information.
Greeds
P.S.: I'm open for a solution like Models.Customer.en.resx
I'm looking to do partial updates on a web api controller action by using the Delta wrapper.
I have a model like such:
public class Person
{
public Guid PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsActive { get; set; }
public int NumVacationDays { get; set; }
public double Salary { get; set; }
}
I have the api controller like such:
public void Put(Delta<Person> person)
{
var p = person.GetEntity();
Person existingPerson = _repository.Get(p.PersonId);
person.Patch(existingPerson);
_repository.Update();
return;
}
I make the call to the web api like such (using fiddler)
url: http://localhost:49933/api/Person (PUT)
Response Body
{
"PersonId": "b269c49f-8a90-41d6-b102-7cfba3812b1c",
"FirstName": "sample string 2",
"LastName": "sample string 3",
"IsActive": true,
"NumVacationDays": 5,
"Salary": 6.1
}
The controller is hit and al
l the data is populated other than the NumVacationDays (which is 0) and the PersonId (which defaults to 00000000-0000-0000-0000-000000000000)
Does anyone know why the GUIDs and int fields are not populating from the json?
This problem is mentioned in this bug: http://aspnetwebstack.codeplex.com/workitem/562 ...Claims to be fixed but still exists in the 4.0 just released.
The problem being that Newtonsoft Json deserializes a number as Int64 which fails test that IsAssignable to an int, and so it is skipped. Similar issue for guids as strings.
You are supposed to be able to fix this by using the OData media type formatters, which are enabled by deriving from ODataController instead of ApiController. However this had no effect for me - int values still do not work (but when I change the data type to Int64, it works).
I would love to see a working example of posting json with a patch delta that contains an int.
I can venture a guess for what is happening with PersonId but NumVacationDays cannot be explained. My guess is that PersonId is the key property for the Person entity and by default the ODataFormatter does not patch key properties. If you want that behavior, you can change the setting on ODataMediaTypeFormatter.PatchKeyMode to Patch.
Also, it would be interesting to see the value of person.GetChangedPropertyNames() in the action to see if PersonId and NumVacationDays actually show up there or not.