Stop validation execution when one of the validation fails on a property that has multiple validations - asp.net-core-webapi

Following this I added the two custom validations on the DTO:
public class InputFile
{
[Required(ErrorMessage = "Please select a file.")]
[DataType(DataType.Upload)]
[MaxFileSize]
[AllowedExtensions]
public IFormFile FileToUpload { get; set; }
}
In the controller:
[HttpPost("Upload")]
public async Task<IActionResult> Upload(FileToUpload fileToUpload)
{
if (!ModelState.IsValid)
return BadRequest(new FailureResoponse
{
Error = string.Join(" | ", ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage))
});
... rest of the code removed
}
This code will have error messages from both the validations even if one of them fails since both the custom attributes are run.
So the response will have "Error 1 | Error 2" even when either one of the validation fails.
Is there a way to not execute the "MaxFileSize" when "AllowedExtensions" fails or vice-versa.
Using this in .Net Core 3.1
Thanks in advance.

Related

Use single error message template for all invalid properties

Imagine a razor page with a Form that have many inputs that user fills them.
with post method when it wants to validate the posted model like this :
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page(model);
}
}
If for example 3 property of that model (with names : a,b,c) are not valid, it turns back to the razor view and shows the error (because of asp-validation-for for each property) like this :
The a field is required.
The b field is not a valid e-mail address.
The c field is required.
I want to show a specific error for all of them like this :
This Input is not valid.
This Input is not valid.
This Input is not valid.
I know I can use (ErrorMessage ="") for each of them separately, but its not logical in big size! is there any way to show a specific massage for all of invalid ModelStates?
Edit:
For example before showing errors in View, change their error message like this :
#foreach (var error in modelStateErrors)
{
error.text = "Fill it";
}
I created a solution with an extension method for ModelState.
It basically removes any errors from the state and adds them back with the desired message.
Create a ModelStateExtensions.cs in your namespace:
public static class ModelStateExtensions
{
public static void SetAllErrorMessages(this ModelStateDictionary modelState, string errorMessage)
{
foreach (var state in modelState)
{
if (state.Value.Errors.Count > 0)
{
modelState.Remove(state.Key);
modelState.AddModelError(state.Key, errorMessage);
}
}
}
}
Then if your ModelState is invalid you can transform the message before returning the page:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
ModelState.SetAllErrorMessages("Your message here");
return Page(model);
}
}
I know I can use ErrorMessage for each of them separately, but its not
logical! is there any short way to show a specific massage for all of
invalid ModelStates?
As for this issue, I think the easiest way to display the error message is using the ErrorMessage, If you want to display them at together, you could use the asp-validation-summary attribute, like this:
<div asp-validation-summary="All" class="text-danger"></div>
If you don't want to use the above method, you could also get the invalid fields from the ModelState dictionary, then, re-generate the error message. code like this:
public IActionResult OnPostAsync()
{
if (!ModelState.IsValid)
{
//get the new error message, you could also get all inValid fields.
var messages = ModelState.Keys
.SelectMany(key => ModelState[key].Errors.Select(x => string.Format("The {0} is invalid", key)))
.ToList();
ViewData["message"] = messages; //transfer the error message to the view
return Page();
}
return RedirectToPage("./Index");
}
View code (display the error message(without using asp-validation-for and asp-validation-summary)):
<div class="form-group">
#if (ViewData["message"] != null)
{
foreach (var item in (List<string>)ViewData["message"])
{
<span class="text-danger">#item</span><br/>
}
}
<div id="debug">
</div>
</div>
The output as below:
[Note] The above method is the server side validation. If you want to achieve the same behavior using Client validation, you have to get the client side validation result using JavaScript, and then generate the new error message.
So, in my opinion, I suggest you could try to use the first method (using Error Message and asp-validation-summary) to display the error message, and by using the Error Message for each of properties separators, user could easier to understand the validation rules.
If you don't want to make changes to each and every Razor Page, you can use a Page Filter to clear and rename the error messages automatically.
Here's an example Page Filter:
public class ModelStatePageFilter : IPageFilter
{
public void OnPageHandlerExecuted(PageHandlerExecutedContext ctx) { }
public void OnPageHandlerExecuting(PageHandlerExecutingContext ctx)
{
foreach (var (k, v) in ctx.ModelState
.Where(x => x.Value.ValidationState == ModelValidationState.Invalid))
{
v.Errors.Clear();
v.Errors.Add("This Input is not valid.");
}
}
public void OnPageHandlerSelected(PageHandlerSelectedContext ctx) { }
}
You'll need to register this Page Filter in Startup.ConfigureServices. Here's an example of how to do that:
services.AddRazorPages()
.AddMvcOptions(o => o.Filters.Add(new ModelStatePageFilter()));
You can use the Validation Summary (see : https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-validation-tag-helpers).
#model RegisterViewModel
<form asp-controller="Demo" asp-action="RegisterValidation" method="post">
<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>
If you want to change the displayed error message, you can do it in your ViewModel:
[Required(ErrorMessage = "This Input is invalid")]
public string Email { get; set; }
[Required(ErrorMessage = "This Input is invalid")]
public string Password{ get; set; }

