1

I have .NET Core 2.1 WebAPI project. I create base DbContext which name is DataContext.cs. I want to start DataContext by IAuditHelper. When project start, I can set AuditHelper from my Startup.cs.

But, after project start and execute SaveChangesAsync method, it's being null. How, can I achieve getting AuditHelper from My DataContext? (I know, if I inject IAuditHelper in my DataContext constructor, then I can take. But, in that situation, Datacontext wants IAuditHelper in everywhere.)

DataContext.cs

 public class DataContext : DbContext,IDataContext
 {
     public IAuditHelper AuditHelper { get; set; }

     public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
     {
         auditHelper.LogMyChangesToDatabase()
         return (await base.SaveChangesAsync(true, cancellationToken));
     }
 }

IDataContext.cs

public interface IDataContext : IDisposable
{
    IAuditHelper AuditHelper{ get; set; }
    Task<int> SaveChangesAsync(CancellationToken cancellationToken);
    Task<int> SaveChangesWithoutAuditAsync(CancellationToken cancellationToken);
}

UniversityDbContext.cs

 public class UniversityDbContext: DataContext
 {      
    override protected void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
      optionsBuilder.UseSqlServer("server=.; database=.; user id=.; 
            password=.;");
    }

    public UniversityDbContext() : base()
    {
    }
 }

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IAuditHelper, AuditHelper>();
    services.AddScoped<IDataContext, DataContext>();
    services.AddScoped<DataContext, UniversityDbContext>();
    services.AddDbContext<UniversityDbContext>();
       

    var sp = services.BuildServiceProvider();
    var dataContext = sp.GetService<IDataContext>();
    dataContext.AuditHelper = sp.GetService<IAuditHelper>();
}
TylerH
  • 20,799
  • 66
  • 75
  • 101
realist
  • 2,155
  • 12
  • 38
  • 77
  • 1
    Explicitly inject that dependency into the constructor of the context. – Nkosi Feb 21 '19 at 13:21
  • How can I do this @Nkosi . – realist Feb 21 '19 at 13:22
  • 2
    While you can just inject this, I really think it's a bad idea. Why would you want to mix `HttpContextAccessor` with your `DbContext`? – DavidG Feb 21 '19 at 13:38
  • Because, I want to logging database operation by CurrentUserId. So, I need JWT in my DbContext. @DavidG – realist Feb 21 '19 at 13:40
  • Then I would advise you to inject a generic logging service, not a `HttpContextAccessor`. – DavidG Feb 21 '19 at 13:41
  • I tried @DavidG. I create a `AuditHelper` and `IAuditHelper`. But, there was same problem in that. Because, I can't also access AuditHelper from my `DataContext`. @DavidG – realist Feb 21 '19 at 13:44
  • AddHttpContextAccessor() injects ContextAccessor, you don't need services.AddSingleton(); ASP.NET Core dependency injection doesn't support property injection, you can use other containers instead, e.g. [Unity](https://github.com/unitycontainer/unity) or [Autofac](https://autofac.readthedocs.io/en/latest/getting-started/index.html). What are you trying to achieve? It doesn't seem to be right from a design point of view. – Kosta_Arnorsky Feb 21 '19 at 13:44
  • I'm only want to achieve logging my Database operations by CurrentUserId @Kosta_Arnorsky . So, I need Jwt in my DataContext. If, are there any way for this, I can change my design. – realist Feb 21 '19 at 13:47
  • I clarified my question changing `HttpContextAccessor` by `AuditHelper` @Kosta_Arnorsky @DavidG – realist Feb 21 '19 at 13:56

1 Answers1

2

ASP.NET Core dependency injection doesn't support property injection, you can instead inject dependencies into the constructor like it's shown below. Another option is to use containers that support property injection, like Unity or Autofac.

public class DataContext : DbContext, IDataContext
{
    public DataContext(IAuditHelper auditHelper, DbContextOptions options)
        : base(options)
    {
        AuditHelper = auditHelper;
    }

    public IAuditHelper AuditHelper { get; private set; }

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        AuditHelper.LogMyChangesToDatabase();
        return base.SaveChangesAsync(true, cancellationToken);
    }

    ...
}

public interface IDataContext : IDisposable
{
    IAuditHelper AuditHelper { get; }

    Task<int> SaveChangesAsync(CancellationToken cancellationToken);

    ...
}

public class UniversityDbContext : DataContext
{
    public UniversityDbContext(IAuditHelper auditHelper, DbContextOptions options)
        : base(auditHelper, options)
    {
    }
}

I not quite understand why you need AuditHelper in IDataContext interface, I would save it in a private filed in DataContext and wouldn't expose it.

AuditHelper class:

public class AuditHelper : IAuditHelper
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public AuditHelper(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public void LogMyChangesToDatabase()
    {
        //_httpContextAccessor.HttpContext.
    }
}

In the Startup class:

public class Startup
{
    ...

    public void ConfigureServices(IServiceCollection services)
    {
        ...

        services.AddHttpContextAccessor();
        services.AddDbContext<UniversityDbContext>(options
            => options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TestUniversity;Trusted_Connection=True;MultipleActiveResultSets=true"));
        services.AddScoped<IAuditHelper, AuditHelper>();

        ...
    }

    ...
}

You can find difference between scopes at the link.

A controller:

public class SomeController : ControllerBase
{
    private readonly UniversityDbContext _context;

    public SomeController(UniversityDbContext context)
    {
        _context = context;
    }

    [HttpPost]
    public async Task Post([FromBody] string value)
    {
        ...
        await _context.SaveChangesAsync();
    }
}

I also recommend to follow TAP and change LogMyChangesToDatabase:

    public async Task LogMyChangesToDatabase()
    {
        //_httpContextAccessor.HttpContext.
        //await 
    }

SaveChangesAsync will be accordingly:

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        await AuditHelper.LogMyChangesToDatabase();
        return await base.SaveChangesAsync(true, cancellationToken);
    }

And the connection string of cause should be in the config, see tutorial.

Kosta_Arnorsky
  • 363
  • 3
  • 7
  • Thanks @Kosta_Arnorsky. Firstly, I created my structure like you said. But, I took `'MyDbContext' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TContext' in the generic type or method 'UnitOfWork'` in my `MyRepos.cs` class. So, I want to create my DataContext constructor without parameter. You can check https://stackoverflow.com/questions/54741937/inherit-from-generic-class my question. – realist Feb 21 '19 at 15:45
  • As it's said in the answer to your question, you can use an abstract factory/delegate to create a context. Could you please clarify, why do you need UnitOfWork? Are you trying to create a processing pipeline? – Kosta_Arnorsky Feb 21 '19 at 16:03
  • Thanks @Kosta_Arnorsky. I think there is no other way of doing this by ASP.NET Core dependency injection. So, I will use DbContextFactory. – realist Feb 25 '19 at 07:10