Spring WebFlux and Thymeleaf form validation messages - thymeleaf

I'm trying to learn webflux and thymeleaf but I'm stuck almost at the beginning.
I was able to fetch data from form and validate it, but I'm unable to get error messages using ${#fields(...)} in thymeleaf.
Below you can find what I have currently:
Form:
<form class="need-validation" th:action="#{/group/new}" th:object="${group}" method="post">
<ul th:if="${#fields.hasErrors('name')}">
<li th:each="err : ${#fields.errors('name')}" th:text="${err}">Input is incorrect</li>
</ul>
<div class="form-group row">
<label for="name" class="col-sm-2 col-form-label" th:text="#{groups.group.name}"></label>
<div class="col-sm-10">
<input type="text" class="form-control" id="name" name="name" th:value="*{name}" >
</div>
</div>
</form>
And code that handles form:
public Mono<ServerResponse> createGroup(ServerRequest serverRequest) {
return serverRequest.formData()
.map(this::parseGroupData)
.flatMap(group -> {
Errors errors = validateGroup(group);
if (errors.hasErrors()) {
Map<String, Object> model = new HashMap<>();
model.put("group", group);
model.put("errors", errors.getAllErrors());
//model.put("fields", errors);
return ServerResponse.badRequest().render("/groups/group-create", model);
}
return ServerResponse.ok().bodyValue(group);
});
}
private FormGroup parseGroupData(MultiValueMap<String, String> multiValueMap) {
Map<String, String> map = multiValueMap.toSingleValueMap();
FormGroup formGroup = new FormGroup();
formGroup.setName(map.get("name"));
return formGroup;
}
private Errors validateGroup(FormGroup group) {
Errors errors = new BeanPropertyBindingResult(group, "group");
groupValidator.validate(group, errors);
return errors;
}
Validations looks to work correctly as there is present error on "name" field when i insert data which is incorrect.
How can I display errros using #fields object?
Or maybe in that case I have to write my own solution?

You could trythis key for errors, it works for me.
if (errors.hasErrors()) {
model.addAttribute(BindingResult.class.getName() + ".group", errors);
}
'group' is the thymeleaf object name.

Related

How to validate the HTML controls with data annotations in MVC?

