.netasp.net-mvc-3razorformwizarddata-annotations

Client side form validation is not working for jquery wizard using MetadataType


I am using MetadataType to provide my entities with validation capabilities for two separate entities. As a caveat, I am using a client-side wizard using unobtrusive validation with jQuery and JavaScript. The validation seems to work on one entity and not on the other.

After further analysis, I noticed the data-val="true" is not rendered for the entity that is not validating. Here is where it get confusing, the Display attribute is not working for either entity and subsequently only the property name renders instead of the display attribute. I don't get it. I have been searching all over the internet for anyone who has encountered this problem and am coming up blank.

AppName.Domain project:
namespace AppName.Domain.MemberDomain
{
     public class Member
     {
         public string EmailAddress { get; set; }

         public string Password { get; set; }

         public string FirstName { get; set; }

         public string MiddleName { get; set; }

         public string LastName { get; set; }
     }
}


namespace AppName.Domain.MetricsDomain
{
     public class Metrics
     {
         public string PersonalView { get; set; }

         public double CurrentWeight { get; set; }

         public double IdealWeight { get; set; }
     }
}


AppName.Web project:
namespace AppName.Models
{
    [MetadataType(typeof(MemberMetaData))]
    public partial class Member
    {
    }

    public class MemberMetaData
    {
        [Required]
        [StringLength(255)]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email address")]
        public string EmailAddress { get; set; }

