ValidationMessage - Handle multiple errors for the same property - asp.net-mvc

I'm using ValidationMessage control in MVC. When validating each property, it may have more than one error message to show, but the ValidationMessage only displays the first error message in the list.
Here is an example:
ModelState["Key"] = new ModelState();
ModelState["Key"].Errors.Add("Error 1");
ModelState["Key"].Errors.Add("Error 2");
and in the html I have: <%= Html.ValidationMessage("Key")%>
which displays: "Error 1"
I want to see all error messages on the page which will be "Error 1 Error 2"
Any idea how to do it?

I had exactly the same problem, so I created an extension method for HtmlHelper as replacement for the MVC ValidationMessage method.
The benefit of this over ValidationSummary method is that it displays error message per field so you can place it right next to each field (same as ValidationMessage method).
public static string AllValidationMessage(this HtmlHelper helper, string modelName)
{
StringBuilder builder = new StringBuilder();
TagBuilder ulTag = new TagBuilder("ul");
ulTag.AddCssClass("u-error-list");
builder.Append(ulTag.ToString(TagRenderMode.StartTag));
if (helper.ViewData.ModelState.ContainsKey(modelName) &&
helper.ViewData.ModelState[modelName].Errors.Count > 0)
{
foreach (var err in helper.ViewData.ModelState[modelName].Errors)
{
TagBuilder liTag = new TagBuilder("li") { InnerHtml = HttpUtility.HtmlEncode(err.ErrorMessage) };
liTag.AddCssClass("u-error-item");
builder.Append(liTag.ToString());
}
}
builder.Append(ulTag.ToString(TagRenderMode.EndTag));
var msgSpan = helper.ValidationMessage(modelName, "{placeholder}");
if (msgSpan == null)
return string.Empty;
return msgSpan.ToHtmlString().Replace("{placeholder}", builder.ToString());
}
public static string AllValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
return HtmlHelperExtensions.AllValidationMessage(helper, ExpressionHelper.GetExpressionText(expression));
}
Edit: added AllValidationMessageFor method
Edit: added a null check on msgSpan

With just out-of-the-box MVC, you'll have to add a ValidationSummary:
<%= Html.ValidationSummary() %>
That will show all ModelErrors.

Based on the solutions presented here and in How to display multiple validation errors with #Html.ValidationMessageFor?, I created my own multiline validation message for a property. It behaves somewhat like ValidationSummary but can be used per field. I use it present a validation message for a collection field of a model. This allows me to present a summary message for the collection and only the collection.
public static MvcHtmlString MultilineValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
var propertyName = ExpressionHelper.GetExpressionText(expression);
var modelState = htmlHelper.ViewData.ModelState;
// If we have multiple (server-side) validation errors, collect and present them.
if (modelState.ContainsKey(propertyName) && modelState[propertyName].Errors.Count > 1)
{
var msgs = new StringBuilder();
foreach (ModelError error in modelState[propertyName].Errors)
{
msgs.AppendLine(error.ErrorMessage + "<br />");
}
// Return standard ValidationMessageFor, overriding the message with our concatenated list of messages.
var msgSpan = htmlHelper.ValidationMessageFor(expression, "{0}", htmlAttributes as IDictionary<string, object> ?? htmlAttributes);
var msgDiv = msgSpan.ToHtmlString().Replace("span", "div");
return new MvcHtmlString(string.Format(msgDiv, msgs.ToString()));
}
// Revert to default behaviour.
return htmlHelper.ValidationMessageFor(expression, null, htmlAttributes as IDictionary<string, object> ?? htmlAttributes);
}

A more straight to the point approach:
Controller:
ModelState.AddModelError("other", "error 1");
ModelState.AddModelError("other", "error 2");
ModelState.AddModelError("other", "error 3");
View:
<ul>
#foreach (var error in Html.ViewData.ModelState["other"].Errors)
{
<li>#error.ErrorMessage</li>
}
</ul>

As ModelState follows a dictionary pattern for errors, it seems ultimately we need to concatenate all the errors into the single ModelState key:
ModelState["Key"].Errors.Add("Error 1. " + "Error 2");
If you use the IValidatableObject convention to perform custom validations, you can convert the validation result failures to ModelState entries as follows:
var resultsGroupedByMembers = validationResults
.SelectMany(_ => _.MemberNames.Select(
x => new {MemberName = x ?? "",
Error = _.ErrorMessage}))
.GroupBy(_ => _.MemberName);
foreach (var member in resultsGroupedByMembers)
{
ModelState.AddModelError(
member.Key,
string.Join(". ", member.Select(_ => _.Error)));
}
The cross join is needed noting there may be more than one MemberName per Validation Result. Unbound results are bound to "" and should be available to the ValidationSummary.

