Asp.Net Core Remote validation attribute not making call - asp.net-core-2.2

I have set the Remote attribute to validate that my username is Unique but when I debug it's not firing. What am I messing up?
In my View Model here is the attribute and property:
[Required]
[Remote("VerifyUsername", "Account")]
public string Username { get; set; }
In my form my form attribute is:
<input asp-for="Username" class="form-control mb-4" placeholder="Username" />
<span asp-validation-for="Username"></span>
And in my controller I have tried:
public JsonResult VerifyUsername(string username)
{
if (!_user.UsernameUnique(username))
{
return Json($"{username} is already in use.");
}
return Json(true);
}
And the method format:
[AcceptVerbs("Get", "Post")]
public IActionResult VerifyUsername(string username)
{
if (!_user.UsernameUnique(username))
{
return Json($" {username} is already in use.");
}
return Json(true);
}
I enter a usernam and click around and try tabbing... nothing gets the remote validation to fire. Anyone see what I am missing?

So I found it... kind of a facepalm. I was using another library that was loading another version of jquery. I was not getting an error though so that was weird. I removed that script reference for that other version of jquery and so it just had the current version and it all worked.

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; }

Spring thymeleaf Form url cannot be resolved

I want to know how to transfer parameters in spring form mvc platform.
First, Below code is spring form java file.
public class PostForm {
#NotNull
#Size(max=30, message="type id within 30 limits")
private String title;
#NotNull
#Size(max=100, message="type id within 100 limits")
private String Content;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
}
And the next file is the bounded edit.html file
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<title>Blog modification</title>
</head>
<body>
<h1>Please, Modifiy.</h1>
<form method="post" th:object="${postForm}">
<div><label for="title">title</label></div>
<input id="title" type="text" name="title" th:value="*{title}" />
<span class="formError" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Input title is wrong</span>
<div><label for="content" th:value="*{title}">Content</label></div>
<textarea name="content" rows="20" width="200" th:value="*{content}"></textarea>
<span class="formError" th:if="${#fields.hasErrors('content')}" th:errors="*{content}">Input content is wrong</span>
<div>
<input type="submit" value="Modify" />
Cancel
</div>
</form>
</body>
</html>
Input link url to form is like below,
<td>
edit<br/>
delete
</td>
But the exception is thrown in the spring mvc controller codes.
#RequestMapping("/posts/edit/{id}")
public String edit(PostForm postForm) {
return "posts/edit/{id}"; //This line throws exception.
}
#RequestMapping(value="/posts/edit/{id}", method = RequestMethod.POST)
public String edit(#PathVariable("id") Long id, #Valid PostForm postForm, BindingResult bindingResult) {
Post p = postService.findById(id);
postForm.setTitle(p.getTitle());
postForm.setContent(p.getBody());
.....
The exception is
ERROR 4024 --- [nio-8080-exec-4] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-8080-exec-4] Exception processing template "posts/edit/{id}": Error resolving template "posts/edit/{id}", template might not exist or might not be accessible by any of the configured Template Resolvers
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "posts/edit/{id}", template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:870) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:607) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098) [thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072) [thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
I have no idea how to transfer parameter in Spring Thymeleaf form template.
In Sprig MVC, when #ReqeustMapping annotation with GET Method is called, it tries to find the html template with the name defined in the return value.
#RequestMapping("/posts/edit/{id}")
public String edit(PostForm postForm) {
return "posts/edit/{id}"; //This line throws exception.
}
So here you must return the name of the html template in the resources folder (not the url)
So I guess it's supposed to be
#RequestMapping("/posts/edit/{id}")
public String edit(PostForm postForm) {
return "views/mytemplate";
}
The error obviously indicates that it can't find the template under the resources folder. What your code does is try to locate the thymeleaf template in the 'edit' folder under 'posts' folder under the resources folder with the name of '{id}' but that's not there so it throws the error.
My suggestion is to change the return value of the GET method as I mentioned above.
If you need to pass any parameters to the view, use Model class.
If the parameters' value must be calculated from the {id} then you can use #PathVariable to map the id to a parameter.
#RequestMapping("/posts/edit/{id}")
public String edit(#PathVariable(value="id") String id, Model model) {
// do something here to get values using the id
....
model.addAttribute("parameter1", parameter1);
return "views/mytemplate";
}
By the way you don't need PostForm parameter in the GET method since it does not pass any postForm parameters in the body when it's called. You can leave it blank.
Hope this helps, have fun coding! :)

