ASP.NET Core Web API, inject database context into exception handling
Problem Description:
I’m trying to write custom exception handling that would write error details into the database.
Right now (in the code I inherited) the DB context is injected into static class with extension method:
Startup.cs:
ExceptionMiddleware.ExceptionMiddlewareConstructor(app.ApplicationServices.GetService<IErrorLoggerService>());
ExceptionMiddleware.cs:
public static class ExceptionMiddleware
{
private static IErrorLoggerService _service;
public static void ExceptionMiddlewareConstructor(IErrorLoggerService service)
{
_service = service;
}
public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILog logger)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
if (context != null)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var requestString = "Query: " + context.Request.QueryString.Value;
if (context.Request.Method != "GET")
{
context.Request.Body.Position = 0;
using StreamReader reader = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true);
requestString += "Body" + await reader.ReadToEndAsync();
}
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
var claims = context.User.Claims.ToList();
//Filter specific claim
var email = claims.FirstOrDefault(x =>
x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
StringComparison.OrdinalIgnoreCase))?.Value;
if (contextFeature.Error.StackTrace != null)
{
var error = new ERROR
{
//assinging property values for DB entity
};
await _service.CreateAsync(error);
}
}
}
});
});
}
}
ErrorLoggerService is a service that handles db write for ERROR entities, and has DbContext injected into constructor.
Obviously current code is not thread-safe because of the captured dependency to DbContext, but I’m not sure how to inject DbContext into exception handler properly
Solution – 1
If you registed your dbcontext as a scoped service by default,
// If you didn't pass the parameter of servicelife time into the method,the lifetime would be scoped by default
builder.Services.AddDbContext<ZipTestContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SomeDbContext") ?? throw new InvalidOperationException("Connection string 'SomeDbContext' not found.")),ServiceLifetime.Scoped);
you could get the dbcontext from container as below:
var dbcontext = context.RequestServices.CreateScope().ServiceProvider.GetService<SomeDbContext>();
And the case related may help:Why cannot resolve DbContext in constructor of custom middleware?
Tried as below:
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
// using static System.Net.Mime.MediaTypeNames;
context.Response.ContentType = Text.Plain;
//var dbcontext = context.RequestServices.GetService<ZipTestContext>();
var dbcontext = context.RequestServices.CreateScope().ServiceProvider.GetService<ZipTestContext>();
var somemodel = new SomeModel() { Description = "Description", Name = "SomeName" };
dbcontext?.Add(somemodel);
dbcontext?.SaveChangesAsync();
await context.Response.WriteAsync("An exception was thrown.");
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(" The file was not found.");
}
if (exceptionHandlerPathFeature?.Path == "/")
{
await context.Response.WriteAsync(" Page: Home.");
}
});
});
You could see the different performance between
var dbcontext = context.RequestServices.GetService<ZipTestContext>();
var dbcontext =
And
context.RequestServices.CreateScope().ServiceProvider.GetService<ZipTestContext>();
Result1:
Result2: