Home > OS >  How to pass event handler service method after DbContext registration is done? If event handler serv
How to pass event handler service method after DbContext registration is done? If event handler serv

Time:09-15

I want Register my DbContext service and after registration is successful, want pass to that created instance (dbcontext.SavingChanges) an event handler (AuditingSupport.) . My problem is service which hold that event handler (AuditingSupport.UpdateAuditableEntity) is using DbContext through DI. Try to solve that with autofac OnActivated() method, but throwing exception Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'DIT.Persistence.EF.Repositories.Domain.AppUserService'.' when on runtime try to get DbContext Service.

Example of configuration and registration.

AuditingSupport.cs implementation :

public class AuditingSupport : IAuditingSupport
{
        public AuditingSupport(IAppUserService userService)
        {
            this.userService = userService;
        }
        
        // EventHandler method which want pass to dbContext.SavingChanges event
        public  void UpdateAuditableEntity(object sender, SavingChangesEventArgs e)
        {
        ...
        }
}

AppUserService.cs implementation:

public class AppUserService : IAppUserService
{
    // Consume DbContex through DI
    public AppUserService(DbContext context)
    {
       ...
    } 
    ...   
}

Registre class for DcContext DcContextModule.cs :

public class DcContextModule : Module
{
    ...
        public DcContextModule (IConfiguration configuration, IServiceCollection services)
        {
            this.configuration = configuration;
            this.services = services;
        }

        protected override void Load(ContainerBuilder builder)
        {
            // In this OnActivated() autofac method I try to get Auditing Service and pass event hanlder method
            builder.RegisterType<EUDbContext>()
                   .As<DbContext>()
                   .WithParameter("options", this.GetOptions())
                   .InstancePerLifetimeScope()
                   .OnActivated(x => x.Instance.SavingChanges  = services.BuildServiceProvider().GetRequiredService<IAuditingSupport>().UpdateAuditableEntity);
        }    
}

Program.cs service Registration:

//Register Service
...
builder.Services.AddTransient<IAuditingSupport, AuditingSupport>();
builder.Services.AddSingleton<IAppUserService, AppUserService>();
builder.Host.ConfigureContainer<ContainerBuilder>(builder0 => builder0.RegisterModule(new DbContextModule(configuration, builder.Services)));
...
// Call Service

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        // Exception throw
        var audit = services.GetRequiredService<IAuditingSupport>();
        var context = services.GetRequiredService<DbContext>();
    }
}

I Would like to know how to solve this problem and is this good approach. Can I solve it without autofac and how ?

CodePudding user response:

There's quite a lot to unravel here but hopefully I can at least point you in the right direction.

What you appear to have is sort of property or method injection. Yes, it's an event handler, but the concept is the same - you need a resolved service so you can attach it to the event handler.

There are some examples on that documentation page, but for your purposes, you can do this in one of two ways:

// You can use a lambda expression registration...
builder.Register(ctx =>
  {
    var options = this.GetOptions();
    var context = new EUDbContext(options);
    var auditSupport = ctx.Resolve<IAuditingSupport>();
    context.SavingChanges  = auditSupport.UpdateAuditableEntity;
    return context;
  })
  .As<DbContext>()
  .InstancePerLifetimeScope();

// Or you can use the event handler like you were doing...
builder.RegisterType<EUDbContext>()
       .As<DbContext>()
       .WithParameter("options", this.GetOptions())
       .InstancePerLifetimeScope()
       .OnActivated(x => x.Instance.SavingChanges  = e.Context.Resolve<IAuditingSupport>().UpdateAuditableEntity);

You can't mix and match Autofac syntax with Microsoft.Extensions.DependencyInjection syntax. When you use Autofac as the backing container, what happens under the covers is that the Microsoft-format registrations get converted to Autofac registrations. Trying to use IServiceProvider in an Autofac event handler, or trying to do Microsoft format registrations inside an Autofac module is not going to yield success. Generally speaking, Autofac event handlers and registration lambdas will give you the appropriate things you'll need to do what you need to do without having to try to mix and match.

That said, don't get confused - this doesn't mean you have to try to convert all of the services.AddLogging() or other MS registration things into Autofac format. That's what the Autofac.Extensions.DependencyInjection library does for you. I'm just saying you can't use them both literally together interchangeably.

  • Related