Tag Helper asp-validation-for is always showing - asp.net

I tried using the Asp.Net Core TagHelper but it doesn't seem to work. However, when using HtmlHelpers it works as expected. My issue is that it always display the error message although the ModelState is valid. Am I doing something wrong or can someone reproduce this error?
<label class="control-label" asp-for="Firstname">Firstname</label>
<input type="text" class="form-control" asp-for="Firstname">
<span class="form-control-feedback" asp-validation-for="Firstname"> This field has an error. </span>
The property Firstname has a Required attribute in the ViewModel.
It works like this:
<label class="control-label" asp-for="Firstname">Firstname</label>
<input type="text" class="form-control" asp-for="Firstname">
#Html.ValidationMessageFor(x => x.Firstname)
Edit:
It seems to work if I don't add the custom error message to the Html element but instead to the ViewModel DataAnnotation, like this:
<label class="control-label" asp-for="Firstname">Firstname</label>
<input type="text" class="form-control" asp-for="Firstname">
<span class="form-control-feedback" asp-validation-for="Firstname"></span>
Model:
[Required(ErrorMessage = "This field has an error.")]
public string Firstname { get; set; }

TL;DR:
Consider putting text inside the tag helpers in scenarios when you really want something
different from the generated value.
Full answer
You practically find the solution on your own, but I think I can still throw in my two cents here.
Most tag helpers work in a manner of generating content on a condition when its content is empty or contain only whitespace characters. For example, the ValidationMessageTagHelper checks it in this way:
var tagHelperContent = await output.GetChildContentAsync();
// We check for whitespace to detect scenarios such as:
// <span validation-for="Name">
// </span>
if (!tagHelperContent.IsEmptyOrWhiteSpace)
{
message = tagHelperContent.GetContent();
}
It gets tag content and then fills up message variable if the content is null, empty or whitespace. The message variable is then used to generate the validation message:
var tagBuilder = Generator.GenerateValidationMessage(
ViewContext,
For.ModelExplorer,
For.Name,
message: message,
tag: null,
htmlAttributes: htmlAttributes);
If the message is null or empty then the generator will provide the model error (see line 858 of DefaultHtmlGenerator);
if (!string.IsNullOrEmpty(message))
{
tagBuilder.InnerHtml.SetContent(message);
}
else if (modelError != null)
{
modelExplorer = modelExplorer ?? ExpressionMetadataProvider.FromStringExpression(
expression,
viewContext.ViewData,
_metadataProvider);
tagBuilder.InnerHtml.SetContent(
ValidationHelpers.GetModelErrorMessageOrDefault(modelError, entry, modelExplorer));
}
The GetModelErrorMessageOrDefault() of ValidationHelpers:
public static string GetModelErrorMessageOrDefault(
ModelError modelError,
ModelStateEntry containingEntry,
ModelExplorer modelExplorer)
{
Debug.Assert(modelError != null);
Debug.Assert(containingEntry != null);
Debug.Assert(modelExplorer != null);
if (!string.IsNullOrEmpty(modelError.ErrorMessage))
{
return modelError.ErrorMessage;
}
// Default in the ValidationMessage case is a fallback error message.
var attemptedValue = containingEntry.AttemptedValue ?? "null";
return modelExplorer.Metadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(attemptedValue);
}
So yes, if you put any text inside the <span> validation tag, the tag helper will choose your text over validation error from model state. Similar behaviour occurs if you put text inside the <label> tag as you did:
<label class="control-label" asp-for="Firstname">Firstname</label>
The tag helper will not overwrite the Firstname value you put inside the tag. It may not seem as bad behaviour, but if you would like to use display name for the Firstname property:
[Display(Name = "Fancy first name")]
public string Firstname { get; set; }
you would not see it work! Because the tag helper would again choose the text you put in-between <label> tags over the display name for Firstname.
What you should do is leave it as simple as i can be:
<label class="control-label" asp-for="Firstname"></label>
Consider putting text inside the tag helpers in scenarios when you really want something
different from the generated value.
At the begging I said that most tag helpers work that way. Most of them do, but not all of them. For example SelectTagHelper allows you to put any custom text inside the tag and if you provide a select list, it will generate the options by appending them to the existing content. It is extremely handy for adding custom <option> tags. For example I can easily add a selected and disabled option, so the dropdown does not have initial value, therefore the user is forced to manually select an option. These lines of code:
<select asp-for="LevelId" asp-items="#Model.Levels" class="custom-select">
<option selected disabled>Select option</option>
</select>
will result in:
<select class="custom-select" data-val="true" data-val-required="'Level Id' must not be empty." id="LevelId" name="LevelId">
<option selected disabled>Select parking level</option>
<option value="9">-2</option>
<option value="8">-1</option>
<option value="7">0</option>
</select>