Unexpected character encountered while parsing Value asp.net Core

this is i am trying to do but getting this error
Unexpected character encountered while parsing value
var searchModel = Newtonsoft.Json.JsonConvert.DeserializeObject<EmployeeSearchModel>(filter);
Model
public class EmployeeSearchModel
{
public string EmployeeNameSearch { get; set; } = null;
public string SearchFilter { get; set; } = null;
}
Error Detail
I suspect filter is not a valid JSON.
In fact, the exact error can be reproduced by the following code:
Newtonsoft.Json.JsonConvert.DeserializeObject("a");
//Error: Unexpected character encountered while parsing value: a. Path '', line 0, position 0.
coming string in filter variable
I believe the translation of what you said should be "The incoming filter variable is a string".
When receiving this error it can also mean your controller's action method has not been setup to take in a specific class to deserialize to.
For example this will fail with your error message:
public IActionResult Post([FromBody] string filter)
{
var searchModel = Newtonsoft.Json.JsonConvert.DeserializeObject<EmployeeSearchModel>(filter);
But the following will succeed because we have identified a specific object to deserialize and do not have to call Newtonsoft because .Net Core has deserialized it for us:
public IActionResult Post([FromBody] EmployeeSearchModel searchModel)
{
If (searchModel.EmployeeNameSearch == "OmegaMan")
...
So make sure your JSON incoming body is the same as the class.

Web API 2 does not process PATCH requests for Integers

I'm having a problem with Web API 2 (.net 4.5.1) in that it seems to ignore PATCH requests where the property is an integer, but processes other types without a problem (I've tested string and decimal).
I’ve setup an unsecured test API with a 'products' controller at http://playapi.azurewebsites.net/api/products. If you do a GET to that URL, you’ll get something like this product back:
{"Id": 1,"Name": "Xbox One","Category": "gaming","Price": 300,"Stock": 5}
‘Name’ and ‘Category’ are both strings, ‘Price’ is a Decimal and ‘Stock’ is an Integer.
If you send these requests, they both work (You’ll get a 200/OK with the updated entity):
PATCH, http://playapi.azurewebsites.net/api/products/1 with {"Price": 600.00}
PATCH, http://playapi.azurewebsites.net/api/products/1 with
{"Category": "Electronics"}
However, if you send this, it returns 200/OK, but does not make the update and the stock remains at the original value
PATCH, http://playapi.azurewebsites.net/api/products/1 with
{"Stock": 4}
My controller code is fairly standard boiler plate code (from the scaffolded ODATA controller but moved into a standard API controller):
// PATCH: api/Products/5
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> PatchOrder(int id, Delta<Product> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var item = await db.Products.FindAsync(id);
if (item == null)
{
return NotFound();
}
patch.Patch(item);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return Ok(item);
}
My model for 'Product' is as follows:
namespace PlayAPI.Models
{
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public double Price { get; set; }
public int Stock { get; set; }
}
}
When I debug the controller, I see that the ‘patch’ object has a _changedProperties collection which has no items in it when I do an integer request, but when I do any other kind of request it has the key that I changed.
Should web API support PATCH requests for integer properties? If so, do I need to do anything special on the server or client to make it work?
As a quick fix, Change the int to an Int64 on PlayAPI.Models.Product.
public Int64 Stock { get; set; }
It's my understanding that The Delta object used to patch the existing object doesn’t use JSON.net to convert and is silently throwing an Invalid cast exception when it parses JSON and then compares to the existing object from your database. You can read more about the bug over here: http://aspnetwebstack.codeplex.com/workitem/777
If you can't actually change the data type successfully, there may be a decent hack fix that you can use. Just attached unreadable data into the query string.
Here's a function you can call from within your Patch functions. As long as you aren't using the query string parameters specifically named what it's looking for, you should be just fine.
/// <summary>
/// Tries to attach additional parameters from the query string onto the delta object.
/// This uses the parameters extraInt32 and extraInt16, which can be used multiple times.
/// The parameter format is "PropertyName|Integer"
/// <para>Example: ?extraInt32=Prop1|123&extraInt16=Prop2|88&extraInt32=Prop3|null</para>
/// </summary>
[NonAction]
protected void SetAdditionalPatchIntegers<TEntity>(Delta<TEntity> deltaEntity, bool allowNull = true)
{
var queryParameters = Request.GetQueryNameValuePairs();
foreach (var param in queryParameters.Where(pair =>
pair.Key == "extraInt32" ||
pair.Key == "extraInt16"))
{
if (param.Value.Count(v => v == '|') != 1)
continue;
var splitParam = param.Value.Split('|');
if (allowNull &&
(String.IsNullOrWhiteSpace(splitParam[1]) ||
splitParam[1].Equals("null", StringComparison.OrdinalIgnoreCase)))
{
deltaEntity.TrySetPropertyValue(splitParam[0], null);
continue;
}
if (param.Key == "extraInt32")
{
int extraInt;
if (Int32.TryParse(splitParam[1], out extraInt))
{
deltaEntity.TrySetPropertyValue(splitParam[0], extraInt);
}
}
if (param.Key == "extraInt16")
{
short extraShort;
if (Int16.TryParse(splitParam[1], out extraShort))
{
deltaEntity.TrySetPropertyValue(splitParam[0], extraShort);
}
}
}
}
I really hate that there isn't a better answer, but at least something can be done about it.

