abp icon indicating copy to clipboard operation
abp copied to clipboard

Dependency injection in custom ABP app fail

Open iddelacruz opened this issue 1 year ago • 4 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Description

Hello everyone, I am writing because I am having a problem with dependency injection but not only with this version 8 of ABP, I have had the same incident with version 7 and 6.

Context:

  • When you use an abp app generated by the cli, dependency injection works correctly. If you implement the ITransient, IScoped, ISingleton interface to a service, the system correctly registers the service. On this side, dependency injection has never failed to me.

  • In my case I have not created the app with the cli because I need few abp libraries (aspnetcore, serilog, autofac, ddd.domain, etc). I have created a web api to use an abp backgroungd worker. Here is the code:

[DependsOn(
    typeof(AbpAutofacModule),
    typeof(AbpCachingStackExchangeRedisModule),
    typeof(AbpDistributedLockingModule),
    typeof(AbpAspNetCoreSerilogModule),
    typeof(AbpSwashbuckleModule), 
    typeof(AbpAspNetCoreModule),
    typeof(AbpBackgroundWorkersModule),
    typeof(KernelModule)
)]
public class EyeModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.Add_Marten();
    }
    
    public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext ctx)
    {
        await ctx.ServiceProvider.Use_Collector();
        await ctx.AddBackgroundWorkerAsync<EyeBackgroundWorker>();
    }
}

And I have created another module to have domain services, aggregates, events, commands, handlers, etc. Here is the module config.

[DependsOn(typeof(AbpDddDomainModule))]
[DependsOn(typeof(AbpAutofacModule))]
public class KernelModule : AbpModule
{

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddScoped<Telegram>();
        context.Services.AddTransient<ISentimentProvider, SentimentProvider>();
        context.Services.AddSingleton<IChatGroupCollector, ChatGroupCollector>();
        context.Services.AddTransient<IMessageProvider, MessageProvider>();
        context.Services.AddTransient<ISupervisedChatGroupProvider, SupervisedChatGroupProvider>();

		//local event handlers
        context.Services.AddTransient<ILocalEventHandler<CreateMessageCommand>, CreateOrUpdateMessageCommandHandler>();
        context.Services.AddTransient<ILocalEventHandler<UpdateMessageCommand>, CreateOrUpdateMessageCommandHandler>();
        
        var config = context.Services.GetConfiguration();
        
        var section = config.GetSection("xx");
        
        if (section != null)
            Configure<ApiOptions>(section);
        else
            // ReSharper disable once HeuristicUnreachableCode
            throw new InvalidOperationException("Client section not found.");
        
        context.Services.AddSingleton(provider =>
        {
            var options = provider.GetRequiredService<IOptions<ApiOptions>>().Value;
            return new Client(options.Config);
        });
    }
}
  • With these two modules I decided to use abp local event bus as part of the CQRS logic. I have implemented all the handler but if I use for example ITransient interface the handlers are not registered. Only if I register the handler using context.Services.AddTransient the handler appear registered but the event bus logic never call the event handler but no error appear. Checking the AbpEventBusModule you apply this operation:
private static void AddEventHandlers(IServiceCollection services)
{
    var localHandlers = new List<Type>();
    var distributedHandlers = new List<Type>();

    services.OnRegistered(context =>
    {
        if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(ILocalEventHandler<>)))
        {
            localHandlers.Add(context.ImplementationType);
        }
        else if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IDistributedEventHandler<>)))
        {
            distributedHandlers.Add(context.ImplementationType);
        }
    });

    services.Configure<AbpLocalEventBusOptions>(options =>
    {
        options.Handlers.AddIfNotContains(localHandlers);
    });

    services.Configure<AbpDistributedEventBusOptions>(options =>
    {
        options.Handlers.AddIfNotContains(distributedHandlers);
    });
}

But this always have enmpty collection of events. You can see I am registering the modules in the configuration service but the event bus never call any handler because not find this elements.

Why I am having issues with Autofac? reading your doc you remarks that we need to have installed autofac in all modules we want to use it. I have the module installed an loaded in all modules and I continue having the problem.

Reproduction Steps

  • Create an app without the CLI dotnet new webapi,
  • Configure configure abp as startup module
  • Create a project library called Kernel and configure the module lifecycle.
  • In kernel install Volo.Abp.Ddd.Domain
  • Create a service that implement ILocalEventHandler<EntityEto>
  • From web api try to send an EntityEto event.
  • The event handler never respond to that event.

Expected behavior

I want to get registered all the event handlers and use it in CQRS architecture.

Actual behavior

Local event handlers are never registered by ABP dependency injection system

Regression?

No response

Known Workarounds

No response

Version

8 but I am having this issue in version 7 and 6

User Interface

Common (Default)

Database Provider

None/Others

Tiered or separate authentication server

Separate Auth Server

Operation System

macOS

Other information

No response

iddelacruz avatar Jan 26 '24 07:01 iddelacruz

Hi, did you call the UseAutofac() method in the Program.cs file as described in the documentation?

EngincanV avatar Jan 26 '24 08:01 EngincanV

Hi, yes this is the program:

public static async Task<int> Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
#if DEBUG
            .MinimumLevel.Debug()
#else
            .MinimumLevel.Information()
#endif
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .Enrich.FromLogContext()
            .WriteTo.Async(c => c.File("Logs/logs.txt"))
            .WriteTo.Async(x => x.Console())
            .CreateLogger();

        try
        {
            Log.Information("Starting chat group collector...");
            var builder = WebApplication.CreateBuilder(args);
            
            builder.Host.AddAppSettingsSecretsJson()
                .UseAutofac()
                .UseSerilog();
            
            await builder.AddApplicationAsync<EyeModule>();
            var app = builder.Build();
            await app.InitializeApplicationAsync();
            
            Log.Information("Chat group collector started...");
            
            await app.RunAsync();
            return 0;
        }
        catch (Exception ex)
        {
            if (ex is HostAbortedException)
            {
                throw;
            }

            Log.Fatal(ex, "Host terminated unexpectedly!");
            return 1;
        }
        finally
        {
            await Log.CloseAndFlushAsync();
        }
    }```

iddelacruz avatar Jan 26 '24 10:01 iddelacruz

Since it is not being registered through interfaces or attributes, this method apparently is not taking it, so it cannot subscribe to the events. Can be?

iddelacruz avatar Jan 26 '24 16:01 iddelacruz

I found something: I have the command handler:

CreateMessageCommandHandler:ILocalEventHandler<CreateMessageCommand>, ITransientDependency

If I use only ITransientDependency the command handler is never registered. And if I register the command handler: context.Services.AddTransient<ILocalEventHandler<ICreateOrUpdateCommand>, CreateOrUpdateMessageCommandHandler>(); without the ITransientDependency the handler is registered but when I need to send an event the system not found the command handler and throw an exception:

[ERR] The requested service 'Kernel.Commands.CreateOrUpdateMessageCommandHandler' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.

Then I need classic registration and the interface to put the handler ready for working. How can I fix this problem with autofac to register only using interfaces or attributes

iddelacruz avatar Jan 27 '24 12:01 iddelacruz