1

My login and signup forms are in the same page, and they are separated by a tab element.

Each one of the forms have a ViewModel:

public class RegisterViewModel
{
    public string FullName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string ConfirmPassword { get; set; }
}

public class LoginViewModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}

And since both forms are on the same page, I have a parent ViewModel:

public class LoginAndRegisterViewModel
{
    public LoginViewModel LoginViewModel{ get; set; }
    public RegisterViewModel RegisterViewModel{ get; set; }
}

// Actions...
public ActionResult Register()
{
    return View("Login");
}
public async Task<ActionResult> Register(LoginAndRegisterViewModel model)
{
    // blah.. do stuff here...
    return View("Login", model);
}

But in the main view when I try to render the partials with the forms I get a System.NullReferenceException (Additional information: Object reference not set to an instance of an object.) in Model.LoginViewModel and Model.RegisterViewModel. This is the view:

Login.cshtml:

@model Models.LoginAndRegisterViewModel
...
@Html.Partial("_LoginPartial", Model.LoginViewModel)
...
@Html.Partial("_RegisterPartial", Model.RegisterViewModel)

I have seen that it is common practice to follow this kind of convention, as detailed here in this SO question. I even tried using this PartialOrNull helper but I still get the exception. I also tried the solution based on this "Forms for Deep View Model graphs" article.

Also this didn't work:

@Html.Partial("_LoginPartial", Model.LoginViewModel, new ViewDataDictionary(ViewData){Model = null});

How can I have a register/login form like this? As I said this is EXACTLY the same situation in that other SO question, and everyone reported that the solution worked, but that was for MVC3.

EDIT 1: added the _LoginPartial as reference.

@model Models.LoginViewModel

@{
    using (Html.BeginForm("Login", "Account", new {ReturnUrl = ViewBag.ReturnUrl}, FormMethod.Post))
    {
        @Html.TextBoxFor(m => m.Email)
        @Html.PasswordFor(m => m.Password)
    }
}

EDIT 2: tried the ?? operator and still getting the Exception.

@Html.Partial("_LoginPartial", Model.LoginViewModel ?? new LoginViewModel())

UPDATE: With the accepted answer I made some tests and now it is working without an initialization ViewModel constructor and without sending an empty ViewModel in the Get action.

The only needed requirement is to declare the partial view to have the main model as the @model:

@model Models.LoginAndRegisterViewModel

Then to access the nested ViewModel in the partial:

@model Models.LoginAndRegisterViewModel

@{
    using (Html.BeginForm("Login", "Account", new {ReturnUrl = ViewBag.ReturnUrl}, FormMethod.Post))
    {
        @Html.TextBoxFor(m => m.LoginViewModel.Email)
        @Html.PasswordFor(m => m.LoginViewModel.Password)
    }
}
Community
  • 1
  • 1

1 Answers1

2

You should initialize properties LoginViewModel and RegisterViewModel in the view models constructor

public class LoginAndRegisterViewModel
{
  public LoginAndRegisterViewModel()
  {
    LoginViewModel = new LoginViewModel();
    RegisterViewModel  = new RegisterViewModel ();
  }
  public LoginViewModel LoginViewModel{ get; set; }
  public RegisterViewModel RegisterViewModel{ get; set; }
}

and in the GET method, pass a new instance of the view model

public ActionResult Register()
{
  var model = new LoginAndRegisterViewModel();
  return View("Login", model);
}

and to ensure you model properties post back, pass the model to the partials

@Html.Partial("_LoginPartial", Model)

and change the partial to use LoginAndRegisterViewModel

@model Models.LoginAndRegisterViewModel
@using (Html.BeginForm("Login", "Account", new {ReturnUrl = ViewBag.ReturnUrl}, FormMethod.Post))
{
  @Html.TextBoxFor(m => m.LoginViewModel.Email)
  @Html.PasswordFor(m => m.LoginViewModel.Password)
}

This ensures the that the controls are bound to the view model and generate the correct name attributes

<input type="text: name="LoginViewModel.Email"..>

whereas passing on a property of the model would have generated

<input type="text: name="Email"..>

which would not bind to LoginAndRegisterViewModel on post back and all properties would have been null

  • Still getting the exception. I edited the question and added the _LoginPartial, in case I may be doing something wrong there. – DeboraThaise Nov 19 '14 at 03:50
  • Thing is, if I put the form inside Login, without partials, everything works fine: @Html.PasswordFor(m => m.LoginViewModel.Password). But I'm anticipating for more complex forms, so I need to make these partials work. – DeboraThaise Nov 19 '14 at 03:51
  • Your partial looks fine, and you have correctly declared your model. But even if this worked, you will not be able to post back to `Register` because the controls will not be named correctly. For example it would need to be `` but because your only passing a property to the partial then it will render `` so it wont bind. You would need to change the partials to `@model Models.LoginAndRegisterViewModel` and render the partials as `@Html.Partial("_LoginPartial", Model)` –  Nov 19 '14 at 04:01
  • Thanks! Changing the partials to the parent ViewModel and then having @Html.TextBoxFor(Model => Model.LoginViewModel.Email) inside the partial worked. I'm not getting the exception anymore. (1) Still all the solutions I found where related to MVC 3 and 4, looks like for MVC5 we need to always send the parent ViewModel to partials? Not even the extension helpers worked, like the PartialOrNull. (2) Since the correct answer is in the comment can I still accept your answer or what should I do? What if you edit your answer then I can accept it? – DeboraThaise Nov 19 '14 at 04:07
  • Hi Would this still work if you had 3 level nesting in ViewModles ? e.g ViewModelA->ViewModelB->ViewModelC where -> means "contains" or is there a limit to how may view models you can have nested ? I am having issues where I have 3 view models nested as above and im loosing the data on postback from the inner most view model. I am passing the Partent view model to the partial and in the partial im referencing viewmodelC like Model.ViewmodelB.ViewmodelC.Property – IronHide Apr 16 '16 at 21:32
  • @IronHide, You could have a thousand nested levels if you want. The key to binding is to ensure the `name` attributes of your controls are correct and the best way to do that is to use an `EditorTempate` for each model type rather than partials. If your having problems, ask a question and we will help you solve it :) –  Apr 17 '16 at 00:34
  • @StephenMuecke Thanks Stephen, I was hoping that there was not limit, can you explain how the 'name' attribute needs to be formate, or what are the rules around it, if you let's say have 3 nested views and the deepest one is a list of viewmodels ? Would it be ViewModelA_ViewModelB_ViewModelC_1 ? – IronHide Apr 18 '16 at 10:53
  • @IronHide, Consider how you would access a property in c#. If your model was (say) `Organisation` - `var value = organisation.Employees[2].Qualifications[0].Name;` would get the name of the first qualification of the 3rd employee - just drop `organisation` and thats how you `name` attribute must be - `name="Employees[2].Qualifications[0].Name"` –  Apr 18 '16 at 10:59
  • @StephenMuecke yeah i think they are actually formatted correctly then, but still get all the properties of the models in the list as null... I trying to create a test app that simplifies the problem so i can post the code here – IronHide Apr 18 '16 at 13:25
  • @StephenMuecke ok i figured it out, it was my mistake, i had some fields set to disabled and did know know that they are not being posted back. so that was the reason why all the values were null. Thanks for the help – IronHide Apr 19 '16 at 22:02