Question

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?

Was it helpful?

Solution

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top