2

I made a dropdown menu where a use can log in from the navbar instead of pressing the login button and being redirected to account/login. This was done in a partial view and is rendered in the navbar in _layout.cshtml. the partial view looks like this:



´´´
@using Microsoft.AspNet.Identity
@model Rent_a_Car.Models.LoginViewModel

@{

    if (Request.IsAuthenticated)
    {
        using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
        {
            @Html.AntiForgeryToken()

            <ul class="nav navbar-nav navbar-right">


                <div class="dropdown">
                    <a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                        @User.Identity.GetUserName()
                    </a>

                    <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                        @Html.ActionLink("My account", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage", @class = "dropdown-item" })
                        <a class="dropdown-item disabled" href="#">My orders</a>
                        <div class="dropdown-divider"></div>
                        <a class="dropdown-item" href="javascript:document.getElementById('logoutForm').submit()">Log off</a>

                    </div>
                </div>



                <li></li>
            </ul>
        }
    }
    else
    {
        <div class="dropleft">

            <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Log-In </button>
            <div class="dropdown-menu" aria-labelledby="dropdownMenuButton" style="width: 300px">

                @* form starting *@

                @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
                {
                    @Html.AntiForgeryToken()

                    @Html.ValidationSummary(true, "", new { @class = "text-danger" })


                    <div class="col-lg-12">
                        <div class="text-center">
                            <h3><b>Log In</b></h3>
                        </div>
                        <form id="ajax-login-form" method="post" role="form" autocomplete="off">
                            <div class="form-group">
                                <label for="username">Username</label>
                                @Html.TextBoxFor(m => m.Email, new { @class = "form-control", @placeholder = "Email" })
                            </div>

                            <div class="form-group">
                                <label for="password">Password</label>
                                @Html.PasswordFor(m => m.Password, new { @class = "form-control", @placeholder = "Password" })
                            </div>

                            <div class="form-group">
                                <div class="col-xs-5">


                                    <input type="submit" name="Login" id="login-submit" tabindex="4" class="form-control btn btn-danger" value="Log In">
                                </div>
                            </div>


                            <div class="col-xs-5" align="Center">
                                @Html.CheckBoxFor(m => m.RememberMe)
                                <label for="remember"> Remember Me</label>
                            </div>


                            <div class="form-group">
                                <div class="row">
                                    <div class="col-lg-12">
                                        <div class="text-center">
                                            <a tabindex="5" class="forgot-password">Forgot Password?</a>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </form>
                    </div>

                }
                @*form ending*@

            </div>
        </div>
    }


}


@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

}

´´´

and this is the controller. As you can see, when validation fails, i am returned to the model, something that i don't want.

 public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, change to shouldLockout: true
            var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
            switch (result)
            {
                case SignInStatus.Success:
                    return RedirectToLocal(returnUrl);
                case SignInStatus.LockedOut:
                    return View("Lockout");
                case SignInStatus.RequiresVerification:
                    return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
                case SignInStatus.Failure:
                default:
                    ModelState.AddModelError("", "Invalid login attempt.");
                    return View(model);
            }
        }

I just need the validation error messages to appear on the dropdown menu, without redirecting me to another page. How am i able to achieve this?

6
  • Assuming I understood, it sounds like you want to make sure a username and password are provided, which essentially means do client side validation (Javascript) for however you define them - e.g. required, password complexity, email format (if username is email), etc.. That way you add an initial check (before submitting/processing on the server side) - the server side validation should run too (never replaced - they go hand in hand). Commented Jun 15, 2019 at 15:36
  • Are you using any scripting language in frontend(like jQuery, angular)? If yes, then use the forms validation at client side to prevent the callbacks to the server for validation part. Commented Jun 15, 2019 at 15:48
  • client side validation is not a problem, the problem is with detecting if the email/pass provided are correct e.g serve sided. I am able to bring up the validation but i am redirected to account/login, how can i disable that? Commented Jun 15, 2019 at 15:49
  • Then that's not "validation" (per se) - that's the actual authentication check. You'll have to adjust your login process to do XHR/Ajax (POSTing and handling responses). Commented Jun 15, 2019 at 16:10
  • Mark that controller action to [AllowAnonymous] - see here. Commented Jun 15, 2019 at 16:21

