I am trying to implement my own login framework which originated from my ASP.NET projects and is now about to migrate to ASP.NET MVC.
The Problem
Whenever I click the submit button in CommunityBar.cshtml
I am redirected to: localhost/Home/Login?Length=4
and receive a resource not found error
but having spent the last few hours on this problem, together with our handy friend, google, I have yet to find a solution. So I was hoping somebody here might be able to help.
Next a bunch of code:
RouteConfig.cs
// POST home/login
routes.MapRoute(
name: "Login",
url: "Home/Login/{model}/{returnUrl}",
defaults: new { controller = "Home", action = "Login" }
);
CommunityBar.cshtml
@using (Html.BeginForm("Login", "Home", new {ReturnUrl = ViewBag.ReturnUrl}, FormMethod.Post, new {role = "form"}))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new {@class = "danger"})
@Html.TextBoxFor(m => m.LoginUsername, new {@class = "form-input", @placeholder = "Username"})
@Html.ValidationMessageFor(m => m.LoginUsername, "", new {@class = "danger"})
@Html.TextBoxFor(m => m.LoginPassword, new {@class = "form-input", @placeholder = "Password"})
@Html.ValidationMessageFor(m => m.LoginPassword, "", new {@class = "danger"})
@Html.ActionLink("Sign In", "Login", "Home", new {@class = "form-btn"})
}
HomeController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(UserViewData model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
return Content("test");
//return RedirectToLocal(returnUrl);
}
UserViewData Model
public class UserViewData
{
[Required]
[DataType(DataType.Text)]
public string LoginUsername { get; set; }
[Required]
[DataType(DataType.Password)]
public string LoginPassword { get; set; }
public MembershipUser User { get; private set; }
public bool HasMessages { get; private set; }
public List<MembershipUserPrivateMessage> Messages { get; private set; }
public bool HasNotifications { get; private set; }
public List<Subscription> Subscriptions { get; private set; }
public UserViewData(MembershipUser user)
{
UserService userService = new UserService();
this.User = user;
this.HasMessages = userService.CountUnreadPrivateMessages() > 0;
this.Messages = userService.GetPrivateMessages(false).Where(p => !p.IsRead).Take(5).ToList();
this.HasNotifications = false;
this.Subscriptions = null;
}
}
EDIT: One possible solution, is to avoid the POCO structure
RouteConfig.cs
// POST home/login
routes.MapRoute(
name: "Login",
url: "Home/Login/",
defaults: new { controller = "Home", action = "Login" }
);
CommunityBar.cshtml
@using (Html.BeginForm("Login", "Home", FormMethod.Post))
{
@Html.AntiForgeryToken()
<input type="text" name="username" class="form-input" placeholder="Username"/>
<input type="text" name="password" class="form-input" placeholder="Username"/>
<input type="submit" value="Sign In"/>
}
HomeController.cs
// POST: /Home/Login
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(string username, string password)
{
if (!ModelState.IsValid)
{
return Content("fail");
}
return Content("success" + username + password);
}
I personally don't like this, as this is really just a way to hide the problem than an actual solution. So I'm gonna keep this Question alive, if somebody figures our what the problem is with the original code. Or I do...
RouteConfig.cs is fine in this format.
// POST home/login
routes.MapRoute(
name: "Login",
url: "Home/Login/",
defaults: new { controller = "Home", action = "Login" }
);
CommunityBar.cshtml has no way to submit the form as the action link in the OP renders an action tag that just links back to the page
@using (Html.BeginForm("Login", "Home", new {ReturnUrl = ViewBag.ReturnUrl}, FormMethod.Post, new {role = "form"}))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new {@class = "danger"})
@Html.TextBoxFor(m => m.LoginUsername, new {@class = "form-input", @placeholder = "Username"})
@Html.ValidationMessageFor(m => m.LoginUsername, "", new {@class = "danger"})
@Html.TextBoxFor(m => m.LoginPassword, new {@class = "form-input", @placeholder = "Password"})
@Html.ValidationMessageFor(m => m.LoginPassword, "", new {@class = "danger"})
<!-- Remove action link and add Submit button here -->
<button class="form-btn" type="submit">Sign In</button>
}
UserViewData Model needs a parameter less constructor to allow model binder to create model on post. currently it requires dependency. Also looks like this model has multiple roles. Use a simple POCO.
public class LoginModel {
[Required]
[DataType(DataType.Text)]
public string LoginUsername { get; set; }
[Required]
[DataType(DataType.Password)]
public string LoginPassword { get; set; }
}
HomeController.cs needs to make sure it is passing the model on the original get
[HttpGet]
[AllowAnonymous]
public ActionResult Login() {
var model = new LoginModel();//also why default constructor needed
return View(model);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
//...login logic here
//if reach this far then redirect
if (!string.IsNullOrWhiteSpace(returnUrl)) {
return RedirectToLocal(returnUrl);
} else {
return this.RedirectToAction("MyActionNameHere");
}
}