ValidationSummary displays duplicate messages - asp.net-mvc

If two of textboxes fail validation at once then the ValidationSummary displays the same message twice.
Am I doing something wrong? Or is there a setting I can change to hide duplicate messages?
I have broken it down to the simplest example:
View:
#model MyModel
#Html.ValidationSummary()
#Html.TextBoxFor(model => model.A)
#Html.TextBoxFor(model => model.B)
Model:
public class MyModel : IValidatableObject
{
public int A { get; set; }
public int B { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//Some logic goes here.
yield return new ValidationResult("Validation failed", new[] { "A", "B" });
}
}
Result:

They are not duplicate from the point of view of ValidationSummary - you are assigning model state error to both fields A and B, so there must be 2 errors in validation summary. It doesnt "know" that they are the same.
Easy solutions :
assign model only to one of them
exclude property-assigned errors from summary - Html.ValidationSummary(true)
A little bit harder solution :
make your own ValidationSummary helper, call standard validation summary logic in it, and then filter the result in "select distinct" way (linq is your friend here).
EDIT:
something like this for example :
public static class ValidationExtensions
{
public static MvcHtmlString FilteredValidationSummary(this HtmlHelper html)
{
// do some filtering on html.ViewData.ModelState
return System.Web.Mvc.Html.ValidationExtensions.ValidationSummary(html);
}
}

Whack this is your View
<ul class="validation-summary-errors">
#{
string errorMessage = "", previousError = "";
foreach (ModelState modelState in (ViewContext.ViewData.ModelState.Values)){
foreach (ModelError modelError in modelState.Errors)
{
errorMessage = modelError.ErrorMessage;
if (errorMessage != previousError)
{
<li>#modelError.ErrorMessage</li>
previousError = modelError.ErrorMessage;
}
}
}
}
</ul>
You might be able to improve this as it only works when 2 consecutive errors are the same, if it jumbles the order this might not work, but this will start you off. I suppose you could build an array of error messages and check the error off it each run through, but this solution seems to work most of the time.

ValidationSummary method returns property-level and model-level errors. It just enumerates all validation messages if you don't specify any arguments.
You can:
1) Use different message for field A and B
// logic here
yield return new ValidationResult("Validation failed for left field", new[] { "A" });
// logic here
yield return new ValidationResult("Validation failed for right field", new[] { "B" });
or, in your view
2) Call ValidationSummary with excludePropertyErrors argument set to true - ValidationSummary(true). And place call Html.ValidationMessage[For] near each of your fields.
UPDT:
... and third case:
In your model add common message (model-level):
//logic here
yield return new ValidationResult("Validation failed");
yield return new ValidationResult("any text or empty string", new[] { "A", "B" });
In your view exclude property messages but don't add ValidationMessage for fields:
#model MyModel
#Html.ValidationSummary(true)
#Html.TextBoxFor(model => model.A)
#Html.TextBoxFor(model => model.B)
So you'll get single message and both red boxes.

Related

How to insert custom error message from server side validation attribute