ASP.NET MVC Model validation error beside field

I have a self validation model in my code based on this link:
ASP.NET MVC: Custom Validation by DataAnnotation
public class TestModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("The title is mandatory.", new [] { "Title" });
if (Description == null)
yield return new ValidationResult("The description is mandatory.", new [] { "Description" });
}
}
All of this works well. But my question is this: The error messages above are displayed as ValidationSummary errors. Is there any way to make the title error message display beside the title field (on the form view) and the description error message display beside the description field, just like in client side validation?
First make sure that you have added the correct razor markup next to each field, for example:
#Html.ValidationMesageFor(m => m.Title)
This will display the error message only if there is an error in the ModelState that is associated with the same field, so ensure ModelState["Title"] contains the error, otherwise you won't see the message.
This all is customized, at least, by CSS, or you can always use javascript (jquery).
UPDATE:
Well, i think, this is quite little info, therefore, try to add a some more.Basically, you also can use html.helper ValidationMessageFor + span tag. For instance:
#Html.EditorFor(x=>x.ModelProperty)<span>#Html.ValidationMessageFor(x=>x.ModelProperty)</span>
And in action method after your magic object validates itself, analyze result of your validation.
Just raugh demo:
var result = myObject.Validate(someContext);
if (result != result.Success)
{
ModelState.AddModelError("Title"); // if error in Title prop (otherwise "Description")
return View (myObject);
}
Or, if your model is validated through ValidationAttribute, you can check out this via ModelState.IsValid or ModelState.IsValidField methods.

Spring MVC: Request Scope, trying to update a Command Object with binder.setDisallowedFields

