Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display ValidationSummary using multiple forms using MVC

I have looked at many of the solutions on offer on this site and others for my problem but none of them seem to work perfectly for my solution.

On my Layout page I have a login area on the top of the screen. This is always present unless a user is logged in. This login form has a ValidationSummary on it and every time I post back using another form on the site the validation for this login form is being triggered.

I'm almost certain that this is down to how I call this login page from my Layout page. It is not a partial view that is in a Shared folder, it is in an Area in my project. On my Layout page I call the login form like this:

@Html.Action("LogOn", "Account", new { area = "Store" })

The logon page contains the following:

@model Project.ViewModels.LogOn_ViewModel

@{
    Layout = null;
}

@using (Ajax.BeginForm("LogOn", "Account", new { @area = "Store" }, new AjaxOptions { HttpMethod = "Post", UpdateTargetId = "LoginContainer", LoadingElementId = "actionLoaderImage" }, new { id="LogonForm" }))
{
    @Html.AntiForgeryToken()

    <div class="loginArea">
        <div class="notRegistered">
            <h4>Not registered yet?</h4>
            Register now<br/>
            @Html.ActionLink("Register", "Index", "SignUp", new { area = "Store" }, new { @class = "greenButton" })
        </div> <!-- /notRegistered -->

        <div class="loginForm">
            <div class="formFields">
                <label class="inline">@Html.LabelFor(m => m.UserName)</label>
                @Html.TextBoxFor(m => m.UserName)
                <label class="inline">@Html.LabelFor(m => m.Password)</label>
                @Html.PasswordFor(m => m.Password)
                <input type="submit" name="LogIn" value="Log in" class="blueButton" />
                <img id="actionLoaderImage" src="@Url.Content("~/Content/web/images/loader.gif")" alt="icon" style="margin-right:15px; display:none;" /> 
                @Html.ValidationSummary()
            </div>
        </div> <!-- /loginForm -->
    </div> <!-- /loginArea -->
}

The login controller is standard stuff:

        // GET: /Account/Logon

        public ActionResult LogOn()
        {
            // if logged in show logged in view
            if (User.Identity.IsAuthenticated)
                return View("LoggedIn");

            return View();
        }


        // POST: /Account/Logon

        [HttpPost]
        public ActionResult LogOn(LogOn_ViewModel model)
        {
            if (ModelState.IsValid)
            {
                if (SecurityService.Login(model.UserName, model.Password, model.RememberMe))
                {
                    return View("LoggedIn");
                }
                else
                {
                    ModelState.AddModelError("", "The user name or password provided is incorrect.");
                }
            }
            return PartialView(model);
        }

I suspect that what is happening here is that Html.Action is 'posting' the login form if a post is occurring elsewhere on the page. This makes sense as the layout page itself would be posted as part of a form post action.

I tried implementing the custom Validator examples from some other SO questions and blogs (http://blogs.imeta.co.uk/MBest/archive/2010/01/19/833.aspx) but I found that using those examples would not display the validation summary with client side validation which is not much use to me.

The solution I am looking for would need to allow both client and server side validations to appear for the correct form. Does anyone have an example of something like this using MVC? I'd like to avoid manually looking after the client side validation using jquery if possible and just to use the framework to handle the work for me.

Thanks for your suggestions, Rich

like image 856
Richard Reddy Avatar asked Nov 03 '22 21:11

Richard Reddy


1 Answers

After playing around with this issue for longer than I care to admit, the answer as always was simple once you know how!

The solution to this issue for anyone else who comes up against it is to rename your action so your POST action is a different name to your GET action. This way, when the Layout page is posted back and triggers the PartialView to also be posted back there is only a GET action for your PartialView so the ValidationSummary will not be triggered.

Based on my example above I have altered the form to postback to another action for my login partial view and this has solved this issue for me.

In my Layout page the link to the partial in the store area remains the same:

@Html.Action("LogOn", "Account", new { area = "Store" })

The logon form action is then altered to point to a new action - not called the same name as the GET action:

    @using (Ajax.BeginForm("ConfirmLogon", "Account", new { @area = "Store" }, new AjaxOptions { HttpMethod = "Post", UpdateTargetId = "LoginContainer", LoadingElementId = "actionLoaderImage" }, new { id="LogonForm" }))
    { 
      .... 
    }

Then I just renamed my controller action so that it has a different action name for the POST action so that when the layout page is called using a post and the partial is loaded up with a POST action that it doesn't call my logon POST action:

        // POST: /Account/ConfirmLogon

        [HttpPost]
        public ActionResult ConfirmLogon(LogOn_ViewModel model)
        {
            if (ModelState.IsValid)
            {
                if (SecurityService.Login(model.UserName, model.Password, model.RememberMe))
                {
                    return PartialView("LoggedIn");
                }
                else
                {
                    ModelState.AddModelError("", "The user name or password provided is incorrect.");
                }
            }
            return PartialView("Logon",model);
        }

Hopefully this will help some others out with this issue. Seems so obvious now but drove me nutty :D

like image 93
Richard Reddy Avatar answered Nov 15 '22 11:11

Richard Reddy