HTTP POST can't find the ASP.NET MVC controller Method and bind the ViewModel to it

I have a lengthy ViewModel class that I POST to my Controller with an HTTPPost but the controller method doesn't catch it and instead in my browser console log I get HTTP 500 POST Error
Simplified my code is like this
public class MyDataViewModel{
public int Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public string Property4 { get; set; }
//etc to Property 100
}
cshtml:
<form action="#(Url.Action("Edit", "MyData"))" id="myform" class="myform" method="post">
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
#Html.EditorFor(model => model.Property1)
//...
#Html.EditorFor(model => model.Property100)
Some properties make use of Data Annotations.
When I submit the from the client side validation does work correctly and block the post before it is submitted if a required field is not captured.
However after the form submission my POST Method in my controller does not get hit. I verify this by putting a breakpoints on it as well as seeing the HTTP 500 error in my browser console log.
This is the method
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(MyDataViewModel myDataViewModel)
{
However when I change it the method parameter to an object i.e.
public async Task<IActionResult> Edit(object myDataViewModel)
it does get hit. I then try to cast that object to (MyDataViewModel)myDataViewModel the casting fails so something isn't right.
I have looked through fiddler to confirm all those data properties are being sent and they are.
I then tried the following:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(object myDataViewModel)
{
Type myType = myDataViewModel.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
foreach (PropertyInfo prop in props)
{
object propValue = prop.GetValue(myDataViewModel, null);
// Do something with propValue
}
}
To see if I could find which property has the issue. However props.Count = 0 which means the object is empty. Why would fiddler show all those fields but MVC not receive it? It makes no sense
I have the same type forms on other page that use the same <form> tags and method of posting which work so not sure why this one if failing.
In Visual Studio I can collapse the various html tags in the cshtml including the <form> tag so that doesn't pick up anything suspicious and shows each html tag has a corresponding closing tag.
I ended up creating a dummy viewmodel, using this dummy view model type as the HTTPPost parameter, and putting in a few properties of the real viewmodel in at a time, compiling the solution, test, until I reach the point where it doesn't hit the POST controller method..
Took a while but I eventually found the culprit property.
[Required, Display(Name = "Flag"),StringLength(50)]
public bool IsFlag{ get; set; }
I used a data annotation StringLength for a bool field which wasn't valid for a bool.
This resulted in the front end not showing any client validation jquery.validate error before HTTPPost and the HTTPPost method controller method unable to get hit.
I would still like to now if there is a quicker way to troubleshoot this type of issue or determine which property is at fault as the method I used was very laborious.
In my case, I forgot to put "public" in the post action. It puzzled me quite a while. Also, if there is some problem with the post action, it will enter the get action (if you don't specifically put HttpGet there) and it makes you wonder why I click the button but still goes to the get action.

ASP.NET MVC Redirect To Action does not render final View

I'm trying this code:-
If no query string supplied to the Index Method then render a Branch Locator View. When a Branch Id is selected in that View, post back to a Redirect To Route Result OR Action Result method and then redirect back to Index with a query string of the selected Branch Id.
I can run through the code successfully without and then with the query string.
I even run through the Index View and can see the Model working however, the Index View does not render, the Branch Selector View remains. Network developer tools shows the correct URL with query string correctly in place when doing the Redirect.
(NOTE: Both methods are on the same controller).
If I add the same query string directly in the Browser address bar it works fine!
I have this code:
[HttpGet]
public ActionResult Index()
{
var querystringbranchId = Request.QueryString["branchId"];
if(!string.IsNullOrEmpty(querystringId))
{
....do stuff like build a model using the branchId...
return View(Model);
}
return View("BranchSelector")
}
[HttpPost]
public RedirectToRouteResult BranchDetails(FormCollection formCollection)
{
var querystringBranchId = formCollection["BranchList"];
var branchId = int.Parse(querystringBranchId);
return RedirectToAction("Index", new { branchId });
}
Try using strongly typed model on the post, and specifying the param as an actual param - Using View models is going to be much better for you.
I have tested the below - It seemed to work as expected for me:
[HttpGet]
public ActionResult Index(int? branchId)
{
if (branchId.HasValue)
{
return View(branchId);
}
return View("BranchSelector");
}
[HttpPost]
public RedirectToRouteResult BranchDetails(MyModel myModel)
{
return RedirectToAction("Index", new { myModel.BranchId });
}
public class MyModel
{
public int BranchId { get; set; }
}
The View:
<div>
#using (Html.BeginForm("BranchDetails", "Home", FormMethod.Post))
{
#Html.TextBox("BranchId","123")
<input type="submit" value="Go"/>
}
</div>
#MichaelLake Thanks to your post I found the problem. I tried your code and sure enough it works as expected. I didn't mention I was using a Kendo Combobox control (!) loaded with the branches. I didn't mention that as the actual data I needed was available in the post method so, thought the issue was with the Controller methods. I had the Kendo control name as BranchList, I changed it to BranchId and it now works with the original code as expected! The Kendo name becomes the element Id and has to match to work.
Many Thanks!
This will work for you. Cheers :D
return RedirectToAction("Index", "ControllerName", new { branchId = branchId});

IValidatableObject Validate method firing when DataAnnotations fails

I've a ViewModel which has some DataAnnotations validations and then for more complex validations implements IValidatableObject and uses Validate method.
The behavior I was expecting was this one: first all the DataAnnotations and then, only if there were no errors, the Validate method. How ever I find out that this isn't always true. My ViewModel (a demo one) has three fileds one string, one decimal and one decimal?. All the three properties have only Required attribute. For the string and the decimal? the behavior is the expected one, but for the decimal, when empty, Required validation fails (so far so good) and then executes the Validate method. If I inspect the property its value is zero.
What is going on here? What am I missing?
Note: I know that Required attribute is suppose to check if the value is null. So I'd expect to be told not to use Required attribute in not-nullable types (because it wont ever trigger), or, that somehow the attribute understand the POST values and note that the field wasn't filled. In the first case the attribute shouldn't trigger and the Validate method should fire. In the second case the attribute should trigger and the Validate method shouldn't fire. But my result are: the attributes triggers and the Validate method fires.
Here is the code (nothing too special):
Controller:
public ActionResult Index()
{
return View(HomeModel.LoadHome());
}
[HttpPost]
public ActionResult Index(HomeViewModel viewModel)
{
try
{
if (ModelState.IsValid)
{
HomeModel.ProcessHome(viewModel);
return RedirectToAction("Index", "Result");
}
}
catch (ApplicationException ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, "Internal error.");
}
return View(viewModel);
}
Model:
public static HomeViewModel LoadHome()
{
HomeViewModel viewModel = new HomeViewModel();
viewModel.String = string.Empty;
return viewModel;
}
public static void ProcessHome(HomeViewModel viewModel)
{
// Not relevant code
}
ViewModel:
public class HomeViewModel : IValidatableObject
{
[Required(ErrorMessage = "Required {0}")]
[Display(Name = "string")]
public string String { get; set; }
[Required(ErrorMessage = "Required {0}")]
[Display(Name = "decimal")]
public decimal Decimal { get; set; }
[Required(ErrorMessage = "Required {0}")]
[Display(Name = "decimal?")]
public decimal? DecimalNullable { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return new ValidationResult("Error from Validate method");
}
}
View:
#model MVCTest1.ViewModels.HomeViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post))
{
<div>
#Html.ValidationSummary()
</div>
<label id="lblNombre" for="Nombre">Nombre:</label>
#Html.TextBoxFor(m => m.Nombre)
<label id="lblDecimal" for="Decimal">Decimal:</label>
#Html.TextBoxFor(m => m.Decimal)
<label id="lblDecimalNullable" for="DecimalNullable">Decimal?:</label>
#Html.TextBoxFor(m => m.DecimalNullable)
<button type="submit" id="aceptar">Aceptar</button>
<button type="submit" id="superAceptar">SuperAceptar</button>
#Html.HiddenFor(m => m.Accion)
}
Considerations after comments' exchange:
The consensual and expected behavior among developers is that IValidatableObject's method Validate() is only called if no validation attributes are triggered. In short, the expected algorithm is this (taken from the previous link):
Validate property-level attributes
If any validators are invalid, abort validation returning the failure(s)
Validate the object-level attributes
If any validators are invalid, abort validation returning the failure(s)
If on the desktop framework and the object implements IValidatableObject, then call its Validate method and return any failure(s)
However, using question's code, Validate is called even after [Required] triggers. This seems an obvious MVC bug. Which is reported here.
Three possible workarounds:
There's a workaround here although with some stated problems with it's usage, apart from breaking the MVC expected behavior. With a few changes to avoid showing more than one error for the same field here is the code:
viewModel
.Validate(new ValidationContext(viewModel, null, null))
.ToList()
.ForEach(e => e.MemberNames.ToList().ForEach(m =>
{
if (ModelState[m].Errors.Count == 0)
ModelState.AddModelError(m, e.ErrorMessage);
}));
Forget IValidatableObject and use only attributes. It's clean, direct, better to handle localization and best of all its reusable among all models. Just implement ValidationAttribute for each validation you want to do. You can validate the all model or particular properties, that's up to you. Apart from the attributes available by default (DataType, Regex, Required and all that stuff) there are several libraries with the most used validations. One which implements the "missing ones" is FluentValidation.
Implement only IValidatableObject interface throwing away data annotations. This seems a reasonable option if it's a very particular model and it doesn't requires much validation. On most cases the developer will be doing all that regular and common validation (i.e. Required, etc.) which leads to code duplication on validations already implemented by default if attributes were used. There's also no re-usability.
Answer before comments:
First of all I've created a new project, from scratch with only the code you provided. It NEVER triggered both data annotations and Validate method at the same time.
Anyway, know this,
By design, MVC3 adds a [Required]attribute to non-nullable value types, like int, DateTime or, yes, decimal. So, even if you remove required attribute from that decimal it works just like it is one there.
This is debatable for its wrongness (or not) but its the way it's designed.
In you example:
'DataAnnotation' triggers if [Required] is present and no value is given. Totally understandable from my point of view
'DataAnnotation' triggers if no [Required] is present but value is non-nullable. Debatable but I tend to agree with it because if the property is non-nullable, a value must be inputted, otherwise don't show it to the user or just use a nullable decimal.
This behavior, as it seems, may be turned off with this within your Application_Start method:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I guess the property's name is self-explanatory.
Anyway, I don't understand why do you want to the user to input something not required and don't make that property nullable. If it's null then it is your job to check for it, if you don't wan't it to be null, before validation, within the controller.
public ActionResult Index(HomeViewModel viewModel)
{
// Complete values that the user may have
// not filled (all not-required / nullables)
if (viewModel.Decimal == null)
{
viewModel.Decimal = 0m;
}
// Now I can validate the model
if (ModelState.IsValid)
{
HomeModel.ProcessHome(viewModel);
return RedirectToAction("Ok");
}
}
What do you think it's wrong on this approach or shouldn't be this way?

Resources