rebus-org / Rebus

:bus: Simple and lean service bus implementation for .NET
https://mookid.dk/category/rebus
Other
2.27k stars 354 forks source link

IOC in a IIncomingStep implementation #872

Closed sabbadino closed 4 years ago

sabbadino commented 4 years ago

Hi, I read this wiki page describing how to inject a custom step in the incoming / outgoing pipeline https://github.com/rebus-org/Rebus/wiki/Extensibility

It's no clear to me how to use ioc in this situation : the example provided requires to give a instance of my custom step , while i would like to provide an interface that is resolved by the ioc at run time.

Is it possible ? can you provide an example ? thank you p.s. : using Rebus on dotnertcore 2.2

mookid8000 commented 4 years ago

The thing is, Rebus does NOT use IoC for any of its things at runtime.

It uses Injectionist to compose itself from the configuration you choose, which allows you to hook into it in various ways by decorating services, and then it may use IoC to create your handlers if you provided some kind of container adapter.

But the pipelines are constructed by Injectionist when the bus instance is created, and so it is not possible to resolve the steps from your container.

However, it's certainly possible to write a pipeline step that resolves stuff from your container. If you're using Rebus.ServiceProvider, the current scope will be added to the context when your handler(s) have been resolved, so if you add a step like this:

Configure.With(...)
    .(...)
    .Options(o => {
        // (*)
        o.Decorate<IPipeline>(c => {
            var pipeline = c.Get<IPipeline>();
            var step = new MyStep();

            return new PipelineStepInjector(pipeline)
                .OnReceive(step, PipelineRelativePosition.After, typeof(ActivateHandlersStep));
        });
    })
    .Start();

