Question

I have an MVC 3 project where I have 1 view LoginRegister which contains 2 views with forms for login and pre-register. The problem is after incorrectly completing the pre-register form and using PartialView("LoginRegister", loginRegisterViewModel) validation messages aren't displayed due the ModelState being lost. Before reading the next paragraph it's probably best to jump to the CODE.

Debugging the PartialView("LoginRegister", loginRegisterViewModel) and stepping into the PreRegister view to the following @Html.ErrorMessageFor(model => model.Email). Where the ModelState does not contain the Email key (see below) and hence doesn't display an error message.

    private static MvcHtmlString ErrorMessageHelper(this HtmlHelper htmlHelper, ModelMetadata modelMetadata, string expression, string validationMessage, IDictionary<string, object> htmlAttributes)
    {
        string modelName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);

        if (!htmlHelper.ViewData.ModelState.ContainsKey(modelName)) // ModelState contains no keys, e.g. Email not found and a null is returned
        {
            return null; 
        }

        // code continues here to render error message
    }

CODE

ViewModels

LoginRegisterViewModel

namespace Site.ViewModels.Account
{
    public class LoginRegisterViewModel
    {
        public bool IsDialog { get; set; }
        public PreRegisterViewModel PreRegister { get; set; }
        public QuickLoginViewModel QuickLogin { get; set; }
    }
}

PreRegisterViewModel

using FluentValidation.Attributes;
using Site.ViewModels.Validators;

namespace Site.ViewModels.Account
{
    [Validator(typeof(PreRegisterViewModelValidator))]
    public class PreRegisterViewModel
    {
        public string Email { get; set; }
        public string ConfirmEmail { get; set; }
    }
}

Views

LoginRegister

@model Site.ViewModels.Account.LoginRegisterViewModel

@{
    Layout = ViewBag.Layout;
}

<div id="loginReg" class="mod-loginReg">
    <h2>Login and Registration</h2>
    @{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
    @{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}
</div>

PreRegister

@using Site.Classes.MVC.Extensions;

@model Site.ViewModels.Account.PreRegisterViewModel

@using (Html.BeginForm("PreRegister", "Account", FormMethod.Post, new { @class = "form-errorContainer" }))
{
    <div class="mod-loginReg-register">
        <h3>Register</h3>
        <p>Please enter your email address to register.<br /><br /></p>

        <div class="mod-loginReg-form">
            <div class="field-item">
                @Html.LabelFor(model => model.Email)
                @Html.TextBoxFor(model => model.Email, new { @maxlength = "255", @class = "Textbox required email" })
                @Html.ErrorMessageFor(model => model.Email)
            </div>
            <div class="field-item">
                @Html.LabelFor(model => model.ConfirmEmail)
                @Html.TextBoxFor(model => model.ConfirmEmail, new { @maxlength = "255", @class = "Textbox required email" })
                @Html.ErrorMessageFor(model => model.ConfirmEmail)
            </div>
            <div class="button-group">
                @Html.Button("Register", "Register")
            </div>
        </div>
    </div>
}

AccountController

    [RequireHttps]
    public ActionResult LoginRegister(int showDialog)
    {
        var loginRegisterViewModel = new LoginRegisterViewModel();

        if (showDialog == 1)
        {
            ViewBag.Layout = "~/layouts/structure/dialog.cshtml";
            loginRegisterViewModel.IsDialog = true;
        }
        else
        {
            ViewBag.Layout = "~/layouts/structure/2column.cshtml";
        }

        return PartialView(loginRegisterViewModel);
    }

    [RequireHttps]
    public ActionResult PreRegister()
    {
        return PartialView();
    }

    [HttpPost]
    [RequireHttps]
    public ActionResult PreRegister(PreRegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            return PartialView("PreRegisterComplete");
        }

        var loginRegisterViewModel = new LoginRegisterViewModel { PreRegister = model, QuickLogin = new QuickLoginViewModel() };

        return PartialView("LoginRegister", loginRegisterViewModel);
    }
Was it helpful?

Solution

This is caused by the viewData being cleared out when new ViewDataDictionary() is called.

@{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
@{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}

According to MSDN ViewDataDictionary is used for:

Represents a container that is used to pass data between a controller and a view.

I assume you have a good reason for this otherwise you would not have taken the trouble to add the additional parameter.

If you change it to:

@Html.RenderPartial("PreRegister", Model.PreRegister)
@Html.RenderPartial("QuickLogin", Model.QuickLogin)

the ModelState built up in the controller should be available on your views.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top