Also in your Controller Action you can check the
ModelState.IsValid
and if its false, just return the View and the ValidationSumary will be populated.

Related

Generate label with HTML content

Using MVC and Razor, I want to create a label for a field that contains HTML, namely a hyperlink. However, when I use the Html.LabelFor() method, all HTML is encoded on output.
This screenshot shows the desired result and what MVC actually outputs instead:
Is there a way to generate a label for my model property that will correctly render HTML content?
My ViewModel:
[DisplayName("I accept the Terms & conditions")]
public bool AcceptedTermsAndConditions { get; set; }
My view:
#Html.EditorFor(m => m.AcceptedTermsAndConditions)
#Html.LabelFor(m => m.AcceptedTermsAndConditions)
I also tried to directly pass the content as a "labeltext" parameter, without success:
#Html.LabelFor(m => m.AcceptedTermsAndConditions, "I accept the Terms & conditions")
I ended up writing my own HtmlHelper method based on the decompiled sources of Html.LabelFor():
public static IHtmlString HtmlLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText = null, object htmlAttributes = null)
{
string str = labelText;
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
if (str == null)
{
string displayName = metadata.DisplayName;
if (displayName == null)
{
string propertyName = metadata.PropertyName;
str = propertyName ?? htmlFieldName.Split(new[] {'.'}).Last();
}
else
{
str = displayName;
}
}
string innerHtml = str;
if (string.IsNullOrEmpty(innerHtml))
return MvcHtmlString.Empty;
var tagBuilder = new TagBuilder("label");
tagBuilder.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
tagBuilder.InnerHtml = innerHtml;
tagBuilder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), true);
return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
}
The major change is that I use
tagBuilder.InnerHtml = ..
instead of the original
tagBuilder.SetInnerText(..)
This way the provided labelText does not end up being HTML-encoded.
Usage for my case is:
#Html.HtmlLabelFor(m => m.AcceptedTermsAndConditions)
I got it. So instead of
#Html.LabelFor(m => m.AcceptedTermsAndConditions)
use
Html.GetDisplayName(x => x.AcceptedTermsAndConditions)
Not the cutest, but solution:
#Html.Raw(#HttpUtility.HtmlDecode( Html.LabelFor(x=>x.AcceptedTermsAndConditions).ToString()))
try to use this:
[DisplayName("I accept the <a href='Terms & conditions'>Terms & conditions</a>")]

How to call an Action from a helper and avoid the render?