In .Net MVC. I have a html control. Inorder to bind it with the model property I am using name attribute. How do we get the validations(using data annotation) provided in the model class property into the html control?
In Cshtml
#using (Html.BeginForm("ClaimWarranty", "WarrentyClaim", FormMethod.Post, new{ enctype = "multipart/form-data" }))
{
<div class="form-group row">
<label for="" class="col-md-2 col-form-label input-label">Email Address:</label>
<div class="col-md-8">
<input type="text" name="Emailaddress" class="form-control input-style" placeholder="example#company.com">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" onclick="ValidateFileSize()" class="btn btn-default" />
</div>
</div>
}
//The model class is below;
public class ClaimWarranty
{
[Required(ErrorMessage = "Email ID is Required")]
[DataType(DataType.EmailAddress)]
[MaxLength(50)]
[RegularExpression(#"[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,4}", ErrorMessage = "Incorrect Email Format")]
public string Emailaddress { get; set; }
}
I am using the name property to bind the text box to the model property .
<input type="text" name="Emailaddress" class="form-control input-style" placeholder="example#company.com">
How do I get the validations in the html control ,provided in the model class (using the data annotations) as shown above without using jquery validations or razor code?
In View
#model Demo.Models.Student
#using (Html.BeginForm("SaveStudent", "Student", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(model =>model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model =>model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model =>model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btnbtn-primary" />
</div>
</div>
}
In Model
public class Student
{
[Required(ErrorMessage = "Please enter name"), MaxLength(30)]
public string Name { get; set; }
}
By default, ASP.Net MVC framework executes validation logic during model binding. In Controller side, we need to check
if (ModelState.IsValid)
{
}
OR We can also check Individual validation, as shown below:
if (ModelState.IsValidField("LastName") == false)
if(!ModelState.IsValid)
{
// you can get the error information from model state, convert it into list
var validationErrors = ModelState.Values.Where(E => E.Errors.Count > 0)
.SelectMany(E => E.Errors)
.Select(E => E.ErrorMessage)
.ToList();
// now you have got the list of errors, you will need to pass it to view
// you can use view model, viewbag etc
ViewBag.ErrorList = validationErrors;
return View();
}
else
{
// perform your business operation, save the data to database
return View();
}
On View Page -
you have to add check for validation error list
if(ViewBag.ErrorList != null)
{
foreach(var errorMessage in ViewBag.ErrorList)
{
// here you can display the error message and format in html
}
}
Way you can display error on view page
1. #Html.ValidationSummary() - It will display summary of the validation errors
2. #Html.ValidationMessageFor(x => x.Emailaddress) - It will display error message
for specific property
3. you have to manually retrieve the error information from model state and then store it in list and pass to the view page.

Asp.Net MVC Core built-in tag helpers won't process attributes provided from custom taghelpers

Out of boredom of writing the same boilerplate forms, I thought I'd rather write a tag helper that would produce attributes that can be processed from stock tag helpers. But, even though I managed to get my tag helper before work before form tag helpers, form tag helpers won't process the ones that I produce.
Here is the cshtml:
#model City
#{
Layout = "_Layout";
ViewData["Title"] = "Create City";
}
<form method="post" asp-action="Create">
#foreach (string propName in BaseModel.GetProperties<City>()) {
<formgroup for="#propName" />
}
<div class="form-group">
<label asp-for="Name">Name:</label>
<input class="form-control" asp-for="Name" />
</div>
<div class="form-group">
<label asp-for="Country">Country:</label>
<input class="form-control" asp-for="Country" />
</div>
<div class="form-group">
<label asp-for="Population">Population:</label>
<input class="form-control" asp-for="Population" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
<a class="btn btn-secondary" asp-controller="Home" asp-action="Index">Cancel</a>
</form>
Here is the output:
<form method="post" action="/City/Create">
<div class="form-group"><label asp-for="Name"></label><input asp-for="Name" class="form-control"></div>
<div class="form-group"><label asp-for="Country"></label><input asp-for="Country" class="form-control"></div>
<div class="form-group"><label asp-for="Population"></label><input asp-for="Population" class="form-control"></div>
<div class="form-group">
<label for="Name">Name:</label>
<input class="form-control" type="text" id="Name" name="Name" value="">
</div>
<div class="form-group">
<label for="Country">Country:</label>
<input class="form-control" type="text" id="Country" name="Country" value="">
</div>
<div class="form-group">
<label for="Population">Population:</label>
<input class="form-control" type="number" data-val="true" data-val-required="The Population field is required." id="Population" name="Population" value="">
</div>
<button type="submit" class="btn btn-primary">Create</button>
<a class="btn btn-secondary" href="/">Cancel</a>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8M_6usK6CRRNkwluTiTW8uaAAfhMcU9tAxyCT7z55zQKmpUwpi_lfSDIN4FrlMo9cE3Ka9zgX4WdpXHUdlBFVGsLIw7h_cR3FjJb6Vjqnjm8mQmtKTey_9l188p9E2sKgiksO_OB6K9-F1D7SP2lX0g"></form>
Here is my tag helper:
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
namespace ViewComponents.Infrastructure.TagHelpers {
[HtmlTargetElement("formgroup", Attributes = "for", TagStructure = TagStructure.NormalOrSelfClosing)]
public class FormGroupTagHelper : TagHelper {
/// <inheritdoc />
public override int Order => -2000;
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
protected IHtmlGenerator Generator { get; }
public string For { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
if (output == null) {
throw new ArgumentNullException(nameof(output));
}
// So that other tag helpers are processed after me...
var childContent = await output.GetChildContentAsync();
// Starting up...
// Replace the tag name, include the form-group bootstrap class.
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
output.Attributes.Add("class", "form-group");
PropertyInfo propInfo = ViewContext.ViewData.ModelMetadata.ModelType.GetTypeInfo().GetDeclaredProperty(For);
output.Content.AppendHtml(GenerateLabel(new Dictionary<string, string> { ["asp-for"] = propInfo.Name }));
output.Content.AppendHtml(GenerateInput(new Dictionary<string, string> { ["asp-for"] = propInfo.Name, ["class"] = "form-control" }));
}
public static IHtmlContent GenerateLabel(IReadOnlyDictionary<string, string> attrDict) {
TagBuilder tBuilder = new TagBuilder("label");
foreach (var kvp in attrDict)
tBuilder.Attributes.Add(kvp);
return tBuilder;
}
public static IHtmlContent GenerateInput(IReadOnlyDictionary<string, string> attrDict) {
TagBuilder tBuilder = new TagBuilder("input");
foreach (var kvp in attrDict)
tBuilder.Attributes.Add(kvp);
return tBuilder;
}
}
}
Any help will be appreciated.
Have a nice one!
Tag Helpers aren't processed in a specific order, so you cannot count on one being able to do something based on the output of the other. Each tag helper needs to be designed to work independently of any other, so if you need to render HTML, you'll need to handle that yourself in your tag helper. However, since you're trying to have one tag generate any kind of form field, that's going to be a heavy lift. This is basically beyond the scope of what a tag helper is intended for.

