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"
)
Related
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.
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.
I want to present a button with #Html.ActionLink but i need to have my application font in the text.
With this code:
<span>
#Html.ActionLink(" Create New", "Create", null, new { #class = "btn btn-warning glyphicon glyphicon-plus-sign" })
</span>
I get my button but the Create New text appears with the glyphicon font-family.
Putting the glyphicon classes in the span doesn't change anything
You should not add the glyphicon class to the a-tag.
From the Bootstrap website:
Don't mix with other components
Icon classes cannot be directly combined with other components. They should not be used along with other classes on the same element. Instead, add a nested <span> and apply the icon classes to the <span>.
Only for use on empty elements
Icon classes should only be used on elements that contain no text content and have no child elements.
In other words the correct HTML for this to work the way you want would be: test <span class="glyphicon glyphicon-plus-sign"></span>
This makes the Html.ActionLink helper unsuitable. Instead you could use something like:
<a href="#Url.Action("Action", "Controller")" class="btn btn-warning">
link text
<span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
</a>
This works for me in MVC 5:
#Html.ActionLink(" ", "EditResources", "NicheSites", new { ViewBag.dbc, item.locale, ViewBag.domainId, domainName = ViewBag.domaiName }, new {#class= "glyphicon glyphicon-edit" })
The first parameter cannot be empty or null or it will blow.
It might be better to just write out the HTML rather than try to make it work with HtmlHelper.ActionLink...
<span>
<a href="#Url.Action("Create")" class="btn btn-warning">
<span class="glyphicon glyphicon-plus-sign"></span>
Create New
</a>
</span>
Here's mine. Inspired by Andrey Burykin
public static class BensHtmlHelpers
{
public static MvcHtmlString IconLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues, String iconName, object htmlAttributes = null)
{
var linkMarkup = htmlHelper.ActionLink(linkText, actionName, routeValues, htmlAttributes).ToHtmlString();
var iconMarkup = String.Format("<span class=\"{0}\" aria-hidden=\"true\"></span>", iconName);
return new MvcHtmlString(linkMarkup.Insert(linkMarkup.IndexOf(#"</a>"), iconMarkup));
}
}
I should go with the approach of #Url.Action instead of #Html.ActionLink, se example code below:
<span>
<span class="glyphicon glyphicon-plus-sign"></span> Create New
</span>
You can use simple extension:
private static readonly String SPAN_FORMAT = "<span class=\"{0}\" aria-hidden=\"true\"></span>";
private static readonly String A_END = "</a>";
public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues, String iconName, object htmlAttributes = null)
{
var linkMarkup = htmlHelper.ActionLink(linkText, actionName, routeValues, htmlAttributes).ToHtmlString();
if (!linkMarkup.EndsWith(A_END))
throw new ArgumentException();
var iconMarkup = String.Format(SPAN_FORMAT, iconName);
return new MvcHtmlString(linkMarkup.Insert(linkMarkup.Length - A_END.Length, iconMarkup));
}
Usage:
Html.ActionLink(" ", "DeleteChart", new { Id = _.Id }, "glyphicon glyphicon-trash")
Try it!
#Html.ActionLink(" Cerrar sesiĆ³n", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" , #class = "glyphicon glyphicon-log-in" })
Let's have a try on this. Let me know if it is working .Thanks
<style>
span.super a
{
font: (your application font) !important;
}
</style>
<span class="super">
#Html.ActionLink(" Create New", "Create", null, new { #class = "btn btn-warning glyphicon glyphicon-plus-sign" })
</span>
How about using Html.BeginForm with a FormMethod.Get / FormMethod.Post
#using (Html.BeginForm("Action", "Controller", new { Area = "" },
FormMethod.Get, htmlAttributes: new { title = "SomeTitle" }))
{
<button type="submit" class="btn-link" role="menuitem">
<i class="glyphicon glyphicon-plus-sign"></i>Create New</button>
}
Try this. Worked for me.
<button class="btn btn-primary"><i class ="fa fa-plus">#Html.ActionLink(" ", "Create", "Home")</i></button>
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') }
});
})
I created with Razor 3 radiobuttons:
#Html.RadioButtonFor(model => model.SelectType, "Checking")
#Html.RadioButtonFor(model => model.SelectType, "Funds")
#Html.RadioButtonFor(model => model.SelectType, "MoneyMarket")
Razor created the following HTML:
<input id="SelectType" name="SelectType" type="radio" value="Checking" />
<input id="SelectType" name="SelectType" type="radio" value="Funds" />
<input id="SelectType" name="SelectType" type="radio" value="MoneyMarket" />
Now I want to use JQuery to hide some options depending on which RadioButton was checked. I cannot do it, because each radiobutton has the same Id.
Any ideas how to solve this problem?
Thanks,
z
As tymeJV said just assign the id manually.
#Html.RadioButtonFor(model => model.SelectType, "Checking", new { id = "rbChecking" })
You could just pass an ID:
#Html.RadioButtonFor(m => m.SelectType, "Checking", new { id = "rb1" })
Or, for something a bit more generic, how about defining an extension method that appends the value to the name to produce unique IDs (assuming that you only have one radio button for each value)? Something like:
public static MvcHtmlString UniqueRadioButtonFor<TModel, TValue>(
this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression,
string value)
{
string id = String.Format("{0}_{1}", helper.NameFor(expression), value);
return helper.RadioButtonFor(expression, value, new { id = id });
}
Then, you can just do:
#Html.UniqueRadioButtonFor(m => m.SelectType, "Checking")
And you'll get a unique ID.