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)
{
}
}
}
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.
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
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.
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)
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:
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.
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:
View:
View Model:
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.
This is the page controller:
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
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.
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:
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:
Login Partial View:
Do I need to modify the ViewModel is anyway ?
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 ?
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:
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.
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.
is working on a reply...