I am working with a custom validation attribute and want to show my validation message, where I will specify a name like this:
return new ValidationResult(FormatErrorMessage("Hobbies"));
I found this html was there on the page:
<span data-valmsg-replace="true" data-valmsg-for="Hobbies" class="field-validation-valid text-danger"></span>
When I return message from the IsValid function of the ValidationAttribute class like this:
return new ValidationResult(FormatErrorMessage("Hobbies"));
Where I specify the name "Hobbies" then it should be added into
<span data-valmsg-for="Hobbies">
Why was my error message was not added?
If you want me to paste full code then please tell me.
I apologize, I'm not completely sure about your question. However, since you said "give me a clue what kind of mistake I have done," I'm going to go with explaining the approach that I've taken in a project which utilizes the ViewModel and a couple of custom validation-handling methods.
I don't think this is a direct answer to your question, but it should give you enough information to be able to make custom methods that build the HTML with the error message content inside it the way you want if you are using MVC w/ razor.
Utilizing ViewModels in razor for validation
Normally, with .NET MVC razor (you didn't mention which version of MVC you were using), you just utilize attributes in the view model for validation, e.g.
public class MyViewModel {
[Required(ErrorMessage = "Required")]
public string Name {get; set;}
}
These automatically get validated on post. This will set the property of the view model "Name" to required, and when the validation fails, the error message will be "Required."
Now, if you are doing some processing server side and doing complex validation that is not easy to setup in attributes, then you can manually add an error to your model, like so (my code for captcha validation):
//inside controller HttpPost method
var isCaptchaValid = ReCaptcha.IsCaptchaValid(Request.Form["g-recaptcha-response"] ?? "");
if (!isCaptchaValid)
{
ModelState.AddModelError("Captcha", "Verification Required");
}
Where "Captcha" is the field name and "Verification Required" is the error message.
Displaying Custom Error Messages
Then, stepping away from the standard approach, I have some classes that will build the Bootstrap 3 class "has-error" for the form-group and a custom error that wraps the error message in a label:
public static class HtmlExtensions {
public static string GetErrorClass(this HtmlHelper htmlHelper, bool hasError)
{
return " has-error";
}
public static MvcHtmlString ErrorLabel<TModel>(this HtmlHelper<TModel> helper, ModelStateDictionary modelState, string fieldName)
{
if (modelState[fieldName] != null )
{
var error = modelState[fieldName].Errors.FirstOrDefault();
if (error != null)
{
var span = new TagBuilder("span");
span.AddCssClass("label label-danger");
span.InnerHtml = error.ErrorMessage;
return MvcHtmlString.Create(span.ToString(TagRenderMode.Normal));
}
}
return MvcHtmlString.Empty;
}
}
From there, you can use it just like any of the other #Html
<div class='form-group#Html.GetErrorClass("Name")'>
<label class='control-label col-xs-12 col-sm-6' for='name'>#Html.ErrorLabel(ViewData.ModelState,"Name") Name:</label>
<div class='col-xs-12 col-sm-6'>
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
</div>
</div>
Anyway, I hope that helps you with what you are trying to do. If this is so far away from an answer to your problem, please let me know and I'll remove the answer.
Applying to your HTML requirements:
Oh, and I guess to build the HTML the way you are wanting to, doing it this way, the extension method would look like this:
public static MvcHtmlString MyErrorExtension<TModel>(this HtmlHelper<TModel> helper, ModelStateDictionary modelState, string fieldName)
{
if (modelState[fieldName] != null)
{
var error = modelState[fieldName].Errors.FirstOrDefault();
//<span data-valmsg-replace="true" data-valmsg-for="Hobbies" class="field-validation-valid text-danger"></span>
var span = new TagBuilder("span");
span.Attributes.Add("data-valmsg-replace", "true");
span.Attributes.Add("data-valmsg-for", fieldName);
span.AddCssClass("field-validation-valid text-danger");
if (error != null)
{
span.InnerHtml = error.ErrorMessage;
}
return MvcHtmlString.Create(span.ToString(TagRenderMode.Normal));
}
return MvcHtmlString.Empty;
}
The above method will create your tag regardless, and it will populate it with the first error it finds if there are any errors for that field name.

ASP.NET MVC sever-side validation of select (dropdown) list selected item

When handling form posts in MVC, I find myself writing some, somewhat, tedious code to ensure that the posted drop down list selections are for valid items in the list. The idea is, there's nothing preventing a post that contains a selected ID that was not originally presented in the drop down list. A user could insert their own item into the drop down list (or otherwise post whatever they want) or maybe the form has been sitting in the window for so long that the items that are now available have changed. Regardless of why it could happen, the fact is, you can't control the data that is posted. Here's some example code of how I deal with:
VM:
public class MyViewModel
{
public string SelectedItemID {get; set;}
public List<Items> AvailableItems {get; set;}
}
View:
#using (Html.BeginForm())
{
#Html.DropDownListFor(m => m.SelectedItemID,
Model.AvailableItems.Select(i => new SelectListItem()
{
Value = i.ID.ToString(),
Text = i.Name,
Selected = (Model.SelectedItemID == i.ID)
}), "Select One")
}
Controller:
[HttpPost]
public ActionResult Index(MyViewModel myVM)
{
bool isValid = true;
try
{
//Reload the available items
myVM.AvailableItems = Repository.GetAvailableItems();
if(!ModelState.IsValid)
{
isValid = false;
}
else
{
//Make sure the SelectedItemID is a real item
if(!myVM.AvailableItems.Any(i => i.ID == myVM.SelectedItemID))
{
isValid = false;
myVM.SelectedItemID = null;
ModelState.AddModelError("SelectedItemID", "Required"); //This gets interesting when the selected ID belongs to a nested VM in a collection.
}
}
if(isValid)
{
//Finally I can process the form
}
}
catch(Exception)
{
ModelState.AddModelError("", "Unable to process your submission. Please try again.");
}
//return an ActionResult
}
Setting the error in ModelState gets especially ugly if the SelectedItemID belongs to a nested view model that is inside a collection. This seems like it should be a standard type of validation but, relative to the ease of performing other validation in asp.net MVC, this is pretty ugly and tedious. Is there an easier way to take care of this?
I think you should look at Tim Coker's response here:
Is it possible to update ModelState.IsValid manually?
Basically, you want to make your viewmodel class inherit from IValidatableObject, and then put some validate logic into it. Subsequent validation calls should fail if your criteria isn't met (ie SelectedItemId not in a fresh db query)

ViewBag behind the scenes

