Search code examples
asp.net-mvcasp.net-mvc-partialviewserver-side-validation

Unable to display partial view in modal on instance of getting server side error in MVC


I am struggling on this part from past 3 days. I have a _Layout page wherein I load a Login partial view in a modal div on click of login button. When I submit if any client side validations are there that will work fine and form will not submit until proper data is given. The problem occurs when server side validation happens. If the credentials are invalid it will redirect to same page but modal will not be displayed. When I once again click on login button the Invalid Credentials error message will be displayed in modal. I want to know whether this is possible by the way I am trying or is there any other way to achieve this.

This is part in the _Layout Page :

<a class="btn btn-style btn-success btn-lg btn-wid" data-target="#modal" data-toggle="modal" id="flogin"> Login </a>

and a modal div in same page below footer:

<div id="modal" class="modal">
    @{Html.RenderPartial("_Login", new Sample.Models.LoginModel());}
</div>

Here's the _Login Partial view:

@model Sample.Models.LoginModel
@section Validation {
    @Scripts.Render("~/bundles/jqueryval")
}

<section id="Login">

    @using (Html.BeginForm("Login", "Home", FormMethod.Post, new { enctype = "multipart/form-data", id = "LoginForm", ReturnUrl = ViewBag.ReturnUrl }))
    { 
        <div class="modal-content wrap col-md-4 col-xs-12 col-sm-12 col-md-offset-4">

            @Html.AntiForgeryToken()
            <div class="row">
                <button type="button" class="close" id="btnLoginModalClose" style="margin-top:-15px" data-dismiss="modal" aria-hidden="true">×</button>
            </div>

            <div class="row-fluid">
                <div id="LoginDialog" class="col-md-12">
                    <div class="modal-header">
                        <h3 id="login" class="modal-title">
                            Freshers Login
                        </h3>
                        <div class="ui-state-error-text">
                            @if (TempData["ModelState"] != null)
                            {
                                @TempData["ModelState"]
                            }

                        </div>
                    </div>

                    <div class="modal-body">
                        <div class="form-group">
                            <span class="ico-email"></span>
                            @Html.TextBoxFor(m => m.UserName, new { id = "floginEmail", placeholder = "Email", type = "email", @class = "input-lg ipt ip-email" })
                            <div class="text-danger">
                                @Html.ValidationMessageFor(model => model.UserName)
                            </div>
                        </div>
                        <div class="form-group">
                            <span class="ico-pwd"></span>
                            @Html.TextBoxFor(m => m.Password, new { id = "floginPassword", placeholder = "Password", type = "password", @class = "input-lg ipt ip-pwd" })

                            <a href="#" id="loginpswdshow" onclick="showhide($(this));"><span style="position:relative" id="sploginpswdshow" class="glyphicon glyphicon-eye-open show"></span></a>
                            <div class="text-danger">
                                @Html.ValidationMessageFor(model => model.Password)
                             </div>
                            </div>
                        <div class="form-group">

                            <div class="input-group">
                                <span class="input-group-addon">
                                    @Html.CheckBoxFor(m => m.RememberMe, new { id = "chkRemember", type = "checkbox", @class = "checkbox" })
                                </span>
                                @Html.LabelFor(m => m.RememberMe, new { @class = "form-control" })
                            </div><!-- /input-group -->
                        </div>
                    </div>
                    <div class="modal-footer" style="margin-top:14px">
                        <input type="submit" id="btnLogin" name="login" style="margin-top:-5px" class="btn btn-primary pull-left btn-flogin" value="Log-in" />
                        <div class="row-fluid">
                            <a class="text-center" style="font-size:medium" onclick="fresherForgotClick();" id="lnkForgot" href="#">Forgot password?</a>
                        </div>
                    </div>
                    <span class="text-info"><strong>Do not have an Account? <a class="btn alert-danger" id="btnCreateAccount">Click here to Create one!</a></strong></span>

                </div>
            </div>
        </div>
    }


</section>

and here is my HomeController which has _Login Post Action result

