12

I have an Account Controller and when an incorrect username/password is entered in a bootstrap modal, I want it to return a message "Invalid login attempt." from ActionResult Login() to the modal.

My _loginPartialView:

<div id="loginModal" class="modal fade">
<div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            <h4 class="modal-title">Login</h4>
        </div>
        <div class="modal-body">
            <section id="loginForm">
                @using (Ajax.BeginForm("Login", "Account", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "loginModal" }))
                {
                    @Html.AntiForgeryToken()
                    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                    <div class="form-group">
                        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
                        <div class="col-md-10">
                            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
                            @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
                        </div>
                    </div>
                    <div class="form-group">
                        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
                        <div class="col-md-10">
                            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
                            @Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
                        </div>
                    </div>
                    <div style="text-align:right;">
                        <button type="submit" class="btn btn-primary btn-sm">Submmit</button>
                    </div>
                }

            </section>
        </div>
    </div>
</div>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

<script>
    $(document).ready(function () {
        $('.launchLoginModal').click(function () {
            $('#loginModal').modal({
                keyboard: false
            });
        });
    });   
</script>

My Account Controller:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return PartialView("_LoginModalPartial", model);
    }
}
7
  • Have you thought about using Ajax.BeginForm instead and then return your partial modal view back into the page. Commented Apr 24, 2015 at 8:04
  • @Symeon I have tried using Ajax.BeginForm and return PartialView within controller but when I submmited It redirect to localhost:xxxxx/Account/Login and render partial view. Not show a modal dialog. I don't want to redirect to another views Commented Apr 24, 2015 at 8:34
  • Sounds like the signin was unsuccessful, or the login method is not alowing anaonymous - although you do have the [AllowAnonymous] token at the top. Have you tried it in debug to see if it even gets into the Login method? Commented Apr 24, 2015 at 8:47
  • @Symeon Yes, I have debuged and It got into the Login and after It rendered "_LoginModalPartial" in "/Account/Login" View. Commented Apr 24, 2015 at 8:58
  • So it is returning correctly ? if so if you want that error to return only on error then you also need to add some more lines to your switch statement - e.g. case SignInStatus.LockedOut: case SignInStatus.Success: etc so the default only occurs if it is an invalid login attempt Commented Apr 24, 2015 at 9:06

3 Answers 3

1

I added a comment about this, but it may help as your solution. It sounds like invalid inputs on form submission leads to a redirect with the modal content on a blank page? Since the modal is a partial view, try moving your scripts to the top of the _loginPartialView. The same may hold true for any bundles you are using in the layout page.

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<div id="loginModal" class="modal fade">
       ...
</div>
Sign up to request clarification or add additional context in comments.

Comments

1
  • First, since you're using @Ajax helpers, you should install and reference Microsoft jQuery Unobtrusive Ajax (you can find an older but still pertinent guide here: https://www.asp.net/mvc/overview/older-versions/creating-a-mvc-3-application-with-razor-and-unobtrusive-javascript).

  • Second, you should enable client side validation (see in the guide)

  • Third, you need to specify as UpdateTargetId an existing DOM element to be updated (I do not see the "loginModal" in the code sample). I believe that this id should be inside the modal html code, otherwise it will recreate the modal html and reset its status. (I would move the modal definition in the parent view and let the partial with only the form definition).

Comments

1

Generally:

  1. Modify your controller to return JSON results of login attempt
  2. Make submit button it a regular button so it doesn't actually submit.
  3. Use JQuery to build a JSON object
  4. Use JQuery to POST that JSON object to your controller
  5. In your AJAX, check return from controller and act accordingly
  6. Add a small div for user feedback
  7. remove ModelState.AddModelError("", "Invalid login attempt.");
  8. In your controller, try/catch your logic so it always returns JSON to the modal's ajax. Handle the error there (see below)

(You obviously, can use any choice of words like 'success' or whatever.)

Your razor could look something like:

 // on login button click
    $('#btnSubmit').on('click', function() {   
        $("#loginForm").validate(); // using jqueryvalidate
        if ($('#loginForm').valid()){                       // if valid, submit
            // Get values from Modal and put into JSON object mimicking correct model
        var token = $('input[name="__RequestVerificationToken"]').val(); // this is required for the anti-forgery decoration and must be structured outside the model to be submitted
        var model = {
            Email: $('#Email').val(),
            Password: $('#Password').val()
            };
        var ajaxData = {
            __RequestVerificationToken: token,
            model: model
        }
             // send
        $.ajax({
            type: "GET",
            url: '@Url.Action("method", "controller")',
            data: ajaxData,
            success: function (msg) {
                 // ajax success, but see if the login was a success
                 if (msg.IsError == 'true'{ all good} 
                 else { 
                       if (msg.Message != 'success'){
                            $('#UserFeedback').val('Invalid Login') 
                       }
                      }
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                console.info(errorThrown);
                // userfeedback
                $('#UserFeedback').html(errorThrown);

                });
            }
        });
        }
    });

Your controller could return a custom viewmodel like:

public class LoginCheckViewModel
{       
    [DisplayName("IsError")]
    public string IsError {get; set;}

    [DisplayName("Message")]
    public string Message { get; set; }        
}

You controller would check the login as you have, but then return

myLoginCheckViewModel = new LoginCheckViewModel();
myLoginCheckViewModel.IsError = [true/false];
myLoginCheckViewModel.Message = [success/fail];
return Json(myLoginCheckViewModel, JsonRequestBehavior.AllowGet);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.