Search code examples
.nethttp-error

When using UseExceptionHandler for custom error pages, error page shows up blank


I am trying to implement custom error pages on my site built with .Net 6 and have tried implementing with the following code:

Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();
app.UseExceptionHandler("/Shared/Error");
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

SharedController.cs

public class SharedController : BaseController
{
    private readonly string _cookieKey = "session-info";
    private readonly ILogger<SharedController> _logger;

    public SharedController(ILogger<SharedController> logger, IConfiguration configuration) : base(configuration)
    {
        _logger = logger;
    }

    public IActionResult Error()
    {
        return View(new ErrorViewModel());
    }
}

BaseController.cs

public class BaseController : Controller
{
    private readonly string _cookieKey = "session-info";
    public IConfiguration _config;

    public BaseController(IConfiguration configuration)
    {
        _config = configuration;
    }

    public async Task CheckLogin(BaseModel? model)
    {
        if(model == null)
        {
            return;
        }

        model.UserName = "";
        model.UserId = -1;
        model.IsAdmin = false;
        model.LoggedIn = false;

        var baseAddress = new Uri(_config["Config:BaseAddress"]);
        var cookieContainer = new CookieContainer();
        using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
        {
            var siteCookies = HttpContext.Request.Cookies.Where(cookie => cookie.Key == _cookieKey); ;

            if (!siteCookies.Any())
            {
                return;
            }

            var siteCookie = siteCookies.First();
            cookieContainer.Add(baseAddress, new Cookie(siteCookie.Key, siteCookie.Value));
            var client = new HttpClient(handler);
            var response = await client.GetAsync(baseAddress.AbsoluteUri + "api/authenticate");

            if (response != null && response.IsSuccessStatusCode)
            {
                var userObj = await response.Content.ReadFromJsonAsync<UserLookup>();

                if (userObj.Key != null && userObj.Key != "")
                {
                    model.UserName = userObj.Key;
                    model.UserId = userObj.Id;
                    model.LoggedIn = true;
                }
            }

            response = await client.GetAsync(baseAddress.AbsoluteUri + "api/isAdmin");

            if (response != null && response.IsSuccessStatusCode)
            {
                model.IsAdmin = await response.Content.ReadFromJsonAsync<bool>();
            }
        }
    }

    private class Login
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }

    private class UserLookup
    {
        public string Key { get; set; }
        public int Id { get; set; }
    }
}

Error.cshtml

@model ErrorViewModel
@{
    ViewData["Title"] = "Error";
    Layout = "_Layout";
}

@if (true)
{
    <h1>Error.</h1>
    <h2>An error occurred while processing your request.</h2>
}

Note that I moved the setup outside of the IsDevelopment block just for testing. Though, I get the same results whether testing on Development or Release configurations. When I hit an exception, it gets caught by the middleware, the Error controller method runs, and I can even see, via breakpoints, that the View is being run. However, I just get a completely blank page in the browser. Also, checking the network tab, I am still seeing a 500 error returned. I should also note that I am only able to trigger this via an exception being thrown. If I go to a nonexistent page and get a 404, I just get a blank page without any handlers being hit.

Since the flow is moving through the Error method and the cshtml code, I would think that everything is hooked up fine, but I get a completely blank page. Not even the layout loads. In searching, I haven't seen this particular issue pop up, so I haven't tried much of anything other than changing my config to Release.


Solution

  • I figured this out while debugging with Just My Code turned off. My ErrorViewModel class was inheriting PageModel. At some point in the middleware, it was expecting it to be of type BaseModel. Since the types did not match it was considering the exception not handled and calling Rethrow, which rethrows the error, causing the 500 and blank page I was seeing. I'm not sure of all the ins and outs of WHY this is the expectation, but it is.

    Long story short, I changed ErrorViewModel to inherit BaseModel instead of PageModel, and I get my custom error page now, as intended.