Search code examples
c#model-view-controllerasp.net-mvc-viewmodelmodelstate

Multiple models in one view - login & register using ViewModel - with field & ModelState validation


I have a MVC 5 application that uses multiple models in one view for login & registration. A search on Google & Stack Overflow returns many examples on how to implement this. This example partially works. However, I'm having an issue with validation and I can't seem to find a clear example. If I click the login form without entering any values in the fields I always get a null reference and my ModelState is always valid!

My Login model:

public class LoginModel
{
    [Required(ErrorMessage = "Please enter a valid username.")]
    public string txtUsername { get; set; }

    [Required(ErrorMessage = "The password field is required.")]
    public string txtPassword { get; set; }
}

My Registration model:

public class RegisterModel
{
    [Required(ErrorMessage = "Please enter a first name.")]
    public string txtFirstName { get; set; }

    [Required(ErrorMessage = "Please enter a last name.")]
    public string txtLastName { get; set; }
}

My ViewModel:

public class ViewModelVM
{
    public LoginModel loginModel { get; set; }
    public RegisterModel registerModel { get; set; }

}

My Home Controller submit and register actions:

[HttpPost]
public ActionResult LoginSubmit(ViewModelVM vm)
{
    if (ModelState.IsValid)
    {
        //perform other logic. 
        return View("Index");
    }
    else
    {
        return View("Login", vm.loginModel);
    }
}
[HttpPost]
public ActionResult RegisterSubmit(ViewModelVM vm)
{
    if (ModelState.IsValid)
    { 
        return View("Index");
    }
    else
    {
        return View("Login", vm);
    }
}

My View:

@model MVCTemplate.ViewModel.ViewModelVM

@{
    ViewBag.Title = "Login & Register";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="col-lg-10 mx-auto">
    @using (Html.BeginForm("LoginSubmit", "Home", FormMethod.Post))
    {
        <div id="loginForm" class="p-5">
            <div class="form-group">
                @Html.Label("Username", new { @class = "labeltiny" })
                @Html.TextBox("txtUserName", "", new { @class = "form-control", @PlaceHolder = "Username" })
                @Html.ValidationMessageFor(x => x.loginModel.txtUsername, "", new { @class = "text-danger labeltiny" })
            </div>
            <div class="form-group">
                @Html.Label("Password", new { @class = "labeltiny" })
                @Html.Password("txtPassword", "", new { @class = "form-control", @PlaceHolder = "Password" })
                @Html.ValidationMessageFor(x => x.loginModel.txtPassword, "", new { @class = "text-danger labeltiny" })
            </div>
            <div class="form-group">
                <div class="row">
                    <div class="col-md-12 text-center">
                        <input type="submit" name="btnLogin" id="btnLogin" tabindex="4" class="form-control btn btn-login" value="Log In" onclick="sweetalertSpinner()">
                    </div>
                </div>
            </div>
        </div>
    }

    @using (Html.BeginForm("RegisterSubmit", "Home", FormMethod.Post))
    {
        <div id="registerForm" class="p-5" style="display: none">
            <div class="form-group">
                @Html.Label("First Name", new { @class = "labeltiny" })
                @Html.TextBox("txtFirstName", "", new { @class = "form-control", @PlaceHolder = "Fist Name" })
                @Html.ValidationMessageFor(x => x.registerModel.txtFirstName, "", new { @class = "text-danger labeltiny" })
            </div>
            <div class="form-group">
                @Html.Label("Last Name", new { @class = "labeltiny" })
                @Html.Password("txtLastName", "", new { @class = "form-control", @PlaceHolder = "Last Name" })
                @Html.ValidationMessageFor(x => x.registerModel.txtLastName, "", new { @class = "text-danger labeltiny" })
            </div>
            <div class="form-group">
                <div class="row">
                    <div class="col-md-12 text-center">
                        <input type="submit" name="btnRegister" id="btnRegister" tabindex="4" class="form-control btn btn-login" value="Register" onclick="sweetalertSpinner()">
                    </div>
                </div>
            </div>
        </div>
    }
</div>

Solution

  • I found what appears to be the solution. Add the following to the ViewModel

    public class ViewModelVM
    {
        public LoginModel loginModel { get; set; }
        public RegisterModel registerModel { get; set; }
        public string txtUsername { get; set; }
        public string txtPassword { get; set; }
        public string txtFirstName { get; set; }
        public string txtLastName { get; set; }
    }
    

    Change the View as follows for each form field (removing reference to loginModel and registerModel):

    @Html.ValidationMessageFor(x => x.txtUsername, "", new { @class = "text-danger labeltiny" })
    

    Pass the LoginModel as a paramater instead of the ViewModelVM in the controller like this (do the same with RegisterSubmit):

    [HttpPost]
    public ActionResult LoginSubmit(LoginModel lm)
    {
        ViewModelVM vm = new ViewModelVM();
        if (ModelState.IsValid)
        {
            //perform other logic using form fields lm.txtUsername, etc... 
            return View("Index");
        }
        else
        {
            return View("Login", vm);
        }
    }