Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • ianhoughton 281 posts 605 karma points c-trib
    Nov 04, 2013 @ 23:25
    ianhoughton
    0

    Custom routing for login form

    We're building a site using uSiteBuilder and have gone down the custom controller route. We have a login view with a form, but cannot get the [HttpPost] wired up correctly. Whenever the form is submitted it only hits the Index action. Is this where we need to implement a custom route ?

    Controller:

    public class LoginController : PageController
        {
            public override ActionResult Index(RenderModel model)
            {
                var helper = new UmbracoHelper(UmbracoContext.Current);
    
                var authService = new AuthService(model, helper);
                LoginViewModel loginModel = authService.GetLoginViewModel();
    
                return base.Index(loginModel);
            }
    
            [HttpPost]
            [ValidateAntiForgeryToken]
            public ActionResult HandleLogin(LoginViewModel model)
            {
                //Lets TRY to log the user in       
    
            }

    View:

    @using Vega.USiteBuilder
    @model LoginViewModel
    
    @{
        Layout = "Master.cshtml";
        Html.EnableClientValidation(true);
        Html.EnableUnobtrusiveJavaScript(true);
    }
    
        <div class="container">
        <b>Login</b>
    
            @if (!ViewData.ModelState.IsValid)
            {
    
                <h3>Forgotten your password?</h3>
                <p>
                    Don't worry we all forget our passwords from time to time
                </p>
    
                foreach (ModelState modelState in ViewData.ModelState.Values)
                {
                    var errors = modelState.Errors;
    
                    if (errors.Any())
                    { 
                        <ul>
                            @foreach (ModelError error in errors)
                            {
                                <li><em>@error.ErrorMessage</em></li>
                            }
                        </ul>
                    }
                }
                <p>
                    <a href="/forgotten-password">Remind me</a>
                </p>
            }
    
            @using (Html.BeginForm("HandleLogin","Login", new LoginViewModel(this.Model.Content)))
        {
            @Html.AntiForgeryToken()
            @Html.ValidationSummary(true)
    
            <fieldset>
                <legend>Login</legend>
    
                <div class="editor-label">
                    @Html.LabelFor(model => model.EmailAddress)
                </div>
    
                <div class="editor-field">
                    @Html.EditorFor(model => model.EmailAddress)
                    @Html.ValidationMessageFor(model => model.EmailAddress)
                </div>
    
                <div class="editor-label">
                    @Html.LabelFor(model => model.Password)
                </div>
    
                <div class="editor-field">
                    @Html.EditorFor(model => model.Password)
                    @Html.ValidationMessageFor(model => model.Password)
                </div>
    
                <p><input type="submit" value="Login" /></p>
            </fieldset>
    
        }

    View Model:

    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Web.Mvc;
    using DataAnnotationsExtensions;
    using Umbraco.Core.Models;
    
    namespace ViewModels
    {
        public class LoginViewModel : PageViewModel
        {
            [DisplayName("Email address")]
            [Required(ErrorMessage = "Please enter your email address")]
            [Email(ErrorMessage = "Please enter a valid email address")]
            public string EmailAddress { get; set; }
    
            [UIHint("Password")]
            [Required(ErrorMessage = "Please enter your password")]
            public string Password { get; set; }
    
            [HiddenInput(DisplayValue = false)]
            public string ReturnUrl { get; set; }
    
            public LoginViewModel(IPublishedContent content) : base(content)
            {
            }
    
        }
    }
  • Shannon Deminick 1524 posts 5270 karma points MVP 2x
    Nov 04, 2013 @ 23:36
    Shannon Deminick
    0

    What is PageController? Does it inherit from SurfaceController or not?

    If you have no route specified for this controller and it's not a SurfaceController (which are auto-routed) then there would be no way this controller can even execute via a URL.

    Also, if you inherit from UmbracoController (at a minimum), it gives you access to all of the umbraco services you would want like an UmbracoHelper so you don't have to go creating them yourself.

  • ianhoughton 281 posts 605 karma points c-trib
    Nov 04, 2013 @ 23:40
    ianhoughton
    0

    This is the page controller:

    public class PageController : RenderMvcController
        {
            protected void InitialiseGenericContent(RenderModel model, UmbracoHelper helper)
            {
                var masterPageService = new MasterPageService();
    
                ViewBag.SEO = masterPageService.GetSEO(model);
                ViewBag.OpenGraphTags = masterPageService.GetOpenGraphViewModel(model);
                ViewBag.Social = masterPageService.GetSocialViewModel(model);
                ViewBag.ClientCarousel = masterPageService.GetClientCarousel(model);
    
            }
    
            public override ActionResult Index(RenderModel model)
            {
                var helper = new UmbracoHelper(UmbracoContext.Current);
                InitialiseGenericContent(model, helper);
                return View(model);
            }
        }
  • Shannon Deminick 1524 posts 5270 karma points MVP 2x
    Nov 04, 2013 @ 23:49
    Shannon Deminick
    0

    Ok, RenderMvcController already exposes an UmbracoHelper because it inherits from UmbracoController so you don't need to create an UmbracoHelper, just use the base classes 'Umbraco' property.

    Here's what I think is happening

    • You are hijacking a route - perhaps the page rendering your login form has a document type called "Login" ?
    • That would be why the index action is firing when rendering
    • Hijacking a route doesn't mean that the controller is 'routed' in an MVC way, it is purely a convention to route Umbraco pages to controller/actions. Docs are here: http://our.umbraco.org/documentation/Reference/Templating/Mvc/custom-controllers

    If you want to be able to post to a custom action on a custom controller you will have to declare a route for that.

    Your other option would be to create forms using SurfaceControllers and BeginUmbracoForm. This is the recommended approach because if you don't do this then you cannot easily integrate with the Umbraco pipeline with methods like CurrentUmbracoPage, RedirectToUmbracoPage, RedirectToCurrentUmbracoPage and since SurfaceControllers are auto-routed you don't have to worry about routing. Docs are here: http://our.umbraco.org/documentation/reference/templating/mvc/forms

    If you want to use the same controller for both a Hijacked route and as a SurfaceController, you can inherit from SurfaceController and implement the interface IRenderMvcController.

  • ianhoughton 281 posts 605 karma points c-trib
    Nov 05, 2013 @ 00:29
    ianhoughton
    0

    Thanks for your help :) Yes, the login form has a documenttype called Login.

    Getting closer. I've split the login form into a partial view for the form itself. I call the partial view with this:

    @Html.Partial("LoginForm", Model)

    This is now wired to a surfacecontroller called AuthController. The [HttpPost] action is now getting hit but I'm getting an error "No parameterless constructor defined for this object" when I click the submit button

    Controller:

    public class AuthController : SurfaceController
        {
            [HttpPost]
            [ValidateAntiForgeryToken]
            public ActionResult HandleLogin(LoginViewModel model)
            {
                if (!ModelState.IsValid)
                {
                    return CurrentUmbracoPage();
                }
    
                // Other code removed for display
    
                return PartialView("Login", model);
            }

     Login Partial View:

    @model ViewModels.LoginViewModel
    @{
        // Just a simple test to see if the model is populated
        var test = Model;
    }
    
    @using (Html.BeginUmbracoForm("HandleLogin","Auth", Model))
        {
        @Html.AntiForgeryToken()
        @Html.ValidationSummary(true)
    Login
    @Html.LabelFor(model => model.EmailAddress)
    @Html.EditorFor(model => model.EmailAddress) @Html.ValidationMessageFor(model => model.EmailAddress)
    @Html.LabelFor(model => model.Password)
    @Html.EditorFor(model => model.Password) @Html.ValidationMessageFor(model => model.Password)

    }
    

     Do I need to modify the ViewModel is anyway ?

  • Shannon Deminick 1524 posts 5270 karma points MVP 2x
    Nov 05, 2013 @ 00:36
    Shannon Deminick
    101

    If you are going to use SurfaceController for your forms, please follow the instructions here http://our.umbraco.org/documentation/reference/templating/mvc/forms

    with BeginUmbracoForm so that your form can integrate with the Umbraco pipeline properly.

    Do you have a full stack trace for your error? You say that "The [HttpPost] action is now getting hit" so is this error occuring after that ?

  • ianhoughton 281 posts 605 karma points c-trib
    Nov 05, 2013 @ 01:03
    ianhoughton
    0

    Stripped everything back and started with a real simple form which is now working. I think the issue was that the LoginViewModel was inheriting from PageViewModel (part of uSiteBuilder) hence the empty constructor error. I created a seperate LoginModel and this form code:

    @using (Html.BeginUmbracoForm<AuthSurfaceController>("HandleLogin"))
    {
        @Html.AntiForgeryToken()
        @Html.ValidationSummary(true)
    
        @Html.EditorFor(x => Model)
        <input type="submit" />
    }

    The login form model now gets passed in and I can get the username and password from the form.

    Thanks for your help Shannon, much appreciated.

  • Shannon Deminick 1524 posts 5270 karma points MVP 2x
    Nov 05, 2013 @ 01:07
    Shannon Deminick
    0

    Great! glad you got it working :)

    Just so you know, if you don't use BeginUmbracoForm then you don't get the benefits of integrating directly with the Umbraco Urls which means you'll be posting to normal URLs and won't be able to use things like CurrentUmbracoPage, RedirectToUmbracoPage, etc... In some cases that might be what you want but generally if you are rendering Umbraco content pages and want to render a form you'll want to use SurfaceController + BeginUmbracoForm.

Please Sign in or register to post replies

Write your reply to:

Draft