1 Answer 1

0

The big question as other person have asked is why not use javascript to call an api for login for example with ajax post

$.post("your api here",
{
    userName: "",
    password: "",
    rememberMe: ""
},function(data, status){
     //show user what ever
});

this code is simple enough, But if you cannot use javascript because of anything "I wouldn't know" you will have the below long code and some implementations on your existing controllers

I will explain along

You will have to add a ReturnUrl to your form, change your login form to this

@using Microsoft.AspNet.Identity
@model Rent_a_Car.Models.LoginViewModel
@{
if (Request.IsAuthenticated)
{
    using (Html.BeginForm("LogOff", "Account", new { returnUrl = Request.Url.PathAndQuery }, FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
    {
        @Html.AntiForgeryToken()            
        <ul class="nav navbar-nav navbar-right">
            <div class="dropdown">
                <a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                    @User.Identity.GetUserName()
                </a>
                <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                    @Html.ActionLink("My account", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage", @class = "dropdown-item" })
                    <a class="dropdown-item disabled" href="#">My orders</a>
                    <div class="dropdown-divider"></div>
                    <a class="dropdown-item" href="javascript:document.getElementById('logoutForm').submit()">Log off</a>
                </div>
            </div>
        </ul>
    }
}
else
{
    <div class="dropleft">
        <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Log-In </button>
        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton" style="width: 300px">
            @* form starting *@
            @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
            {
                @Html.AntiForgeryToken()
                <p>@ViewBag.error</p>@*Display the error here*@
                <div class="col-lg-12">
                    <div class="text-center">
                        <h3><b>Log In</b></h3>
                    </div>
                    <form id="ajax-login-form" method="post" role="form" autocomplete="off">
                        <div class="form-group">
                            <label for="username">Username</label>
                            @Html.TextBoxFor(m => m.Email, new { @class = "form-control", @placeholder = "Email" })
                        </div>
                        <div class="form-group">
                            <label for="password">Password</label>
                            @Html.PasswordFor(m => m.Password, new { @class = "form-control", @placeholder = "Password" })
                        </div>
                        <div class="form-group">
                            <div class="col-xs-5">
                                <input type="submit" name="Login" id="login-submit" tabindex="4" class="form-control btn btn-danger" value="Log In">
                            </div>
                        </div>
                        <div class="col-xs-5" align="Center">
                            @Html.CheckBoxFor(m => m.RememberMe)
                            <label for="remember"> Remember Me</label>
                        </div>
                        <div class="form-group">
                            <div class="row">
                                <div class="col-lg-12">
                                    <div class="text-center">
                                        <a tabindex="5" class="forgot-password">Forgot Password?</a>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>
                </div>
            }
            @*form ending*@
        </div>
    </div>
}
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Then in your Account Controller, return a redirect to the view where the user is currently, because it is a redirect, you will have to put you model into a TempData which is why @Html.ValidationSummary(true, "", new { @class = "text-danger" }) and some other model error might not work for you, so you do you own validation.

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        //here send back the error and the model with validation message to any view
        TempData["LoginModel"] = model;
        TempData["LoginError"] = "Incorrect details";
        return Redirect(returnUrl);
    }
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
        case SignInStatus.Failure:
        default:
            TempData["LoginModel"] = model;
            TempData["LoginError"] = "Invalid login attempt";
            return Redirect(returnUrl);                    
    }
}

Now this is where you have alot to do.

You need to go to all the possible controllers where the user might hit first (I will advice all your controller) and get the TempData (model) and error. example the Index method of your CarController can be like:

public ActionResult Index(int Id)
{
    LoginViewModel model = (LoginViewModel) TempData["LoginModel"];
    ViewBag.error = TempData["LoginError"];
    //Do other logic
    return View(model);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your answer. I have no problem using Javascript for validation so i will stick to that. I just thought there would be an easier way to do it, but it seems jquery will be easier. Thanks man!

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.