Retain query string param after submit

On my website I use MVC Authentication, if a user wants to reset his password, he clicks on 'Forgot Password' then he gets by email a link with a unique code to reset his password.
My issue is if for some reason the reset fails (for example- password and password confirmation don't match etc) then it returns to the View without the unique code that was generated in the link that the user got by email and the reset password won't work afterwards (code query string param)
How can I keep the query string unique code when the reset fails and return it to the View?
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
}
ViewBag.Error = "Password reset failed";
return View();
}
And this is the form:
<form id="reset-form" method="post" action="Account/ResetPassword">
#Html.AntiForgeryToken()
<div class="header">
<h2>Reset Password</h2>
#if (!String.IsNullOrEmpty(ViewBag.Error))
{
<div class="invalid">
#ViewBag.Error
</div>
}
</div>
<div class="inputField">
<input type="email" id="email" name="email" placeholder="USERNAME" />
</div>
<div class="inputField">
<input type="password" id="password" name="password" placeholder="PASSWORD" />
</div>
<div class="inputField">
<input type="password" id="confirm-password" name="confirmPassword" placeholder="CONFIRM PASSWORD" />
<input type="submit"/>
</div>
</form>
I need it to return to the View like this:
~/account/resetpassword?code=xyz
The URL will remain the same as what you post when the view is returned unless you do a redirect (not recommended).
However, the issue isn't that you need the URL to change after the action method runs. You need to change the URL that is posted to the action method to include the query string so it is there when the action method returns the view. This can be done by simply changing the URL of the form.
Note that you should never hard code the URL in MVC views. Instead, you should always use controller, action, and other route values to resolve the URL using the UrlHelper (and related extension methods such as ActionLink). The routing infrastructure should be the only place where URLs exist so they can be changed in one place.
In this case the solution is to use Html.BeginForm to build up the form tag based on routing.
#using (Html.BeginForm("ResetPassword", "Account", new { code = "xyz" }, FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="header">
<h2>Reset Password</h2>
#if (!String.IsNullOrEmpty(ViewBag.Error))
{
<div class="invalid">
#ViewBag.Error
</div>
}
</div>
<div class="inputField">
<input type="email" id="email" name="email" placeholder="USERNAME" />
</div>
<div class="inputField">
<input type="password" id="password" name="password" placeholder="PASSWORD" />
</div>
<div class="inputField">
<input type="password" id="confirm-password" name="confirmPassword" placeholder="CONFIRM PASSWORD" />
<input type="submit"/>
</div>
}
Of course, you shouldn't hard code "xyz" here either, it should be passed to the view from the HttpGet ResetPassword method either in a Model or in ViewBag.
Note that if the url parameter of the route definition (typically in RouteConfig.cs) does not contain a parameter for code, it will automatically be added to the URL as a query string parameter.
I'm using Thymeleaf and had a similar issue. Here's how I solved it using the thymeleaf functionality:
HTML:
<form class="forms-sample" action="/login"
!!! th:action="#{/reset_password(token=${token})}" !!!
th:object="${passwordDto}" method="post">
**INSERT CODE HERE**
</form>
MVC Controller:
if(bindingResult.hasErrors()) {
model.addAttribute("token", tokenStr); !!!!!!!!
return "create_password";
}
try {
PasswordResetToken token = passwordResetTokenService.getByToken(tokenStr);
User user = token.getUser();
user = userMapper.setNewPassword(user, passwordDto);
userService.update(user);
passwordResetTokenService.delete(token);
return "home";
}catch (PasswordNotMatchException e) {
bindingResult.rejectValue("confirmNewPassword", "password_error",
"Confirm password did not match.");
model.addAttribute("token", tokenStr); !!!!!!!!
return "create_password";
}
catch (EntityNotFoundException e) {
return "create_password";
}
What I do is I put the token in the Model and if the post method encounters an error I make sure to add it again, before returning the view for creating a password.
The important thing to note is, as NightOwl mentioned, that the url stays the same, only the Model changes. Thats why we need to readd the token as a model attribute.

