Search code examples
c#asp.net.netasp.net-coreasp.net-web-api

ASP.NET Core Global Exception Handling Middleware Not Working In NET 8?


I'm trying to implement global exception handling in my ASP.NET Core application. I followed a tutorial, but it doesn't seem to work. I suspect the issue might be with the middleware order. I've tried placing the middleware in different orders, but it's still not working.

Here's my exception handler:

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using System.Net;
using System.Text.Json;

namespace HospitalAPI.Features.GlobalException
{
    public class AppExceptionHandler : IExceptionHandler
    {
        public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
        {
            var statusCode = (int)HttpStatusCode.InternalServerError;
            httpContext.Response.StatusCode = statusCode;
            var problem = new ProblemDetails
            {
                Status = statusCode,
                Title = "Server Error",
                Detail = exception.Message,
            };
            var json = JsonSerializer.Serialize(problem);
            httpContext.Response.ContentType = "application/json";
            await httpContext.Response.WriteAsync(json, cancellationToken);
            return true;
        }
    }
}

this is Program.cs:

using HospitalAPI.Features.JsonConverter;
using HospitalAPI.Features.Mail.Repository;
using HospitalAPI.Features.Mail.Service;
using HospitalAPI.Features.Mail;
using HospitalAPI.Features.Redis.Repository;
using HospitalAPI.Features.Redis.Service;
using HospitalAPI.Models.DbContextModel;
using HospitalAPI.Repositories;
using HospitalAPI.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using StackExchange.Redis;
using Swashbuckle.AspNetCore.Filters;
using System.Text;
using System.Text.Json.Serialization;
using HospitalAPI.Features.Cookies.IServices;
using HospitalAPI.Features.Cookies.Repository;
using HospitalAPI.Features.Utils.IServices;
using HospitalAPI.Features.Utils.Repository;
using HospitalAPI.Middlewares;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authentication.Cookies;
using HospitalAPI.Features.GlobalException;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddDbContext<MyDbContext>(x => x.UseSqlServer(
    builder.Configuration.GetConnectionString("SQLConnection")
));

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddLogging();
builder.Services.AddSingleton<IConnectionMultiplexer>(x => ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("RedisConnection")!));
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Hospital System API", Version = "v1" });

    c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey,
        Description = "API Key Authentication"
    });

    c.OperationFilter<SecurityRequirementsOperationFilter>();

    c.MapType<DateOnly>(() => new OpenApiSchema
    {
        Type = "string",
        Format = "date",
        Example = new OpenApiString(DateOnly.FromDateTime(DateTime.Now).ToString("yyyy-MM-dd"))
    });
    c.MapType<DateTime>(() => new OpenApiSchema
    {
        Type = "string",
        Format = "datetime",
        Example = new OpenApiString(DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ"))
    });
});

builder.Services.AddControllers()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.Converters.Add(new DateOnlyJsonConverter());
            options.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
            options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
            options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        });

builder.Services.AddScoped<MyDbContext>();
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddSingleton<IResponseStatus, ResponseStatusRepository>();

builder.Services.AddTransient<IDoctorService, DoctorRepository>();
builder.Services.AddTransient<IDepartmentService, DepartmentRepository>();
builder.Services.AddTransient<IPatientService, PatientRepository>();
builder.Services.AddTransient<IAppointmentService, AppointmentRepository>();
builder.Services.AddTransient<IBillingService, BillingRepository>();
builder.Services.AddTransient<IPrescriptionService, PrescriptionRepository>();
builder.Services.AddTransient<IUserService, UserRepository>();

builder.Services.AddTransient<IAuthService, AuthRepository>();
builder.Services.AddTransient<IMailService, MailRepository>();
builder.Services.AddTransient<IRedisService, RedisRepository>();
builder.Services.AddTransient<ICookieService, CookieRepository>();
builder.Services.AddTransient<IUtilitiesService, UtilitiesRepository>();
builder.Services.AddTransient<ITokenService, TokenRepository>();

builder.Services.AddTransient<VerifyTokenMiddleware>();

builder.Services.Configure<MailSettings>(builder.Configuration.GetSection("MailSettings"));

builder.Services.AddAutoMapper(typeof(Program).Assembly);

builder.Services.AddAuthentication(x =>
{
    x.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
    .AddJwtBearer(option =>
    {
        option.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = TimeSpan.Zero,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes((builder.Configuration["Jwt:KeyAccessToken"]!)))
        };
    })
    .AddGoogle(x =>
    {
        var config = builder.Configuration.GetSection("Auth:Google");
        x.ClientId = config["ClientId"]!;
        x.ClientSecret = config["ClientSecret"]!;
    }).AddCookie();
;

builder.Services.AddAuthorization();
builder.Services.AddExceptionHandler<AppExceptionHandler>();
builder.Services.AddProblemDetails();
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Hospital System APIv1");
    });
}

app.UseWhen(context => !context.Request.Path.StartsWithSegments("/api/auth"), appBuilder =>
{
    appBuilder.UseMiddleware<VerifyTokenMiddleware>();
});

app.UseCors
    (x => x 
        .AllowAnyOrigin()
        .AllowAnyHeader()
        .AllowAnyMethod()
    );

app.UseHttpsRedirection();

app.UseExceptionHandler();

app.UseRouting();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

I've added the AppExceptionHandler to the services and configured the UseExceptionHandler middleware, but exceptions are not being handled as expected. What am I missing or doing wrong? Any help would be appreciated.


Solution

  • During the development phase, IDEs (Integrated Development Environments) such as Visual Studio will interrupt the program when an exception is thrown, allowing you to inspect the details of the exception and the stack trace. This behavior is intended to help developers debug and fix issues.

    The break may be like:

    the break image

    So, you need to continue running the program to test the exception handler by clicking the button as below:

    button1

    or

    button2

    Then you can catch the exception:

    the result image

    After the program is released, exceptions will be captured and handled by the custom exception handler without interrupting the program's execution at the point where the exception is thrown.