Reputation: 6367
I'm using this construct to host multiple forms in the same view:
When I submit the first form without entering credentials, validation fails. The form reloads as a GET, with a URL of /home/signin
, and the expected error messages don't display.
The controller's ModelState
is invalid as expected, but I can't figure out why this state isn't passed back to the view.
What am I doing wrong? How can I get validation to work with multiple forms?
Here's my code:
Controller
Imports System.Threading.Tasks
Imports System.Web.Mvc
Imports Website.ViewModels.Home
Imports Microsoft.AspNet.Identity.Owin
Namespace Controllers.Home
Partial Public Class HomeController
<AllowAnonymous>
Public Function Index() As ActionResult
Dim oViewModel As SignInOrSignUp
oViewModel = New SignInOrSignUp
Return Me.View("Index", oViewModel)
End Function
<HttpPost>
<ValidateAntiForgeryToken>
Public Async Function SignIn(Model As SignIn) As Task(Of ActionResult)
Dim oViewModel As SignInOrSignUp
Dim eStatus As SignInStatus
Dim oAction As ActionResult
oViewModel = New SignInOrSignUp With {.SignIn = Model}
If Me.ModelState.IsValid Then
eStatus = Await Me.SignInManager.PasswordSignInAsync(Model.Username, Model.Password, isPersistent:=False, shouldLockout:=False)
If eStatus = SignInStatus.Success Then
oAction = Me.Redirect("www.domain.com")
Else
Me.ModelState.AddModelError("", "Invalid signin attempt.")
oAction = Me.View("Index", oViewModel)
End If
Else
oAction = Me.View("Index", oViewModel)
End If
Return oAction
End Function
End Class
End Namespace
ViewModel
Imports System.ComponentModel.DataAnnotations
Namespace ViewModels.Home
Public Class SignIn
<Required(ErrorMessage:="User Name is required.")>
<Display(Name:="User Name")>
Public Property Username As String
<Required>
Public Property Password As String
<Display(Name:="Remember Me")>
Public Property RememberMe As Boolean
End Class
End Namespace
View
@ModelType ViewModels.Home.SignInOrSignUp
@Code
Layout = Nothing
End Code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Website</title>
@Scripts.Render("~/scripts/bootstrap")
@Scripts.Render("~/scripts/jquery")
@Styles.Render("~/styles/bootstrap")
@Styles.Render("~/styles/home")
</head>
<body>
<div class="row">
<div class="wrapper">
<div class="section">
<h5>Sign in here</h5>
<div class="box">
<div class="titlebar">Sign In</div>
@Using (Html.BeginForm("signin", "home", FormMethod.Post, New With {.id = "SignInForm"}))
@Html.AntiForgeryToken
@Html.EditorFor(Function(M) Model.SignIn)
@<div class="button">
<input type="submit" value="Sign In" Class="button" />
</div>
@<div class="anchor">
<a href="/">Forgot your password?</a>
</div>
End Using
</div>
</div>
<div class="section">
<h5>Or enter your City Code here</h5>
<div class="box">
<div class="titlebar">Sign Up</div>
@Using (Html.BeginForm("signup", "home", FormMethod.Post, New With {.id = "SignUpForm"}))
@Html.AntiForgeryToken
@Html.EditorFor(Function(M) Model.SignUp)
@<div class="button">
<input type="submit" value="Continue" Class="button" />
</div>
End Using
</div>
</div>
</div>
</div>
</body>
</html>
--EDIT--
I've been able to get unblocked in the meantime with a clunky workaround in my view:
@Code
If IsPost AndAlso Request.Path = "/home/signin" AndAlso Model.SignIn.Username.IsNothing Then
@Html.ValidationMessageFor(Function(Model) Model.SignIn.Username, "Username is required", New With {.class = "text-danger"})
End If
End Code
But I'd still like to use the in-built validation mechanism if it's possible to do so with multiple forms in the same view.
Upvotes: 0
Views: 366
Reputation: 26075
There are 2 things you can try:
1. Put validation summary on the page:
With this option you are not changing anything in your controller but you are putting error summary on your page. You will not get highlighted field but you will still be able to display all errors, like:
<body>
<div class="row">
<div class="wrapper">
@Html.ValidationSummary(false)
...
</div>
</div>
</body>
Not an elegant solution!
2. In controller use SignInOrSignUp
as an input but from UI provide either SignIn
or SignUp
model:
With this approach you are not changing your vbhtml/cshtml file it will remain as it is only thing you are changing is type of argument on controller methods:
Namespace Controllers.Home
Partial Public Class HomeController
...
<HttpPost>
<ValidateAntiForgeryToken>
Public Async Function SignIn(Model As SignInOrSignUp) As Task(Of ActionResult)
...
End Function
<HttpPost>
Public Async Function SignUp(Model As SignInOrSignUp) As Task(Of ActionResult)
...
End Function
End Class
End Namespace
unless there is some complex validation involved, this approach will work like a charm!
Upvotes: 1