Search code examples
asp.net-corerazor

How to handle error in controller in a .cshtml (without redirecting to error page)?


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?


Solution

  • 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);
        }
    }