I'm working on ASP.NE MVC4 and I'm using LungoJS library for the development.
I'm developing a Helper to render LungoJS controls in the way I need.
For the Select inputs, I want to get a JSON that contains the data for each option.
I can get the needed JSON from an Action of a existing Controller.
But when I call the action from the helper, the view renders my html inside a <pre> tag :-(
View
<div class="form" id="address-data">
#Html.LungoInputFor(m => m.CaseServiceCaseModel.CaseAddressModel.AddressDataModel.TerritoryId, "text", #Labels.Territory, #htmlAttributesRO, true)
#Html.LungoSelectFor(m => m.CaseServiceCaseModel.CaseAddressModel.AddressDataModel.TerritoryId, #Labels.Territory, null, true, "GetTerritoryKendo", "Address")
Helper
public static MvcHtmlString LungoSelectFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string label = "", object htmlAttributes = null, bool fieldset = false, string action = "", string controller = "")
{
TagBuilder tag = new TagBuilder("select");
JavaScriptSerializer js = new JavaScriptSerializer();
MvcHtmlString data = ChildActionExtensions.Action(html, action, controller);
object[] json = js.Deserialize<object[]>(data.ToString());
foreach (Dictionary<string,object> item in json)
{
foreach (var property in item)
{
TagBuilder option = new TagBuilder("option");
switch (property.Key)
{
case "Selected":
if ((bool)property.Value)
{
option.MergeAttribute("selected","selected");
}
break;
case "Text":
option.InnerHtml = property.Value.ToString();
break;
case "Value":
option.MergeAttribute("value", property.Value.ToString());
break;
default:
break;
}
tag.InnerHtml += option.ToString(TagRenderMode.Normal);
}
}
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
I don't understand why if I'm getting the htmlstring from the Action in a variable, and after I return only the Select input html, affects the whole page... If I comment ChildActionExtensions.Action(html, action, controller) the view renders good (but without the content I need...)
You should not be using child actions here (ChildActionExtensions.Action helper) because they write directly to the response. What you could do instead is to include a property on your view model that will contain the collection you need and directly pass it as argument to your helper. Alternatively (but this is not a good solution) would be to call your DAL in the helper to retrieve the list.

ASP.NET MVC RadioButtonListFor is always preset

I have a radiobuttonlistFor custom adapter working, but if a users form data is reset, and no data has been previously submitted, one of the radio buttons (the first) is always preselected, I want to avoid this, how can I achieve this?
#Html.RadioButtonForSelectList(model => model.ViewModelForThingCreate.ThingTypeID, Model.ViewModelForCarCreate.CarTypeSelectList)
and:
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(this HtmlHelper<TModel> HTMLHelper,Expression<Func<TModel, TProperty>> Expression, IEnumerable<SelectListItem> ListOfValues)
{
var MetaData = ModelMetadata.FromLambdaExpression(Expression, HTMLHelper.ViewData);
var SB = new StringBuilder();
if (ListOfValues != null)
{
foreach (SelectListItem Item in ListOfValues)
{
var ID = string.Format("{0}_{1}", MetaData.PropertyName, Item.Value);
var Radio = HTMLHelper.RadioButtonFor(Expression, Item.Value, new { id = ID }).ToHtmlString();
SB.AppendFormat("<label class=\"radio inline\" for=\"{0}\">{1} {2}</label>", ID, Radio, HttpUtility.HtmlEncode(Item.Text));
}
}
return MvcHtmlString.Create(SB.ToString());
}
Thanks!
This is your custom helper with the buttons set to not checked. Try this, I assume it will render all radio buttons unchecked.
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(this HtmlHelper<TModel> HTMLHelper,Expression<Func<TModel, TProperty>> Expression, IEnumerable<SelectListItem> ListOfValues)
{
var MetaData = ModelMetadata.FromLambdaExpression(Expression, HTMLHelper.ViewData);
var SB = new StringBuilder();
if (ListOfValues != null)
{
foreach (SelectListItem Item in ListOfValues)
{
var ID = string.Format("{0}_{1}", MetaData.PropertyName, Item.Value);
var Radio = HTMLHelper.RadioButtonFor(Expression, Item.Value, new { id = ID }).ToHtmlString();
SB.AppendFormat("<label class=\"radio inline\" checked="false" for=\"{0}\">{1} {2}</label>", ID, Radio, HttpUtility.HtmlEncode(Item.Text));
}
}
return MvcHtmlString.Create(SB.ToString());
}
I've just tried your method in mvc3 template and it seems to work fine for me. Basically I've created some Model
public class IndexModel
{
public string ID;
public IEnumerable<SelectListItem> Elements;
}
Then created instance and filled values:
var model = new IndexModel()
{
ID = "a",
Elements =
new List<SelectListItem>() {
new SelectListItem() { Text = "test1", Value = "1"},
new SelectListItem() { Text = "test2", Value = "2"}}
};
In view I've used your extension method
<form>
#(Extensions.RadioButtonForSelectList(Html, x => x.ID, Model.Elements))
<button type="reset">Reset</button>
</form>
All seem perfectly fine after launch. Fields are not selected at load and they're cleared after pressing "Reset" button.
Can you give some more details as I'm not sure if I fully understand what are you trying to achieve :-)
EDIT:
Here's example in plain HTML of radio buttons. They're definitely not filled at the beginning and if you want them to be required add required but by default you can send form without selecting any radio button. Also you can make one checked by adding checked as in second example. Are you using some javascript on client side? Maybe it is causing this side-effect? http://jsbin.com/isadun/1
mz
Unfortunately One radio button must always be checked. That is the unfortunate part about radio buttons; however, You could always add a hidden radio button to your form and set the checked property to true; Have your internal code accept a null or whatever you expect if nothing is selected from it.
Try Setting all of the radio buttons value's to unchecked or false
foreach(button in ButtonGroup){
button.checked = false;
}

Set disable attribute based on a condition for Html.TextBoxFor

I want to set disable attribute based on a condition for Html.TextBoxFor in asp.net MVC like below
#Html.TextBoxFor(model => model.ExpireDate, new { style = "width: 70px;", maxlength = "10", id = "expire-date" disabled = (Model.ExpireDate == null ? "disable" : "") })
This helper has two output disabled="disabled " or disabled="". both of theme make the textbox disable.
I want to disable the textbox if Model.ExpireDate == null else I want to enable it
The valid way is:
disabled="disabled"
Browsers also might accept disabled="" but I would recommend you the first approach.
Now this being said I would recommend you writing a custom HTML helper in order to encapsulate this disabling functionality into a reusable piece of code:
using System;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes,
bool disabled
)
{
var attributes = new RouteValueDictionary(htmlAttributes);
if (disabled)
{
attributes["disabled"] = "disabled";
}
return htmlHelper.TextBoxFor(expression, attributes);
}
}
which you could use like this:
#Html.MyTextBoxFor(
model => model.ExpireDate,
new {
style = "width: 70px;",
maxlength = "10",
id = "expire-date"
},
Model.ExpireDate == null
)
and you could bring even more intelligence into this helper:
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes
)
{
var attributes = new RouteValueDictionary(htmlAttributes);
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (metaData.Model == null)
{
attributes["disabled"] = "disabled";
}
return htmlHelper.TextBoxFor(expression, attributes);
}
}
so that now you no longer need to specify the disabled condition:
#Html.MyTextBoxFor(
model => model.ExpireDate,
new {
style = "width: 70px;",
maxlength = "10",
id = "expire-date"
}
)
Actually, the internal behavior is translating the anonymous object to a dictionary.
So what I do in these scenarios is go for a dictionary:
#{
var htmlAttributes = new Dictionary<string, object>
{
{ "class" , "form-control"},
{ "placeholder", "Why?" }
};
if (Model.IsDisabled)
{
htmlAttributes.Add("disabled", "disabled");
}
}
#Html.EditorFor(m => m.Description, new { htmlAttributes = htmlAttributes })
Or, as Stephen commented here:
#Html.EditorFor(m => m.Description,
Model.IsDisabled ? (object)new { disabled = "disabled" } : (object)new { })
I like Darin method. But quick way to solve this,
Html.TextBox("Expiry", null, new { style = "width: 70px;", maxlength = "10", id = "expire-date", disabled = "disabled" }).ToString().Replace("disabled=\"disabled\"", (1 == 2 ? "" : "disabled=\"disabled\""))
One simple approach I have used is conditional rendering:
#(Model.ExpireDate == null ?
#Html.TextBoxFor(m => m.ExpireDate, new { #disabled = "disabled" }) :
#Html.TextBoxFor(m => m.ExpireDate)
)
If you don't use html helpers you may use simple ternary expression like this:
<input name="Field"
value="#Model.Field" tabindex="0"
#(Model.IsDisabledField ? "disabled=\"disabled\"" : "")>
I achieved it using some extension methods
private const string endFieldPattern = "^(.*?)>";
public static MvcHtmlString IsDisabled(this MvcHtmlString htmlString, bool disabled)
{
string rawString = htmlString.ToString();
if (disabled)
{
rawString = Regex.Replace(rawString, endFieldPattern, "$1 disabled=\"disabled\">");
}
return new MvcHtmlString(rawString);
}
public static MvcHtmlString IsReadonly(this MvcHtmlString htmlString, bool #readonly)
{
string rawString = htmlString.ToString();
if (#readonly)
{
rawString = Regex.Replace(rawString, endFieldPattern, "$1 readonly=\"readonly\">");
}
return new MvcHtmlString(rawString);
}
and then....
#Html.TextBoxFor(model => model.Name, new { #class= "someclass"}).IsDisabled(Model.ExpireDate == null)
Is solved this using RouteValueDictionary (works fine as htmlAttributes as it's based on IDictionary) and an extension method:
public static RouteValueDictionary AddIf(this RouteValueDictionary dict, bool condition, string name, object value)
{
if (condition) dict.Add(name, value);
return dict;
}
Usage:
#Html.TextBoxFor(m => m.GovId, new RouteValueDictionary(new { #class = "form-control" })
.AddIf(Model.IsEntityFieldsLocked, "disabled", "disabled"))
Credit goes to https://stackoverflow.com/a/3481969/40939
This is late, but may be helpful to some people.
I have extended #DarinDimitrov's answer to allow for passing a second object that takes any number of boolean html attributes like disabled="disabled" checked="checked", selected="selected" etc.
It will render the attribute only if the property value is true, anything else and the attribute will not be rendered at all.
The custom reuseble HtmlHelper:
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes,
object booleanHtmlAttributes)
{
var attributes = new RouteValueDictionary(htmlAttributes);
//Reflect over the properties of the newly added booleanHtmlAttributes object
foreach (var prop in booleanHtmlAttributes.GetType().GetProperties())
{
//Find only the properties that are true and inject into the main attributes.
//and discard the rest.
if (ValueIsTrue(prop.GetValue(booleanHtmlAttributes, null)))
{
attributes[prop.Name] = prop.Name;
}
}
return htmlHelper.TextBoxFor(expression, attributes);
}
private static bool ValueIsTrue(object obj)
{
bool res = false;
try
{
res = Convert.ToBoolean(obj);
}
catch (FormatException)
{
res = false;
}
catch(InvalidCastException)
{
res = false;
}
return res;
}
}
Which you can use like so:
#Html.MyTextBoxFor(m => Model.Employee.Name
, new { #class = "x-large" , placeholder = "Type something…" }
, new { disabled = true})
if you dont want to use Html Helpers
take look it my solution
disabled="#(your Expression that returns true or false")"
that it
#{
bool isManager = (Session["User"] as User).IsManager;
}
<textarea rows="4" name="LetterManagerNotes" disabled="#(!isManager)"></textarea>
and I think the better way to do it is to do that check in the controller and save it within a variable that is accessible inside the view(Razor engine) in order to make the view free from business logic
Yet another solution would be to create a Dictionary<string, object> before calling TextBoxFor and pass that dictionary. In the dictionary, add "disabled" key only if the textbox is to be diabled. Not the neatest solution but simple and straightforward.
Another approach is to disable the text box on the client side.
In your case you have only one textbox that you need to disable but consider the case where you have multiple input, select, and textarea fields that yout need to disable.
It is much easier to do it via jquery + (since we can not rely on data coming from the client) add some logic to the controller to prevent these fields from being saved.
Here is an example:
<input id="document_Status" name="document.Status" type="hidden" value="2" />
$(document).ready(function () {
disableAll();
}
function disableAll() {
var status = $('#document_Status').val();
if (status != 0) {
$("input").attr('disabled', true);
$("textarea").attr('disabled', true);
$("select").attr('disabled', true);
}
}
I like the extension method approach so you don't have to pass through all possible parameters.
However using Regular expressions can be quite tricky (and somewhat slower) so I used XDocument instead:
public static MvcHtmlString SetDisabled(this MvcHtmlString html, bool isDisabled)
{
var xDocument = XDocument.Parse(html.ToHtmlString());
if (!(xDocument.FirstNode is XElement element))
{
return html;
}
element.SetAttributeValue("disabled", isDisabled ? "disabled" : null);
return MvcHtmlString.Create(element.ToString());
}
Use the extension method like this:
#Html.EditorFor(m => m.MyProperty).SetDisabled(Model.ExpireDate == null)

Html.EditorFor Validation Messages

When using a Html.EditorFor and passing in my ViewModel is it possible to not have each individual form element display their error message.
I'm using the validation summary and as such the error messages are showing up twice. Once for the form element and then again in the summary.
You can remove the individual errors by removing the error message from the ValidationMessage by using "*" as in the example below. If you however have an error message passed then it will be displayed.
<%= Html.ValidationMessage("PropertyName", "*") %>
If you however have an error message passed then it will be displayed. This is also true for the Editor templates or using the new lambda version of Html.Helper as shown below
Html.ValidationMessageFor(m=>m.prop,...)
Hope that helped,
Eddy
You can create a custom validation summary and add all your errors with a special key (in this example i use _FORM. For example :
private const string VALIDATIONSUMMARY_HMTL = "<div class=\"input-validation-error\">{0}</div>";
public static string ValidationSummary(this HtmlHelper helper, bool customErrorOnly)
{
return ValidationSummary(helper, customErrorOnly, "_FORM");
}
public static string ValidationSummary(this HtmlHelper helper, bool customErrorOnly, string errorName)
{
if (helper.ViewData.ModelState.IsValid)
{
return null;
}
string list = "<ul>";
bool displayList = false;
foreach (KeyValuePair<string, ModelState> pair in helper.ViewData.ModelState)
{
foreach (ModelError error in pair.Value.Errors)
{
if (pair.Key.ToUpper() == "_FORM" || !customErrorOnly)
{
list += "<li>" + error.ErrorMessage + "</li>";
displayList = true;
}
}
}
list += "</ul>";
if (!displayList)
{
return null;
}
return string.Format(VALIDATIONSUMMARY_HMTL, list);
}
When you want to add a specific error :
ViewData.ModelState.AddModelError("_FORM", "My error message");

Resources