Both login and registration form on one view MVC3 Razor

14,493

Solution 1

SOLVED!!! Used Html.RenderAction instead. Here is code:

Index.cshtml

<!-- Login form -->
@{ Html.RenderAction("Login", "Account"); }

<!-- Register form -->
@{ Html.RenderAction("Register", "Account"); }

Partial views are same…

Controller’s actions:

//
// GET: /Account/Login

public ActionResult Login()
{
    Login model = new Login();

    if (TempData.ContainsKey("Login"))
    {
         ModelStateDictionary externalModelState = (ModelStateDictionary)TempData["Login"];
         foreach (KeyValuePair<string, ModelState> valuePair in externalModelState)
         {
             ModelState.Add(valuePair.Key, valuePair.Value);
         }
    }

    return View("_LoginHome", model);
}

// 
// POST: /Account/Login

[HttpPost]
public ActionResult Login(Login model)
{
    if (Request.IsAuthenticated)
    {
        return RedirectToAction("Index", "User", new { id = HttpContext.User.Identity.Name });
    }
    else
    {
        if (ModelState.IsValid)
        {
            if (model.ProcessLogin())
            {
                return RedirectToAction("Index", "User", new { id = HttpContext.Session["id"] });
            }
        }                
    }

    TempData.Remove("Login");
    TempData.Add("Login", ModelState);

    // If we got this far, something failed, redisplay form
    return RedirectToAction("Index", "Home");
}

Same things for Register actions.

As u can see I’m using TempData to store model state, and then retrieve it when action is begin render. Other question is how we can know where we should come back if model state is invalid. My solution is to parse Request.UrlReferrer, like ataddeini said.

Big thanks to my friend Danylchuk Yaroslav for him help!

Solution 2

In this case I would recommend having a central login/registration page where users will be redirected to when validation fails. For the purpose of example, let's say this is a view named SiteAuthenticate

So in the case of your LogIn(Login model) action, if validation fails, you would always return the SiteAuthenticate view which would be strongly typed to include the Login and Register models, very similar to your Home model.

That last catch to make this approach work would be to add a ReturnUrl property to your Login and Register models. This could be populated however you wish, or using something like Request.UrlReferrer. This way you'll know where to send the user back to when they eventually login/register successfully.

So, when all is said and done, your LogIn action might look something like this:

[HttpPost]
public ActionResult LogIn(Login model)
{
    if (Request.IsAuthenticated)
    {
        return RedirectToAction(model.ReturnUrl);
    }
    else
    {
        if (ModelState.IsValid)
        {
            if (model.ProcessLogin())
            {
                return RedirectToAction(model.ReturnUrl);
            }
         }
     }

     // If we got this far, something failed. Return the generic SiteAuthenticate view
     var saModel = new SiteAuthModel { LoginModel = model };
     return View("~/Views/Account/SiteAuthenticate.cshtml", saModel);
}

Your SiteAuthenticate would also then post back to the same Login action. Since you've passed along the original RetunrUrl, if login is then successful, the use will be redirected to their original destination as planned.

Hope that helps, let me know if anything is unclear or doesn't make sense.

Share:
14,493
Nazar Grynko
Author by

Nazar Grynko

Updated on June 12, 2022

Comments

  • Nazar Grynko
    Nazar Grynko almost 2 years

    My task is create both login and registration form on one view!

    I have home view. On a home view I have two forms, login and register. They rendered by @Html.RenderPartial(partial view name, model). That forms relates to Account controller (Login and Register actions).

    Let me first provide you with some code…

    Models:

    // Login model, that contains email and password
    public class Login
    {
        …
    }
    // Registration model that contains minimum user data, e.g. name, surname
    public class Register
    {
        ...
    }
    // Model for home view which contains models for both register and login
    public class Home
    {
        public Login    LoginModel    { get; set; }
        public Register RegisterModel { get; set; }
    }
    

    Views: Index.cshtml

    @model App.Models.Home.Home
    <!-- Login Box -->
    @{ Html.RenderPartial("_LoginArea", Model.LoginModel); }
    <!-- Register Box -->
    @{ Html.RenderPartial("_RegisterArea", Model.RegisterModel); }
    

    _LoginArea.cshtml

    @model App.Models.Account.Login
    <div style="float: left; margin-right: 100px;">
        <h1>@Localization.Account.Login</h1>
        <!-- Login form -->
        <div id="loginBox">
        @using (Html.BeginForm("Login", "Account", FormMethod.Post)) 
        {    
            <div class="editor-label">
            @Html.LabelFor(m => m.Email):
            </div>
            <div class="editor-field">
            @Html.TextBoxFor(m => m.Email)
            @Html.ValidationMessageFor(m => m.Email)
            </div>
            <div class="editor-label">
            @Html.LabelFor(m => m.Password):
            </div>
            <div class="editor-field">
            @Html.PasswordFor(m => m.Password)
            @Html.ValidationMessageFor(m => m.Password)
            </div>
            <div class="editor-label">
            @Html.CheckBoxFor(m => m.RememberMe)
            @Html.LabelFor(m => m.RememberMe)
            </div>
            <p>
                <input type="submit" value="@Localization.Account.Login" />
            </p>    
        }
        </div>
    </div>
    

    _RegisterArea.cshtml

    @model App.Models.Account.Register
    <div style="vertical-align: bottom;">
        <h1>@Localization.Account.Register</h1>
        <div>
        @using (Html.BeginForm("Register", "Account"))
        {
            //same things like in login
        } 
        ...
    

    Controllers:

    HomeController

    //
    // GET: /Home/
    
    public ActionResult Index(Home model)
    {
        //
        // If logedin redirect to profile page
        // Else show home page view
        //
    
        if (Request.IsAuthenticated)
        {
            return RedirectToAction("Index", "User", new { id = HttpContext.User.Identity.Name });
        }
        else
        {
            model.LoginModel = new Login();
            model.RegisterModel = new Register();
            return View(model);
        }
    }
    

    Just show you Login action from Account controller

    // 
    // POST: /Account/Login
    
    [HttpPost]
    public ActionResult LogIn(Login model)
    {
        if (Request.IsAuthenticated)
        {
            return RedirectToAction("Index", "User", new { id = HttpContext.User.Identity.Name });
        }
        else
        {
            if (ModelState.IsValid)
            {
                if (model.ProcessLogin())
                {
                    return RedirectToAction("Index", "User", new { id = HttpContext.Session["id"] });
                }
             }
         }
    
         //Note: this is problem part
         // If we got this far, something failed, redisplay form
         return View("~/Views/Home/Index.cshtml", model);
    }
    

    So everything works fine, BUT! When model state is not valid I have following exception

    The model item passed into the dictionary is of type 'App.Models.Account.Login', but this dictionary requires a model item of type App.Models.Home.Home'.
    

    I can bind that partial views and account actions to Home model, but this is not what I need. I planning to use _LoginArea.cshtml view in other views (for example in a page header of ‘User’ view, and that view has another model) So, I need action method (e.g. Login) to retrieve Login model, not Home or whatever else. But in this case I’m unable to return model to Home view. How can I solve this issue? What is a best proper way? Sorry for a lot of code, just want clear things.