I have a simple sign in view and controller that throws an error if the login fails. The sign in view should show conditional error message to user instead redirecting to some error page.
SignIn.cshtml:
@model AuthServer.ViewModels.SignInViewModel
<form autocomplete="off" asp-route="SignIn">
<input type="hidden" asp-for="ReturnUrl"/>
<div class="card">
<input type="text" class="form-control" asp-for="Username" autofocus>
<input type="password" class="form-control" asp-for="Password">
</div>
<p>
<button type="submit"">Sign in</button>
</p>
<!-- Conditionally shown alert box (e.g. span) that says :
Wrong email or password -->
</form>
SignInViewModel
public class SignInViewModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
public string? ReturnUrl { get; set; }
}
AuthController
public class AuthController : Controller
{
[Route("signin")]
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SignIn(SignInViewModel model)
{
ViewData["ReturnUrl"] = model.ReturnUrl;
if (ModelState.IsValid)
{
if (await _userManager.CheckNameAndPasswordAsync(user) == false)
throw new InvalidOperationException("Invalid username or password");
As mentioned, I know how to redirect to another page in controller, but have no idea how to show some message in SignIn view if sign in in the controller fails.
Can it be done e.g. by not throwing error and setting error in a new property in SignInViewModel which is then conditionally shown in SigniIn view?
Now when an error is encountered, the reason for the redirect is in this line of code.
throw new InvalidOperationException("Invalid username or password");
When you throw an exception, it will be captured by UseDeveloperExceptionPage
or UseExceptionHandler
. Like below:
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
// for development
app.UseMigrationsEndPoint();
}
else
{
// for production
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
So we can following below steps to fix it.
SignInViewModel.cs
public class SignInViewModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
public string? ReturnUrl { get; set; }
// add error message
public string? ErrorMessage { get; set; }
}
SignIn.cshtml
@model AuthServer.ViewModels.SignInViewModel
<form autocomplete="off" asp-route="SignIn">
<input type="hidden" asp-for="ReturnUrl"/>
<div class="card">
<input type="text" class="form-control" asp-for="Username" autofocus>
<input type="password" class="form-control" asp-for="Password">
</div>
<p>
<button type="submit">Sign in</button>
</p>
@if (Model != null && !string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="alert alert-danger">
@Model.ErrorMessage
</div>
}
</form>
AuthController.cs
public class AuthController : Controller
{
[Route("signin")]
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SignIn(SignInViewModel model)
{
ViewData["ReturnUrl"] = model.ReturnUrl;
if (ModelState.IsValid)
{
if (await _userManager.CheckNameAndPasswordAsync(user) == false)
{
model.ErrorMessage = "Invalid username or password"; // error message
return View(model);
}
// if success
}
return View(model);
}
}