0
votes

ASP.Net MVC 4.0 - Validation Issues With array based properties on ViewModel .

Scenario : When a ViewModel has a string array as a property type,the default Scaffolding template for say, Edit, does not render the that property in the markup.

Say, I have ViewModel setup like this :

Employee.cs

public class Employee
    {
        [Required]
        public int EmpID
        {
            get;
            set;
        }

        [Required]
        public string FirstName
        {
            get;
            set;
        }

        [Required]
        public string LastName
        {
            get;
            set;
        }

        [Required]
        public string[] Skills
        {
            get;
            set;
        }
    }
}

The (strongly typed) Edit View generated by the scaffolding template, as shown below, typically skips the portion relevant to field Skills.

**Employee.cshtml**

@model StringArray.Models.Employee

@{
    ViewBag.Title = "EditEmployee";
}

<h2>EditEmployee</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Employee</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.EmpID)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.EmpID)
            @Html.ValidationMessageFor(model => model.EmpID)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.FirstName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.LastName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

The corresponding Controller code is

..
[HttpGet]
public ActionResult EditEmployee()
        {
            Employee E = new Employee()
            {
                EmpID = 1,
                FirstName = "Sandy",
                LastName = "Peterson",
                Skills = new string[] { "Technology", "Management", "Sports" }
            };

            return View(E);
        }

            [HttpPost]
        public ActionResult EditEmployee(Employee E)
        {

            return View(E);
        }

To get the missing section for the Skills field, I added

  • Snippet to the View

        <div class="editor-label">
           @Html.LabelFor(model => model.Skills)
       </div>
       <div class="editor-field">
           @Html.EditorFor(model => model.Skills)
           @Html.ValidationMessageFor(model => model.Skills)
       </div>
    
  • Corresponding UIHint to the ViewModel

    [UIHint("String[]")] public string[] Skills ...

  • EditorTemplates inside relevant folder as ~\View\shared\EditorTemplates\String[].cshtml and ~\View\shared\EditorTemplates\mystring.cshtml

    string[].cshtml

    @model System.String[]
    @if(Model != null && Model.Any()) 
    {  
    for (int i = 0; i < Model.Length; i++)
    {
    
        @Html.EditorFor(model => model[i], "mystring")
        //Html.ValidationMessageFor(model => model[i])
    }
    }
    

    mystring.cshtml

     @model System.String
                @{
                    //if(Model != null)
                    {
                     //To resolve issue/bug with extra dot getting rendered in the name - like 
                         //Skills.[0], Skills.[1], etc.
                       //ViewData.TemplateInfo.HtmlFieldPrefix=ViewData.TemplateInfo.HtmlFieldPrefix.Replace(".[", "[");
    
                        @Html.TextBoxFor(model => model)
                    }
                }
    

But despite this all, the Validations for the Skills section [with 3 fields/elements - refer the EditEmployee method in Controller above.] are entirely skipped, on postback.

I tried below changes inside the mystring.cshtml EditorTemplate :

//to correct the rendered names in the browser from Skills.[0] to Skills for all the 3 items in the 
//Skills (string array), so that model binding works correctly.
string x = ViewData.TemplateInfo.HtmlFieldPrefix;
x = x.Substring(0, x.LastIndexOf("."));

@Html.TextBoxFor(model =>model, new { Name = x })

Postback WORKS But Validations DON'T, since the "data-valmsg-for" still points to <span class="field-validation-valid" data-valmsg-for="Skills" data-valmsg-replace="true"></span> and thus doesn't apply at granular level - string element level.

Lastly, I tried removing @Html.ValidationMessageFor(model => model.Skills) from the Employee.cshtml and correspondingly adding the same to string[].cshtml as @Html.ValidationMessageFor(model => model[i]). But this led to data-valmsg-for getting rendered for each granular string element like

data-valmsg-for="Skills.[0]" , data-valmsg-for="Skills.[1]" and data-valmsg-for="Skills.[2]", respectively.

Note: Validations work for other fields - EmpID, FirstName LastName, BUT NOT for Skills.

Question How do I set the data-valmsg-for="Skills" for each of the above three granular elements related to Skills property.

I am stuck on this for quite some time now. It would be nice if some one can point out the issue, at the earliest.

Thanks, Sandesh L

1

1 Answers

1
votes

This is where you like to change

   [Required]
    public string[] Skills
    {
        get;
        set;
    }

You are giving validation on the array.

you might want to have a new string class call Skill

[Required]
public string Skill
{
            get;
            set;
}

And you can change to you model with

    [Required]
    public List<Skill> Skills
    {
        get;
        set;
    }

I prefer using List instead of array. Then, you can change you skill view according to the model updated

you template view can be something like

@model IEnumerable<Skill>

<div class="editor-label">
 <h3>@Html.LabelFor(model=> model.Skills)
</h3> 
</div> 

<div class="editor-field"> 
@foreach (var item in Model)
 { @Html.Label(model => item) 
   @Html.TextBoxFor(model => item) <br/> 
} 
@Html.ValidationMessageFor(model => item)