1
votes

I'm building an Asp.Net Core 2.2 Razor Pages Application. I'm writing an global exception handling to avoid try catch blocks in many places. I followed the article - Exception Handling Reference

Here is the Demo Solution Link

Here is my code,

In Startup.cs

if(env.IsDevelopment())
{
    //app.UseDeveloperExceptionPage(new DeveloperExceptionPageOptions {
    //    SourceCodeLineCount = 2
    //});
    //app.UseDatabaseErrorPage();
    app.UseExceptionHandler("/Error");
    app.UseStatusCodePagesWithReExecute("/Error/{0}");
    app.UseExceptionMiddleware();
} else {...}

In ExceptionMiddleware.cs

public ExceptionMiddleware(RequestDelegate next,IMailService emailSender)
{
    _next = next;
    _emailSender = emailSender;
}

public async Task InvokeAsync(HttpContext context)
{
    try
    {
        await _next(context);
    } catch(Exception ex)
    {
        EmailException(context,ex);
    }
}

private async void EmailException(HttpContext context,Exception ex)
{
    var uaString = context.Request.Headers["User-Agent"].ToString();
    var ipAnonymizedString = context.Connection.RemoteIpAddress.AnonymizeIP();
    var userId = "Unknown";
    var profileId = "Unknown";
    if(context.User.Identity.IsAuthenticated)
    {
        userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
        profileId = context.User.GetUserClaim(ClaimsKey.ProfileId);
    }

    var sb = new StringBuilder($"An error has occurred on {context.Request.Host}. \r\n \r\n");
    sb.Append($"Path = {context.Request.Path} \r\n \r\n");
    sb.Append($"Error Message = {ex.Message} \r\n");
    sb.Append($"Error Source = {ex.Source} - {profileId} \r\n");

    if(ex.InnerException != null)
    {
        sb.Append($"Inner Exception = {ex.InnerException.ToString()} \r\n");
    } else
    {
        sb.Append("Inner Exception = null \r\n");
    }

    sb.Append($"Error StackTrace = {ex.StackTrace} \r\n");

    await _emailSender.SendMasterEmailAsync($"Error on {context.Request.Host}.",sb.ToString(),uaString,ipAnonymizedString,userId);

    throw ex;
}

Now, I'm creating an exception in one of my page get request as shown below,

public void OnGet()
{
    throw new Exception();
}

The middleware catches the exception and it's throwing that after sending email. Then Visual Studio says application is in break mode and visual studio stops. But my Error.cshtml.cs is not capturing the exception before that application breaks and stops. Any Idea of where I'm going wrong?

1

1 Answers

3
votes

In his async guidance GitHub repository, David Fowler states that:

Use of async void in ASP.NET Core applications is ALWAYS bad. Avoid it, never do it. Typically, it's used when developers are trying to implement fire and forget patterns triggered by a controller action. Async void methods will crash the process if an exception is thrown.

For the error you're describing, that last line is crucial - your application is throwing an exception, which is crashing the process.

For EmailException, you should switch from using async void to using async Task, like this:

public ExceptionMiddleware(RequestDelegate next,IMailService emailSender)
{
    ...
}

public async Task InvokeAsync(HttpContext context)
{
    try
    {
        await _next(context);
    }
    catch(Exception ex)
    {
        await EmailException(context, ex); // Await.
    }
}

private async Task EmailException(HttpContext context, Exception ex) // Return a Task.
{
    ...

    throw ex;
}

If the reason you're trying to use async void is so that you don't have to wait for the email to be sent before returning a response to the user, you might want to look at using a Background Task for that.