realist
realist

Reputation: 2385

Access services from DbContext in .NET Core 2.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>();
}

Upvotes: 1

Views: 2113

Answers (1)

Kosta_Arnorsky
Kosta_Arnorsky

Reputation: 421

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.

Upvotes: 2

Related Questions