Unobtrusive validation for nested collections

Since I'm handling a extremely complex model and forms I will reduce my problem in a more understandable example (please excuse if there is any typo).
First I will show the scenario:
The model...
public class Foo
{
[Required]
public int fooId {get; set;}
public string fooName {get; set;}
public List<Bar> barList {get; set;}
}
public class Bar
{
[Required]
public int barId {get; set;}
public string barName {get; set;}
}
The view...
#model Foo
#using (Html.BeginForm("Save", "form", FormMethod.Post))
{
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(model => Model.fooId)
</div>
</div>
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(model => Model.fooName)
</div>
</div>
#for (int i = 0; i < Model.barList.Count(); i++)
{
#Html.EditorFor(m => m.barList[i])
}
}
The "bar" editor template...
#model Bar
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(m => m.barId)
</div>
</div>
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(m => m.barName)
</div>
</div>
The problem that I'm having is during the client-side validation for inputs in nested collections, in this case I'm not able to validate the "barId" input field. It simply ignores it...
In the case of the fooId field, it's validated OK.
If we go deeper, a "foo" object with 2 "bar" items would generate something like this:
<div class="control-group">
<div class="controls">
<input class="input-validation-error" id="fooId" name="fooId" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="fooName" name="fooName" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_0__barId" name="barList[0].barId" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_0__barName" name="barList[0].barName" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_1__barId" name="barList[1].barId" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_1__barName" name="barList[1].barName" type="text" value="">
</div>
</div>
As you can see, the items inside the "bar" collection have received a different id and name. This is the normal behaviour for rendering the collections.
But it seems to be that the client-side validation doesn't work with these ids and names. The validation will work only if I change the id & name to "barId", removing the collection index..
After hours of investigation, I've found some articles and posts regarding issues like this, but nothing concrete and I still could not solve this.
IValidatableObject in MVC3 - client side validation
mvc clientside validation for nested (collection) properties
I did not find a solution, but I did find a workaround.
Functional explanation: the Model is called "InsuranceLine", and it has a collection "InsuranceLine.Letters." Each Letter has a nullable Boolean property "Letter.IsDeficient". If "IsDeficient" is changed from False to True then the string field "Letter.ReasonCode" is required. "IsDeficient" is rendered as a checkbox, and "ReasonCode" is rendered as two radio buttons, "Corrected" and "Waived".
Here is the custom attribute:
public class ReasonCodeAttribute : ValidationAttribute, IClientValidatable
{
private const string errorMessage = "When 'Deficient' is changed from True to False you must select a Reason.";
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = errorMessage,
ValidationType = "reasoncoderequired"
};
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Letter letter = validationContext.ObjectInstance as Letter;
if(!letter.IsDeficient.GetValueOrDefault()
&& letter.IsDeficient_OriginalState.GetValueOrDefault()
&& (value == null || string.IsNullOrEmpty(value.ToString())))
{
return new ValidationResult(errorMessage);
}
else
{
return null;
}
}
}
I decorate Letter.ReasonCode with the custom attribute:
[ReasonCodeAttribute]
public string ReasonCode { get; set; }
I render the nested Letters collection in the *.cshtml page:
#for (int i = 0; i < Model.Letters.Count; i++)
{
#Html.EditorFor(m => m.Letters[i].IsDeficient, "MyCustomTemplate", new { htmlAttributes = new { #class="cb-is-deficient" } })
<div class="rb-reason-code">
<label>#Html.RadioButtonFor(m => m.Letters[i].ReasonCode, myVar == "C", new { id = id + "C", #class ="rb-reason-code" }) Corrected</label>
<label>#Html.RadioButtonFor(m => m.Letters[i].ReasonCode, myVar == "W", new { id = id + "W", #class = "rb-reason-code" }) Waived</label>
</div>
}
The GetClientValidationRules() method of the ReasonCode attribute causes the asp.net runtime to generate the following attribute when it renders the ReasonCode into an html radio button:
data-val-reasoncoderequired="When 'Deficient' is changed from True to False you must select a Reason.".
In JavaScript I add the 'reasoncoderequired' method to the validator like so in the document ready method. As part of my workaround I need to manually add the class "error" to my display so that the user gets a visual hint of the invalid state of the model:
$.validator.addMethod('reasoncoderequired', function (value, element) {
var $parent = $(element).closest('div.parent');
var $cb = $parent.find('input[type="checkbox"].cb-is-deficient');
if ($cb.prop('defaultChecked')) {
var $selectedRadioButton = $parent.find('div.rb-reason-code').find('input[type="radio"]:checked');
if ($selectedRadioButton.length == 0) {
$parent.addClass('error');
return false;
}
}
$parent.removeClass('error');
return true;
});
Finally, I add the reasoncoderequired rule to each ReasonCode radio button like so, also in the document ready method. The "messages" simply reads from the data-val-reasoncoderequired attribute rendered with each input to display the error message:
$form.find('input[type="radio"].rb-reason-code').each(function () {
$(this).rules('add',
{
reasoncoderequired: true,
messages: { reasoncoderequired: $(this).attr('data-val-reasoncoderequired') }
});
})