Related

Remove multiple selection from asp.net core application

I have List<SelectListItem> variable with two values. I want to represent it as dropdown box in html so I'm doing like this.
<div class="form-group row">
<label asp-for="Roles" class="col-sm-2 col-sm-offset-2 form-control-label"></label>
<div class="col-md-6">
<select asp-for="Roles" asp-items="#Model.Roles" class="form-control selectpicker bs-select-hidden"></select>
</div>
</div>
and this code shows me the list with those two items, but it also generates
multiple="multiple"
attribute for select tag.
How can I make not to generate multiple attribute?
The Select Tag Helper automatically makes the select element multiple if your asp-for property is an IEnumerable. The way to avoid that is to use your base class (not a collection) as the asp-for property. The asp-items property should still be a collection since these are the items that will become options in the select list.
In your example this is simply changing your asp-for="Roles" to asp-for="Role"
<div class="form-group row">
<label asp-for="Roles" class="col-sm-2 col-sm-offset-2 form-control-label"></label>
<div class="col-md-6">
<select asp-for="Role" asp-items="#Model.Roles" class="form-control selectpicker bs-select-hidden"></select>
</div>
You may need to adjust your view model being passed to the view so that it has access to the base class Role, as well as the collection of Roles to be enumerated
ASP NET Core Select Tag Helper Reference
The select tag helper will automatically generate a multi-select if the property specified in the asp-for attribute is an IEnumerable check this link
So you can easily solve this problem by using string for name and list for items as follow:
public SelectList Roles { get; set; }
public string Role { get; set; }
Then in view
<select asp-for="Role" asp-items="Model.Roles">
</select>
You can remove this attribute multiple with javascript:
Model:
public int RoleId { get; set; }
public SelectList Roles { get; set; }
Add helper css class for example: single-select
<select asp-for="RoleId" asp-items="#Model.Roles" class="single-select">
</select>
Javascript:
$(function(){
$(".single-select").removeAttr("multiple");
});
Note:
If you submit the form you can check the property called RoleId. The property called Roles is probably only for display all items.
Set multiple=false in your select
Try following
<select multiple="multiple" size="1">

CSS for Form Field Errors

We have a three dropdown date of birth field in our form, which, right now is producing three separate error messages if the fields are left blank and user clicks submission.
We would like to either make it just one of those error messages, or show some other display beneath the submission button.
Any guidance would be fantastic!
Although not mentioned, I'd have to imagine you're using JavaScript to validate the form, and if so, try something like the following, whereby in your HTML you have a single span tag (or p) that's accessible via an id tag (like #error or something). In your JavaScript, simply set the #error tag to whichever error was last seen by the JavaScript. As you'll note below, if both "birth date" and "name" are missing, the span tag will only display the missing name error text. That said, you could easily concatenate the strings if need be.
Pseudocode (JS)
function validateForm() {
var isValid = true,
var myErrorText;
if (birth date is missing) {
isValid = false;
errorText = "You must enter a birth date";
}
if (name is missing) {
isValid = false;
errorText = "You must enter a name";
}
// Display the error message if the form is invalid
if (!validForm) {
$('span#error').text(myErrorText);
}
return validForm;
}
HTML
<form method="post" action="/whatever" onSubmit="return validateForm()">
<input type="text" id="birthDate" name="birthDate" >
<input type="text" id="name" name="name">
<button type="submit" id="submit" class="btn">Submit</button>
</form>
<span id="error"></p>

Move #Html.Labelfor after the element