(positioning your step af Rebus' own ActivateHandlersStep), then your step can get access to the scope like this:

public class MyStep : IIncomingStep
{
    public async Task Process(IncomingStepContext context, Func<Task> next)
    {
        var scope = context.Load<IServiceScope>();

        // whee!

        await next();
    }
}

(*) Please always hide the nasty .Decorate(...) stuff behind an extension method so it becomes pretty like this:

Configure.With(...)
    .(...)
    .Options(o => o.UseMyStep())
    .Start();

EDIT: Just corrected the first snippet to use ActivateHandlersStep for positioning. Check out this test for a working sample.

slimjack commented 4 years ago

@mookid8000 I tried the following configuration

services.AddRebus(configure =>
{
    return configure
        .Transport(t => t.UseAzureServiceBus(connectionString, serviceQueueName))
        .Options(o =>
        {
            o.Decorate<IPipeline>(c => {
                var pipeline = c.Get<IPipeline>();
                var step = new MyStep();

                return new PipelineStepInjector(pipeline)
                    .OnReceive(step, PipelineRelativePosition.After, typeof(DependencyInjectionHandlerActivator));
            });
        });
});

But it fails with

Rebus.Injection.ResolutionException: Could not resolve Rebus.Pipeline.IPipeline with decorator depth 0 - registrations:
 Rebus.Injection.Injectionist+Handler ---> System.ArgumentException:
 Could not finish composition of the RECEIVE pipeline because the following injections could not be made:
 <customnamespace>.Management.Api.MyStep => After => Rebus.ServiceProvider.DependencyInjectionHandlerActivator

Please pick other steps to use when anchoring your step injections, or pick another way of assembling the pipeline.
If you require the ultimate flexibility, you will probably need to decorate IPipeline and compose it manually.

   at Rebus.Pipeline.PipelineStepInjector.ComposeReceivePipeline()+MoveNext()
   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
   at Rebus.Pipeline.PipelineStepConcatenator.ReceivePipeline() in C:\\projects-rebus\\Rebus\\Rebus\\Pipeline\\PipelineStepConcatenator.cs:line 78
   at Rebus.Pipeline.PipelineCache..ctor(IPipeline pipeline) in C:\\projects-rebus\\Rebus\\Rebus\\Pipeline\\PipelineCache.cs:line 14
   at Rebus.Config.RebusConfigurer.<>c.<Start>b__13_22(IResolutionContext c) in C:\\projects-rebus\\Rebus\\Rebus\\Config\\RebusConfigurer.cs:line 356
   at Rebus.Injection.Injectionist.ResolutionContext.Get[TService]() in C:\\projects-rebus\\Rebus\\Rebus\\Injection\\Injectionist.cs:line 199
   --- End of inner exception stack trace ---
   at Rebus.Injection.Injectionist.ResolutionContext.Get[TService]() in C:\\projects-rebus\\Rebus\\Rebus\\Injection\\Injectionist.cs:line 248
   at Rebus.Config.RebusConfigurer.<>c.<Start>b__13_11(IResolutionContext c) in C:\\projects-rebus\\Rebus\\Rebus\\Config\\RebusConfigurer.cs:line 232
   at Rebus.Injection.Injectionist.ResolutionContext.Get[TService]() in C:\\projects-rebus\\Rebus\\Rebus\\Injection\\Injectionist.cs:line 199
   at Rebus.Config.RebusConfigurer.<>c.<Start>b__13_13(IResolutionContext c) in C:\\projects-rebus\\Rebus\\Rebus\\Config\\RebusConfigurer.cs:line 254
   at Rebus.Injection.Injectionist.ResolutionContext.Get[TService]() in C:\\projects-rebus\\Rebus\\Rebus\\Injection\\Injectionist.cs:line 199
   at Rebus.Config.RebusConfigurer.<>c__DisplayClass13_0.<Start>b__27(IResolutionContext c) in C:\\projects-rebus\\Rebus\\Rebus\\Config\\RebusConfigurer.cs:line 369
   at Rebus.Injection.Injectionist.ResolutionContext.Get[TService]() in C:\\projects-rebus\\Rebus\\Rebus\\Injection\\Injectionist.cs:line 199
   at Rebus.Config.RebusConfigurer.<>c__DisplayClass13_0.<Start>b__28(IResolutionContext c) in C:\\projects-rebus\\Rebus\\Rebus\\Config\\RebusConfigurer.cs:line 390
   at Rebus.Injection.Injectionist.ResolutionContext.Get[TService]() in C:\\projects-rebus\\Rebus\\Rebus\\Injection\\Injectionist.cs:line 199
   at Rebus.Injection.Injectionist.Get[TService]() in C:\\projects-rebus\\Rebus\\Rebus\\Injection\\Injectionist.cs:line 57
   at Rebus.Config.RebusConfigurer.Start() in C:\\projects-rebus\\Rebus\\Rebus\\Config\\RebusConfigurer.cs:line 436
   at Rebus.Config.DelayedStartupConfigurationExtensions.Create(RebusConfigurer configurer) in C:\\projects-rebus\\Rebus\\Rebus\\Config\\DelayedStartupConfigurationExtensions.cs:line 19
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitSingleton(SingletonCallSite singletonCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Rebus.ServiceProvider.ServiceCollectionExtensions.<>c.<AddRebus>b__1_5(IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitSingleton(SingletonCallSite singletonCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Rebus.ServiceProvider.ServiceCollectionExtensions.<>c.<AddRebus>b__1_6(IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitSingleton(SingletonCallSite singletonCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Rebus.ServiceProvider.ServiceProviderExtensions.UseRebus(IServiceProvider provider, Action`1 busAction)
   at <customnamespace>.Management.Api.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env) in <customproject>\\Startup.cs:line 169
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.Configure(IApplicationBuilder app)
   at Microsoft.ApplicationInsights.AspNetCore.ApplicationInsightsStartupFilter.<>c__DisplayClass2_0.<Configure>b__0(IApplicationBuilder app)
   at Microsoft.AspNetCore.Mvc.Internal.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
   at Microsoft.AspNetCore.Hosting.Internal.AutoRequestServicesStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()","Properties":{"EventId":{"Id":6},"SourceContext":"Microsoft.AspNetCore.Hosting.Internal.WebHost"
sabbadino commented 4 years ago

Hi, according to the post of @slimjack , shouldn't you re open the topic ? Or a new one must opened ?

mookid8000 commented 4 years ago

hi @slimjack , sorry for taking so long to get back to you ... and no need to reopen.... I'll get back to you on Tuesday 🙂

mookid8000 commented 4 years ago

Hi again @slimjack , sorry, but the exception you get there is because I was a little bit too fast when I posted the code snippets above! I've corrected the snippets to work.

The reason it didn't work is because DependencyInjectionHandlerActivator is not a step, it's a service, which Rebus uses from its built-in ActivateHandlersStep – so we simply need to position our step AFTER the ActivateHandlersStep, which is done like this:

    .OnReceive(step, PipelineRelativePosition.After, typeof(ActivateHandlersStep));

If you want to see a working example (including wrapping in an extension method), check out this test: https://github.com/rebus-org/Rebus.ServiceProvider/blob/master/Rebus.ServiceProvider.Tests/CheckServiceScopeAccessFromPipelineStep.cs