While using @Html.BeginCollectionItem helper by Steven Sanderson I'm trying to validate the collection items on the server side using the IValidatableObject interface.
I want to prevent the user from selecting two equal items. So for example, given a list of idioms the user speaks, one can postback these values:
English
English
Spanish
The Validate
implementation looks like this:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach(var idiom in Idioms)
{
if(Idioms.Any(i => i != idiom && i.Idiom == idiom.Idiom))
{
yield return new ValidationResult("Idiom already selected", new string[] { "Idiom" });
}
}
}
The problem with this is that the MemberName
("Idiom") passed to the ValidationResult
is different from the MemberName
present in the ModelState
dictionary since the helper by Steven uses Guid
's and looks like this:
[42] = {[Idioms[83c2c6db-0157-42f3-bf3f-f7c9e6bc0a37].Idiom, System.Web.Mvc.ModelState]}
as you can see Idiom != [Idioms[83c2c6db-0157-42f3-bf3f-f7c9e6bc0a37].Idiom
.
In the best case I'd have to have a way of passing for example [Idioms[83c2c6db-0157-42f3-bf3f-f7c9e6bc0a37].Idiom
as the MemberName
but I don't know how to get this info from the validationContext
or even if that's possible at all. This has to be dynamic anyways.
Do you know about any way to overcome this?
After a lot of Googling I found the right way of doing what I want:
Model Validation in ASP.NET MVC 3
To validate (ie. find duplicate entries) in a collection/list property in your ViewModel
you must add a
@Html.ValidationMessageFor(u => u.Idioms)
for the property in your View
and compose the errorMessage
inside the Validate
method. Finally assign the message to the correct property name, that is, Idioms
in my case.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var grouping = Idioms.GroupBy(ui => ui.Idiom);
var duplicates = grouping.Where(group => group.Count() > 1);
if(duplicates.Any())
{
string message = string.Empty;
foreach(var duplicate in duplicates)
{
message += string.Format("{0} was selected {1} times", duplicate.Key, duplicate.Count());
}
yield return new ValidationResult(message, new[] { "Idioms" });
}
}
BONUS
If you want to display each duplicate group in separate lines
(adding line breaks <br>
), do this:
Replace {0} was selected {1} times
with {0} was selected {1} times<br>
and then on the View
side do this:
@Html.Raw(HttpUtility.HtmlDecode(Html.ValidationMessageFor(u => u.Idioms).ToHtmlString()))
Output will be:
French was selected 2 times
English was selected 3 times