6

I want to build a login form that displays in a sidebar across every page in my site. If the user enters an incorrect user/pass, I want the errors to be displayed above this form (with the rest of the page remaining as it was before), and if he logs in successfully, I want the form to change to a list of information about the user (again, with the rest of the page the same as before the login). I am using an MVC 3 web application project with the default Internet Application template. I have this:

_Layout.cshtml

@{ 
    if (User.Identity.IsAuthenticated)
    {
        Html.RenderAction("ShowUserInfo", "User");
    }
    else
    {
        Html.RenderAction("LogIn", "User");   
    }        
}

UserController

    [ChildActionOnly]
    public PartialViewResult ShowUserInfo()
    {
        // populate loggedInInfo from database based on
        // User.Identity.Name
        return PartialView("_LoggedInInfo", loggedInInfo);
    }

    private ActionResult RedirectToPrevious(string returnUrl)
    {
        if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
            && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
        {
            return Redirect(returnUrl);
        }
        else
        {
            return RedirectToAction("index", "");
        }
    }

    [ChildActionOnly]
    public PartialViewResult LogIn()
    {
        return PartialView("_LogInForm");
    }

    //
    // POST: /User/LogIn

    [HttpPost]
    public ActionResult LogIn(LogInModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            if (Membership.ValidateUser(model.UserName, model.Password))
            {
                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);

                return RedirectToPrevious(returnUrl);
            }
            else
            {
                ModelState.AddModelError("", "The user name or password provided is incorrect.");
            }
        }

        return RedirectToPrevious(returnUrl);
    }

_LogInForm

@model MyProject.Models.LogInModel

<h2>Login</h2>
<p>
    Please enter your username and password. @Html.ActionLink("Register", "register", "user") if you don't have an account.<br />
    @Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.")
</p>

@using (Html.BeginForm("LogIn", "user")) {
   html stuff
}

This works almost as intended, except that when I enter a wrong username/password, the page just reloads with an empty form and no error is displayed. I have tried some other things too, but I either get errors about how I cannot issue a redirect from a partial view or I get the partial view (with the errors showing up) displayed as a whole view, so it shows up as a single page, separate from the rest of the site. If I log in correctly, everything works fine.

How can I get the errors to be correctly displayed above the form? I would rather not use any Ajax or JQuery to do this.

IVlad
  • 43,099
  • 13
  • 111
  • 179

2 Answers2

4

The problem seem to be that you are doing a redirect rather than just returning the appropriet view.

After you've added a model error, you need to return the view instead of performing a redirect :

return View("LoginViewNameGoesHere")

So you do not want to return the partial view here, but the entire view.

Filip Ekberg
  • 36,033
  • 20
  • 126
  • 183
  • Maybe I'm missing something really obvious, but I don't get it. I don't know what my view is - it can be anything. The partial views pertaining to the login process are rendered in every page. So which view do I return? I have no specialized view that deals with the login stuff, just the partial ones I've mentioned. – IVlad Nov 04 '11 at 23:00
  • Ok, so your partial login view kan be displayed on any view page? Then the only option that I can think of is to use Ajax and use the "Update Target", then you can return the partial login view and the place holder for the partial login view will be updated accordingly. Do you get where I am going at? Because you cannot use redirect in this case. – Filip Ekberg Nov 05 '11 at 08:51
  • Yes, my partial login view is displayed everywhere. Can you explain how to use Ajax or recommend a tutorial for this? – IVlad Nov 05 '11 at 10:08
  • Of course! Here's an article from Scott Hanselman talking about it: http://www.hanselman.com/blog/ASPNETMVCPreview4UsingAjaxAndAjaxForm.aspx it's older asp.net mvc though. Here's a thread on SO talking about it http://stackoverflow.com/questions/4961157/why-does-ajax-beginform-replace-my-whole-page get back to me if you need more content I'd be happy to help. – Filip Ekberg Nov 05 '11 at 14:59
0

When doing, RedirectToAction and Redirect, current request ends with http status code for redirection - 3xx, which tells browser to make another request to specified url. This means, all the validation data of current request is lost, and all-new, pure request is made to login url. And you get empty form and no errors.

What you should do is render the login view within current request scope, not via doing redirection. General pattern for redisplaying invalid view

public ActionResult Login(LogInModel model)
{
    if(ModelState.IsValid)
    {
       return RedirectToAction("Home");
    }
    return View(model); //! no redirection
}
archil
  • 39,013
  • 7
  • 65
  • 82