Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access HttpContextAccessor from startup.cs in .net Core WebApi

I'm logging exceptions to database in asp.net core. MyDbContext take HttpContextAccessor parameter.So, I'm sending HttpContextAccessor to MyDbContext.cs for access my JWT. But, I can't access my HttpContextAccessor from Startup.cs. How can I achieve this?

Startup.cs

   public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpContextAccessor();
        services.AddMvc();
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddDbContext<MyDbContext>();
        services.AddTransient<IUnitOfWork, UnitOfWork>();
    }


    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
         app.UseExceptionHandler(builder => builder.Run(async context =>
         {
             var error = context.Features.Get<IExceptionHandlerFeature>();

             context.Response.AddApplicationError(error,???????);//I want access HttpContextAccessor
             await context.Response.WriteAsync(error.Error.Message);
         }));

        app.UseHttpsRedirection();
        app.UseMvc();
    }

ExceptionHelper.cs

 public static class ExceptionHelper
    {
        public static async Task AddApplicationError(this HttpResponse response, IExceptionHandlerFeature error, IHttpContextAccessor httpContextAccessor)
        {
           Log log = new Log();
           log.Message = error.Error.Message;          

           MyDbContext context = new MyDbContext(null, httpContextAccessor);
           UnitOfWork uow = new UnitOfWork(context);
           uow.LogRepo.AddOrUpdate(log);
           await uow.CompleteAsync(false);               
        }
    }

MyDbContext

public class MyDbContext : DbContext
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
        : base(GetOptions())
    {
        _httpContextAccessor = httpContextAccessor;
    }

    private static DbContextOptions GetOptions()
    {
        return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), "server=asd; database=; user id=asd; password=1234").Options;
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        var token = _httpContextAccessor.HttpContext.Request.Headers["Authorization"];
        var audits = AuditHelper.AddAuditLog(base.ChangeTracker, token);
        return (await base.SaveChangesAsync(true, cancellationToken));
    }
}
like image 925
realist Avatar asked Dec 18 '22 20:12

realist


1 Answers

You can inject whatever you need into the Configure method. You have already added it to the service collection with this line:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

So all you need to do is add it to the list of arguments on the method like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IHttpContextAccessor accessor)
{
    // make use of it here
}

As an aside: I would also point out that it's a bit of a code smell that you are manually creating an instance of your DbContext inside your static helper class when you are using dependency injection.

Update in response to comment

In order to tidy things up a bit I would start by changing your startup to configure you DbContext something like this:

public class Startup
{
    private readonly IConfiguration configuration;

    public Startup(IConfiguration configuration)
    {
        this.configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        // register other things here...

        services.AddDbContext<DataContext>(o => o.UseSqlServer(
            config.GetConnectionString("MyConnectionString") // from appsettings.json
        ));
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // set up app here...
    }
}

You can then remove the .GetOptions() method from MyDbContext, and change the constructor to:

public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
    : base(options)
{
    _httpContextAccessor = httpContextAccessor;
}

Then you inject an instance of MyDbContext into whatever class needs access to it. The problem is that (to my knowledge) DI does not work well with static classes/methods, and you are using an extension method on the HttpResponse to log your error.

In my opinion it would be better to create a class that is responsible for logging the error with a dependency on your MyDbContext and have that injected into the Configure method:

public class ErrorLogger
{
    private MyDataContext db;

    public ErrorLogger(MyDataContext db) => this.db = db;

    public void LogError(IExceptionHandlerFeature error)
    {
        Log log = new Log();
        log.Message = error.Error.Message; 

        UnitOfWork uow = new UnitOfWork(this.db);
        uow.LogRepo.AddOrUpdate(log);
        await uow.CompleteAsync(false);
    }
}

Register it with the DI container as you have with other things, then inject it into Configure instead of the HTTP accessor:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ErrorLogger logger)
{
     app.UseExceptionHandler(builder => builder.Run(async context =>
     {
         var error = context.Features.Get<IExceptionHandlerFeature>();
         logger.LogError(error);
         await context.Response.WriteAsync(error.Error.Message);
     }));
}

I have not tested this, and I am not familiar with .UseExceptionHandler(...) as I use application insights to log exceptions etc (take a look at it if you've not seen it). One thing to be aware of is the scope of your dependencies; your DbContext will be Scoped by default (and I think you should leave it that way), which means you cannot inject it into Singleton objects.

like image 128
Ben Avatar answered Jan 25 '23 19:01

Ben