MVC4 Form Input Validation - Customising the error span

I have a form with several inputs using Html.TextBoxFor and each input has a ValidationMessageFor which gets it's error message from the ViewModel attributes. For this example, we'll pretend there's just one input:
#Html.TextBoxFor(x => x.Code)
#Html.ValidationMessageFor(x => x.Code)
When there's a model error, the validator displays it's error message within a couple of spans which looks like this:
<input type="text" value="" name="Code" id="Code" data-val-required="Personalised code is required" data-val="true" class="input-validation-error">
<span data-valmsg-replace="true" data-valmsg-for="Code" class="field-validation-error">
<span for="Code" generated="true" class="">Personalised code is required</span>
</span>
How do I customise this error message?
For example change the outer span to a div and give both the div and span clases?
<div class="myOuterSpan" data-valmsg-replace="true" data-valmsg-for="Code" class="field-validation-error">
<span class="myInnerSpan" for="Code" generated="true" class="">Personalised code is required</span>
</div>
Or just have one span?
<span class="errorWrapper" for="Code" generated="true">Code is required</span>
Or wrap the whole lot in a div?
<div class="myOuterDiv">
<span data-valmsg-replace="true" data-valmsg-for="Code" class="field-validation-error">
<span for="Code" generated="true" class="">Personalised code is required</span>
</span>
</div>
You get the idea...
THE SOLUTION
I based my solution on Darin's answer and created a CustomValidationMessage, not the customValidationMessageFOR as I was initially intending on creating.
CONTROLLER
ModelState.AddModelError("Code", "Invalid Code");
VIEW
#Html.CustomValidationMessage("Code")
EXTENSION
public static class Extensions
{
public static MvcHtmlString CustomValidationMessage(this HtmlHelper htmlHelper, string modelName)
{
var modelState = htmlHelper.ViewData.ModelState[modelName];
var modelErrors = modelState == null ? null : modelState.Errors;
var modelError = ((modelErrors == null) || (modelErrors.Count == 0))
? null
: modelErrors.FirstOrDefault(e => !string.IsNullOrEmpty(e.ErrorMessage)) ?? modelErrors[0];
if (modelError != null)
{
return MvcHtmlString.Create("<span class='validation_wrapper customValidation'><span>" + modelError.ErrorMessage +"</span></span>");
}
return new MvcHtmlString(string.Empty);
}
}
There's no way to modify the markup generated by the ValidationMessageFor helper. If you want to do that you will have to write a custom helper. Here's how the signature of this helper might look like:
public static MvcHtmlString CustomValidationMessageFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
{
...
}
You might also checkout a similar post.
There's really no need for a custom extension. For example:
Controller:
ModelState.AddModelError("InvalidLogin", "Your credentials are invalid.")
View:
#Html.ValidationMessageFor
(
model => model.InvalidLogin,
"",
htmlAttributes: new { #class = "text-error" },
tag: "div"
)

Resources