public ActionResult Login(string redirect, Models.FreshersModel model)
        {
            if (model.IsValid(model.UserName,model.Password))
            {
                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                if (!String.IsNullOrEmpty(redirect) || String.Compare(redirect, "none", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if (redirect != null) Response.Redirect(redirect, true);
                }
                else
                {
                    return View("Index", "User");
                }
            }
            ModelState.AddModelError("", "Incorrect User Credentials");
            //ModelState.AddModelError("", "You entered an invalid password");
            TempData["ModelState"] = "Incorrect User Credentials";
            //return null;
            return RedirectToAction("Index", "Home");
        }

Please let me know if its possible to achieve this by the way am following.


Solution

  • Your workflow should probably follow this:

    1. Instead of posting the form normally, use jQuery or similar to post the form asynchronously.
    2. Return a JsonResult with a true/false success value and optional message.
    3. If the success value is true, refresh the page, otherwise, insert the error message into the modal.

    First, modify your login action to something similar to this:

    public ActionResult Login(Models.FreshersModel model, string returnUrl)
    {
        bool success = false;
        string message = "User name or password is incorrect.";
    
        if (ModelState.IsValid)
        {
            success = model.IsValid(model.UserName,model.Password);
            if(success)
            {
                message = string.Empty;
                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
            }
        }
    
        return Json(new { success = success, message = message });
    }
    

    Second, I've trimmed most of your modal back to (almost) the minimum necessary to make the modal plugin work:

    <div id="LoginDialog" class="modal hide">
        <div class="modal-header">
            <h3 id="login" class="modal-title">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                Freshers Login
            </h3>
        </div>
        <div class="modal-body">
            @using (Html.BeginForm("Login", "Home", new { returnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { id = "LoginForm", role="form" }))
            { 
                @Html.AntiForgeryToken()
    
                <div id="errors" class="ui-state-error-text">
                </div>
    
                <div class="form-group">
                    <span class="ico-email"></span>
                    @Html.TextBoxFor(m => m.UserName, new { id = "floginEmail", placeholder = "Email", type = "email", @class = "input-lg ipt ip-email" })
                    <div class="text-danger">
                        @Html.ValidationMessageFor(model => model.UserName)
                    </div>
                </div>
                <div class="form-group">
                    <span class="ico-pwd"></span>
                    @Html.PasswordFor(m => m.Password, new { id = "floginPassword", placeholder = "Password", @class = "input-lg ipt ip-pwd" })
                    <div class="text-danger">
                        @Html.ValidationMessageFor(model => model.Password)
                     </div>
                    </div>
                <div class="form-group">
                    <div class="input-group">
                        <span class="input-group-addon">
                            @Html.CheckBoxFor(m => m.RememberMe, new { id = "chkRemember", @class = "checkbox" })
                        </span>
                        @Html.LabelFor(m => m.RememberMe, new { @class = "form-control" })
                    </div><!-- /input-group -->
                </div>
            }
        </div>
        <div class="modal-footer">
            <a href="#" class="btn">Close</a>
            <button type="button" class="btn btn-primary" id="btnLogin">Login</button>
        </div>
    </div>
    

    Third, and last, add some jQuery to the layout. If you want, you can wrap this in a Razor conditional, so that it only gets injected into the view if the user isn't authenticated.

    $(function(){
    
        $(document)
            .on('submit', '#LoginForm', function(e) {   
                e.preventDefault(); /* we're taking over the default behaviour */
    
                var url = $(this).attr('action');
                var formData = $(this).serialize();
    
                $.post(url, formData)
                    .fail(function(jqxhr, status, error){
                        $('#errors').html('An error occurred. Please try again.');
                    })
                    .done(function(response, status, jqxhr){
                        if(response.success) {
                            window.location.redirect('/');
                        }
                        else {
                            $('#errors').html(response.message);
                        }
                    });
            })
            .on('click', '#btnSubmit', function(e) {
                e.preventDefault();
                $('#LoginForm').trigger('submit');
            });
    
    });
    

    I've left out some parts, like firing off the modal, but this should get you started.