I have this Object
public class Deportista implements Serializable {
private static final long serialVersionUID = 6229604242306465153L;
private String id;
...
#NotNull(message="{field.null}")
public String getId() {
return id;
}
...
}
I have the following Controller's methods
#InitBinder(value="deportistaRegistrar")
public void registrarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> registrarInitBinder >>>>>>>>>>>>>");
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.GET)
public String crearRegistrarFormulario(Model model){
logger.info("crearRegistrarFormulario GET");
Deportista deportista = new Deportista();
model.addAttribute("deportistaRegistrar", deportista);
return "deportista.formulario.registro";
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.POST)
public String registrarPerson(#Validated #ModelAttribute("deportistaRegistrar") Deportista deportista,
BindingResult result){
logger.info("registrarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.registro";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.insertDeportista(deportista);
return "redirect:/manolo.htm";
}
Until here the Controller is able to create a form (GET) and submit (POST) a new command object, Validation code works well.
The problem is with the update.
I have the following:
#InitBinder(value="deportistaActualizar")
public void actualizarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> actualizarInitBinder >>>>>>>>>>>>>");
binder.setDisallowedFields("id");
}
Observe I have binder.setDisallowedFields("id")
public String crearActualizarFormulario(#PathVariable("id") String id, Model model){
logger.info("crearActualizarFormulario GET");
Deportista deportista = this.fakeMultipleRepository.findDeportista(id);
model.addAttribute("deportistaActualizar", deportista);
return "deportista.formulario.actualizacion";
}
#RequestMapping(value="/{id}/actualizar.htm", method=RequestMethod.POST)
public String actualizarPerson(#Validated #ModelAttribute("deportistaActualizar") Deportista deportista,
BindingResult result){
logger.info("actualizarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.actualizacion";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.updateDeportista(deportista);
return "redirect:/manolo.htm";
}
The problem is:
when the form or command has any error, the controller re-render the view and the form appear showing the error messages how is expected, but without the ID value
or
if I try to update the object, of course keeping the id value, and without any error to simply proceed to update, it fails
The following appears in the Console:
- -------- createCollections ---------------
- >>>>>>>> actualizarInitBinder >>>>>>>>>>>>>
- Skipping URI variable 'id' since the request contains a bind value with the same name.
- actualizarPerson POST
- Deportista [id=null, nombre=Manuel, ...]
- There are errors!!!!
- Error Field error in object 'deportistaActualizar' on field 'id': rejected value [null]; codes [NotNull.deportistaActualizar.id,NotNull.id,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [deportistaActualizar.id,id]; arguments []; default message [id]]; default message [The field must be not empty]
The id is null. How I can around this problem keeping the Request Scope?
I have an alternate controller which is working with #SessionAttributes and all works perfect. But since is a huge risk if the user has many tabs open in the same web browser, one for create and other for updating, all is going to be very wrong. According with Spring MVC + Session attributes and multiple tabs, request scope should be used instead of session scope. It has sense.
Sadly seems Spring is not going to fix this:
#SessionAttributes doesn't work with tabbed browsing
Addition
According with your suggestion, I have the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#RequestParam(defaultValue="") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Observe the method uses #RequestParam, my problem is how update that method to work when the URL to update has the following style
http://localhost:8080/spring-utility/deportista/1/actualizar.htm. There is no param in the URL, therefore #RequestParam is useless now.
I already have read the Spring Reference documentation:
Using #ModelAttribute on a method
Second Addition
Yes, you was right, and I did that yesterday, but I forget to share the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#PathVariable(value="id") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Since a #ModelAttribute is called always before by any handler method, the following URL fails http://localhost:8080/spring-utility/deportista/registrar.htm, the following appears on the page
HTTP Status 400 -
type Status report
message
description The request sent by the client was syntactically incorrect.
Of course because the URL does not contains the expected id. Therefore I can't create new records to later edit/see.
I can confirm, that for the following work:
http://localhost:8080/spring-utility/deportista/1/detalle.htm
http://localhost:8080/spring-utility/deportista/1/actualizar.htm
the id (1) is retrieved.
How I could resolve this?
Thank You

Resources