I have something like this:
#Html.CheckBoxFor(model=>...)
<label>
#Html.DisplayFor(model=>...)
</label>
#Html.HiddenFor(model=>...)
But the final result in browser is:
1)Element
2)Hidden field
3)Label
I need to fix this because a library I use needs the label to be immediately after the Checkbox.
Why are you wrapping a <label> around a DisplayFor? Just use #Html.LabelFor.
Additionally. If you use the #Html.CheckBox.. helpers they will render a hidden field after the checkbox. There is a good reason for this, but requires a lengthy example.
What you probably want is an editor template. This should get you started:
#model Boolean
<input type="checkbox"
id="#ViewData.TemplateInfo.GetFullHtmlFieldId("")"
name="#ViewData.TemplateInfo.GetFullHtmlFieldName("")" />
The file for the code above is called "MyCheckbox" and is in Views/Shared/EditorTemplates
Model:
[UIHint("MyCheckbox")]
public bool MyProp { get; set; }
View:
#Html.EditorFor(x=> x.MyProp)
#Html.LabelFor(x=> x.MyProp)
Output:
<input type="checkbox" id="MyProp" name="MyProp">
<label for="MyProp">MyProp</label>

How to get reference to json key name inside {{#with}} helper?

I want to draw fields using a Handlebars partial that have the key name and value. My json is like this:
{ "someKnownField" : { "textValue" : "the value I want" } }
I want a label with someKnownField as its text, and an input with the value of textValue in it. I use a partial because I have hundreds of these fields and don't want to have to hard code their names.
Here's my partial code, called textfield
<div class="control-group">
<label class="control-label" for="input{{#key}}">{{#key}}</label>
<div class="controls">
<input type="text" id="input{{#index}}" placeholder="{{#key}}" value="{{textValue}}">
</div>
</div>
Now, I can't use a {{#with}} helper, a-la {{#with someKnownField}}{{> textfield}}{{/with}} since a with doesn't give you a #key. {{#each}} has a #key but its of the context within the each node (textValue); So how do you key the key name OF the each node itself?
This does not work, but demonstrates what I need to grab:
<label>{{../#key}}</label>
since it's expecting an id in the parent path, not a calculated value (which doesn't exist anyway, since it's not an array itself).
You've done well, but made a typo. The correct way:
<label>{{#../key}}</label>

How to read checkbox/radio value in freemarker?

I'm working with share forms in alfresco and trying to read the values of ticked checkboxes and checked radio buttons form a form. I extended both the user creation and userprofile form with these input controls and so far I have been unsuccessful at reading the textual values of said controls. Below is a snippet of code:
<div class="row">
<span class="label"><input id="${el}-input-spokenEnglish" type="checkbox" name="spokenLanguages" value="${msg("label.anglais"!"")?html}" /> ${msg("label.anglais")}</span>
<span class="label"><input id="${el}-input-spokenSpanish" type="checkbox" name="spokenLanguages" value="${msg("label.espagnol"!"")?html}" /> ${msg("label.espagnol")}</span>
<span class="label"><input id="${el}-input-spokenGerman" type="checkbox" name="spokenLanguages" value="${msg("label.allemand"!"")?html}" /> ${msg("label.allemand")}</span>
<span class="label"><input id="${el}-input-spokenChinese" type="checkbox" name="spokenLanguages" value="${msg("label.chinois"!"")?html}" /> ${msg("label.chinois")}</span>
<br/>
<span class="label">${msg("label.otherLanguages")} : </span>
<span class="input"><input id="${el}-input-spokenLanguages" type="text" size="30" maxlength="256" value="" <#immutablefield field="spokenLanugages" /> /> </span>
</div>
unfortunately I get nothing so far from whatever is returned and would gladly appreciate some insight into this.fre
If you look at userprofile.get.html.ftl, you'll see the following snippet:
<script type="text/javascript">//<![CDATA[
var userProfile = new Alfresco.UserProfile("${args.htmlid}").setOptions(
{
This means it's triggering a client-side JS file from Alfresco, in this case profile.js (see the head file). So just adding some input fields isn't enough.
You need to extend the client-side JS file.
In the function onEditProfile it gets the Dom elements.
But that's just for showing the actual fiels 'after' it's saved.
In profile.js you'll see: form.setSubmitAsJSON(true); that you have a json object from which you can get your fields.
And in userprofile.post.json.ftl it does a loop on the user.properties:
for (var i=0; i<names.length(); i++)
{
var field = names.get(i);
// look and set simple text input values
var index = field.indexOf("-input-");
if (index != -1)
{
user.properties[field.substring(index + 7)] = json.get(field);
}
// apply person description content field
else if (field.indexOf("-text-biography") != -1)
{
user.properties["persondescription"] = json.get(field);
}
}
user.save();
This probably means that you haven't extended the contentmodel of the cm:person object with your new properties.

Resources