        [Required]
        [StringLength(10)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [Required]
        [StringLength(10)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        [Compare("Password", ErrorMessage = "The passwords you entered do not match.")]
        public string ConfirmPassword { get; set; }

        [Required]
        [StringLength(20)]
        [DataType(DataType.Text)]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [StringLength(20)]
        [DataType(DataType.Text)]
        [Display(Name = "Middle Name")]
        public string MiddleName { get; set; }

        [Required]
        [StringLength(20)]
        [DataType(DataType.Text)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
    }

    [MetadataType(typeof(PersonMetricsMetaData))]
    public partial class Metrics
    {
    }

    PersonMetricsMetaData
    {
         [Required]
         [StringLength(255)]
         [Display(Name = "How do you see yourself?")]
         public string PersonalView { get; set; }

         [Required]
         [RegularExpression(@"[0-9]*\.?[0-9]+", ErrorMessage = "You must enter a valid Number.")]
         [Display(Name = "Current Weight")]
         public double CurrentWeight { get; set; }

         [Required]
         [RegularExpression(@"[0-9]*\.?[0-9]+", ErrorMessage = "You must enter a valid Number.")]
         [Display(Name = "Ideal Weight")]
         public double IdealWeight { get; set; }
    }
}


public class RegisterModel
{
        public MetricsDomain.Metrics Metrics { get; set; }
        public MemberDomain.Member Member { get; set; }    
}

Form element for the Member entity:
<div class="elementContainer">
    <input id="Member_FirstName" name="Member.FirstName" type="text" value="" />
    <span class="field-validation-valid" data-valmsg-for="Member.FirstName" data-valmsg-replace="true"></span>
</div>

Form element for the Metrics entity:
<div class="elementContainer">
    <input data-val="true" data-val-number="The field Personal View must be a number." data-val-required="The Personal View field is required." id="Metrics_PersonalView" name="Metrics.PersonalView" type="text" value="" />
    <span class="field-validation-valid" data-valmsg-for="Metrics.PersonalView" data-valmsg-replace="true"></span>
</div>

Here is the html:

@model AppName.Models.RegisterModel

@{
    ViewBag.Title = "Register";
    Layout = "~/Views/Shared/_OuterLayout.cshtml";
}

<style type="text/css">
    .formContainer { display: none; }
</style>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script type="text/javascript">
    var i = 1;
    var stepCount = 1;

    $(function () {

        $(".formContainer:first").fadeIn(); // show first step
        $("#lblCurrentStep").text(stepCount);
        //$(".buttonContainerBottom").hide();
        //$("#divWizardMenu").hide();

        // attach backStep button handler
        // hide on first step
        $("#previous").hide().click(function () {
            if (stepCount > 0)
                $("#lblCurrentStep").text(--stepCount);

            var $step = $(".formContainer:visible"); // get current step
            if ($step.prev().hasClass("formContainer")) { // is there any previous step?
                $step.hide().prev().fadeIn();  // show it and hide current step

                // disable backstep button?
                if (!$step.prev().prev().hasClass("formContainer")) {
                    $("#previous").hide();
                }
            }
        });


        // attach nextStep button handler       
        $("#next").click(function () {

            if (stepCount < 2)
                $("#lblCurrentStep").text(++stepCount);


            var $step = $(".formContainer:visible"); // get current step

            var validator = $("form").validate(); // obtain validator
            var anyError = false;

            $step.find("input").each(function () {
                if (!validator.element(this)) { // validate every input element inside this step
                    anyError = true;
                }

            });

            if (anyError)
                return false; // exit if any error found

        /*
            if ($step.next().hasClass("confirm")) { // is it confirmation?
                // show confirmation asynchronously
                $.post("/Account/Register", $("form").serialize(), function (r) {
                    // inject response in confirmation step
                    $(".formContainer.confirm").html(r);
                });
            }
        */

            if ($step.next().hasClass("formContainer")) { // is there any next step?
                $step.hide().next().fadeIn();  // show it and hide current step
                $("#previous").show();   // recall to show backStep button
            }

            else { // this is last step, submit form
                $("form").submit();
            }


        });

    });

</script>

@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    <div id="divFormContainerMember" class="formContainer">
        <div id="divMemberLeft" class="leftContainer">
            <h1>User, great work.  Tell others about yourself</h1>
        </div>
        <div id="divMemberRight" class="rightContainer">
            <div class="labelContainer">
                @Html.LabelFor(m => m.Member.FirstName)
            </div>
            <div class="elementContainer">
                @Html.TextBoxFor(m => m.Member.FirstName)
                @Html.ValidationMessageFor(m => m.Member.FirstName)
            </div>
            <div class="labelContainer">
                @Html.LabelFor(m => m.Member.MiddleName)
            </div>
            <div class="elementContainer">
                @Html.TextBoxFor(m => m.Member.MiddleName)
                @Html.ValidationMessageFor(m => m.Member.MiddleName)
            </div>
            <div class="labelContainer">
                @Html.LabelFor(m => m.Member.LastName)
            </div>
            <div class="elementContainer">
                @Html.TextBoxFor(m => m.Member.LastName)
                @Html.ValidationMessageFor(m => m.Member.LastName)
            </div>
            <div class="labelContainer">
                @Html.LabelFor(m => m.Member.EmailAddress)
            </div>
            <div class="elementContainer">
                @Html.TextBoxFor(m => m.Member.EmailAddress)
                @Html.ValidationMessageFor(m => m.Member.EmailAddress)
            </div>
            <div class="labelContainer">
                @Html.LabelFor(m => m.Member.Password)
            </div>
            <div class="elementContainer">
                @Html.PasswordFor(m => m.Member.Password)
                @Html.ValidationMessageFor(m => m.Member.Password)
            </div>
            <div class="labelContainer">
                @Html.LabelFor(m => m.Member.NewPassword)
            </div>
            <div class="elementContainer">
                @Html.PasswordFor(m => m.Member.NewPassword)
                @Html.ValidationMessageFor(m => m.Member.NewPassword)
            </div>
        </div>
    </div>
    <div id="divFormContainerMetrics" class="formContainer">
        <div id="divMetricsLeft" class="leftContainer">
            <div class="labelContainer">
                @Html.LabelFor(m => m.Metrics.PersonalView)
            </div>
            <div class="elementContainer">
                @Html.TextBoxFor(m => m.Metrics.PersonalView)
                @Html.ValidationMessageFor(m => m.Metrics.PersonalView)
            </div>
            <div class="labelContainer">
                @Html.LabelFor(m => m.Metrics.CurrentWeight)
            </div>
            <div class="elementContainer">
                @Html.TextBoxFor(m => m.Metrics.CurrentWeight)
                @Html.ValidationMessageFor(m => m.Metrics.CurrentWeight)
            </div>
        <div class="labelContainer">
                @Html.LabelFor(m => m.Metrics.IdealWeight)
            </div>
            <div class="elementContainer">
                @Html.TextBoxFor(m => m.Metrics.IdealWeight)
                @Html.ValidationMessageFor(m => m.Metrics.IdealWeight)
            </div>        
    </div>
        <div id="divMetricsRight" class="rightContainer"> 
        </div>
    </div>
    <div class="buttonContainerBottom">
        <span class="buttonContainerInner">
            &nbsp;<input type="button" id="previous" name="previous" value="Previous" class="orangeButton" />
            <input type="button" id="next" name="next" value="Next" class="orangeButton"/>&nbsp;
            <label id="lblStep" class="wizardStep">Step&nbsp;<label id="lblCurrentStep"></label>&nbsp;of&nbsp;2</label>
        </span>
    </div>
}

Solution

  • I finally found the problem and solution. There were multiple issues. 1) My entities are in a separate project from the web project, partials cannot span multiple projects. Fixed that by making the partial inherit from the entity class. 2) After this was fixed, I begin receiving jquery errors for the password field. Apparently, jquery.unobtrusive.validate has an issue with mvc3 naming and the Compare attribute. I had to perform the fix to the .js file as outlined in this article: Compare (password) attribute. After these changes, things appear to be working correctly.