This is code from the Contoso University online example:
Controller:
[HttpGet]
public ActionResult Edit(int id)
{
Department department = departmentService.GetById(id);
PopulateAdministratorDropDownList(department.PersonID);
return View(department);
}
// POST: /Department/Edit/5
[HttpPost]
public ActionResult Edit(Department department)
{
try
{
if (ModelState.IsValid)
{
departmentService.Update(department);
return RedirectToAction("Index");
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem
persists, see your system administrator.");
}
PopulateAdministratorDropDownList(department.PersonID);
return View(department);
}
private void PopulateAdministratorDropDownList(object selectedAdministrator = null)
{
var administrators = instructorService.GetAll().OrderBy(i => i.LastName);
ViewBag.PersonID = new SelectList(administrators, "PersonID", "FullName",
selectedAdministrator);
}
View:
<div class="editor-field">
#Html.DropDownList("PersonID", String.Empty)
#Html.ValidationMessageFor(model => model.PersonID)
</div>
My question is: if in the View we are not accessing the ViewBag.PersonID (we just create a DropDownList, that generates an html select list with the ID="PersonID", without any default selected value), how the hell the ViewBag.PersonID property is binded to that DropDownList? What's happening behind the scenes? This looks like magic!!!
The second question is when Posting the data, I think the controller searches in the view for any html form field whose ID matches a property in the model and that's how we get the selected Department.PersonID on the postback, even if the view code doesn't reference the model (somtheing like model => model.PersonID) right?
Behind the scenes:
The view is calling Html.DropdownList(this HtmlHelper htmlHelper, string name, string optionLabel) which eventually ends up calling SelectExtensions.SelectInternal(htmlHelper, metadata, optionLabel, expression, selectList, allowMultiple, htmlAttributes1) This checks selectList1 for null, and if if it is, it calls SelectExtensions.GetSelectData(htmlHelper, name) which does the magic part of checking the View data for a key matching the name you passed in.
Posting:
Your assumptions here are pretty much right, but the in addition to form fields the framework will also check the query string and route data and any other IValueProvider that is plugged in to the pipe line.

Validation ASP.NET MVC Child Elements

I'm currently working on an ASP.NET MVC 3 application. I'm building a screen with out-of-the-box validation. Allow me to clarify the situation using the following screenshot.
Above you see a collection of TemplateItems. The second column 'Uitvoerder' is required. This works fine in most cases.
The problem however, is that it's not a regular list of items, but that it's structured to represent the hierarchy shown in the UI. Hence the second item is a child of the first, and thus contained in the first TemplateItem object you see.
Validation does not fire for the contained items.
You could argue that the front end model should be flattened and made less complex, but I'd like to avoid that. Is there any way I can have the validation trigger for the child elements as well?
The model looks like this:
public class WorkflowTemplateItemModel
: IValidatableObject
{
public WorkflowTemplateItemModel[] ChildWorkflowTemplateItems { get; set; }
public long? ExecutionParticipantId { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (ExecutionParticipantId == null)
{
yield return new ValidationResult("Contact needs to be specified",new[] {"ExecutionParticipantId"});
}
}
}
The relevant Razor part:
<td>
#Html.DropDownListFor(model => model.ExecutionParticipantId,
Model.AvailableUsers.Select(user => new SelectListItem
{
Text = user.UserName,
Value = user.Id.ToString(),
Selected = (Model.ExecutionParticipantId == user.Id)
}),
string.Empty
)
</td>
and the razor which builds the tree view:
#for (int i = 0; i < Model.ChildWorkflowTemplateItems.Length; i++)
{
#Html.EditorFor(model => model.ChildWorkflowTemplateItems[i], new { Depth = Depth + 1, ParentId = Model.WorkflowItemId, RootModel = GetViewData<CreateWorkflowModel> ("RootModel") })
}
You can do that but you will need to create a custom validation including the client-side validation code.
The process is similar to this one: http://haacked.com/archive/2009/11/18/aspnetmvc2-custom-validation.aspx

asp.net MVC RuleViolation.ErrorMessage into Html.ValidationMessage

I want to use the error message returned by a RuleViolation as the validationMessage of the Html.ValidationMessage(modelName, validationMessage).
Simple Example: Person.cs
public partial class Person
{
public bool IsValid
{
get { return (GetRuleViolations().Count() == 0); }
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
if (String.IsNullOrEmpty(Name))
yield return new RuleViolation("Name required", "Name");
yield break;
}
}
adding errors to modelstate
foreach (RuleViolation issue in errors)
{
modelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
asp page
/createPerson/
<%=Html.TextBox("Name",Model.Name)%>
<%=Html.ValidationMessage("Name", "Name required")%>
What I want to do is use the RuleViolation message instead of "Name required" above. Is this possible?
solution is (from Alexander Prokofyev ) use ValidationMessage with a single paramter
<%=Html.ValidationMessage("Name")%>
this is a very simple example, some of my business rules can throw different RuleViolations on an the input depending on the value.
Many thanks.
Simon
You should use ValidationMessage() helpers with only one parameter like
<%= Html.ValidationMessage("Name") %>
and call code
foreach (var ruleViolation in GetRuleViolations())
ModelState.AddModelError(ruleViolation.PropertyName,
ruleViolation.ErrorMessage);
in controller before returning a view (I suppose you have taken RuleViolation class definition from NerdDinner source).

Resources