1
votes

Is it possible to match a ViewModel property to the matching ModelState.Key value when the ViewModel is a (has a) collection?

Example: To edit a collection of viewmodel items, I am using the extension found here.

That adds a GUID to the id of the fields on the page.

example:

class Pets
{
    string animal;
    string name;
}

For a list of Pets, the generated html source is like this:

<input name="Pets.index" autocomplete="off" value="3905b306-a9..." type="hidden">
<input value="CAT" id="Pets_3905b306-a9...__animal" name="Pets[3905b306-a9...].animal" type="hidden">
<input value="MR. PEPPERS" id="Pets_3905b306-a9...__name" name="Pets[3905b306-a9...].name" type="hidden">


<input name="Pets.index" autocomplete="off" value="23342306-b4..." type="hidden">
<input value="DOG" id="Pets_23342306-b4...__animal" name="Pets[23342306-b4...].animal" type="hidden">
<input value="BRUTICUS" id="Pets_23342306-b4...__name" name="Pets[23342306-b4...].name" type="hidden">

So when this gets bound on post, the ModelState gets loaded with all the form fields. In ModelSTate.Keys, there is:

Pets[23342306-b4...].name
Pets[23342306-b4...].animal
Pets[3905b306-a9...].name
Pets[3905b306-a9...].animal

Everything good so far, but I am doing some business logic validation, things like, cant add new animal if one exists with the same name. In that case, I want to be able to highlight the input field that is in error.

So if my create function fails, it will return an error/key value pair like this:

{ error = "Duplicate Name", key="name" }

So I at least will now what property caused the problem.

But since my repository functions don't know about the view field ids, how can I match the key "name" to the appropriate ModelState key (in this case, either Pets[23342306-b4...].name or Pets[3905b306-a9...].name)?

2

2 Answers

0
votes

If you used the built in functionality of MVC for displaying collections (Html.DisplayFor(m => m.Pets) or Html.EditorFor(m => m.Pets)) with appropriate display/editor template, MVC would render something like this:

Pets[0].name
Pets[0].animal
Pets[1].name
Pets[1].animal

This maps to IEnumerable<Pets> and you know that first item has index of 0, second item 1 etc.

So if the second item has an error, you can set error for the ModelState key "Pets[1].name" for example.

0
votes

If you are using the Html.BeginCollectionItem extension method, like I was, I was able to get around this by not using the GUID. I need the dynamic add and delete, but I was always looking up known items, persons that have an ID, which I had in my editor. So instead of using the GUID, I just assign the ID (uniqueId) in the code below. I could then find the key because I knew it was Person[234232]. Of course if you are adding new items and not displaying selected items, it might not work for you.

        public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName, string uniqueId)
    {
        var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
        string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : uniqueId;

        // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
        html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));

        return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
    }