0

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 Answer 1

1

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)
Sign up to request clarification or add additional context in comments.

2 Comments

Trying out your suggestion, with corresponding chnage to the View like : <div class="editor-label"> <h3>@Html.LabelFor(model=> model.Skills)</h3> </div> <div class="editor-field"> @for(int i =0; i < ((List<StringArray.Models.Skill>)(Model.Skills)).Count; i++) { @Html.Label(Model.Skills[i].ToString()) @Html.TextBox("skill", Model.Skills[i].SkillName}) <br /> } @Html.ValidationMessageFor(model => model.Skills), causes Skill to be null on postback :(.
Sandesh Lohar, make sure you are posting back your Employee model to your controller. I was having the same problem as you where the array would never be valid, but what Miller said is correct. You need to create a new model specific for handling required on the skill itself not the array.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.