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
Related
I'm trying to configure ASP.NET Core 5's ForwardedHeadersMiddleware from appsettings.config. I'm having trouble to set KnownProxies (IList<IPAddress> KnownProxies { get; }) and it keeps reverting back to the default value. I assume it has to do with the options machinery not knowing how to convert the string to an IPAddress, or KnownProxies only having a getter.
{
"ForwardedHeaders": {
"ForwardedHeaders": "All"
"KnownProxies": ["10.0.0.1"]
}
}
services.Configure<ForwardedHeadersOptions>(Configuration.GetSection("ForwardedHeaders"));
How can I achieve what I want, without doing the parsing manually?
Can I specify the mapping somewhere generic?
Why doesn't this throw an exception that some of my configuration could not be parsed / is invalid?
I may propose my recipe for this:
Define your own options class like the following:
public class ForwardedForKnownNetworks
{
public class Network
{
public string Prefix { get; set; }
public int PrefixLength { get; set; }
}
public List<Network> Networks { get; set; } = new List<Network>();
public List<string> Proxies { get; set; } = new List<string>();
}
To make your code shorter and more agile you may need kind of helper method to resolve hostnames and/or convert parse IP addressed strings:
public static class Extensions
{
public static IPAddress[] ResolveIP(this string? host)
{
return (!string.IsNullOrWhiteSpace(host))
? Dns.GetHostAddresses(host)
: new IPAddress[0];
}
}
When configuring your services, you can add something similar to the following:
var forwardedForKnownNetworks = builder.Configuration.GetSection(nameof(ForwardedForKnownNetworks)).Get<ForwardedForKnownNetworks>();
_ = builder.Services.Configure<ForwardedHeadersOptions>(options => {
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
forwardedForKnownNetworks?.Networks?.ForEach((network) => options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse(network.Prefix), network.PrefixLength)));
forwardedForKnownNetworks?.Proxies?.ForEach((proxy) => proxy.ResolveIP().ToList().ForEach((ip) => options.KnownProxies.Add(ip)));
});
And finally your appsettings.json may appear like the following:
{
"ForwardedForKnownNetworks": {
"Networks": [
{
"Prefix": "172.16.0.0",
"PrefixLength": 12
},
{
"Prefix": "192.168.0.0",
"PrefixLength": 16
},
{
"Prefix": "10.0.0.0",
"PrefixLength": 8
}
],
"Proxies": ["123.234.32.21", "my.proxy.local"] // Here you can mention IPs and/or hostnames as the ResolveIP() helper will take care of that and resolve any hostname to its IP(s)
}
}
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 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.
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));
}