rebus-org / Rebus.Autofac

:bus: Autofac container adapter for Rebus
https://mookid.dk/category/rebus
Other
12 stars 11 forks source link

Resolve ComponentContext from steps #19

Closed Nemadjo94 closed 2 years ago

Nemadjo94 commented 3 years ago

Hi! I noticed that ComponentContext cannot be resolved in steps like in Rebus.ServiceProvider package where the ServiceProviderProviderStep provides the ServiceProvider for the rest of the pipeline. Would it be possible to enable resolving ComponentContext in steps for the next version?

For example in my project we are using rebus middleware to fetch the headers from http context and set them to message context for audit purposes. Something like this:

`/// <summary>
    /// Implementation of <see cref="IOutgoingStep"/> that fetches the requestId neccesseary for audit trail
    /// from Http Context and attaches it to rebus message context.
    /// </summary>
    internal class AttachRequestIdToOutgoingMessagesStep : IOutgoingStep
    {
        public async Task Process(OutgoingStepContext context, Func<Task> next)
        {
            var componentContext = context.Load<IComponentContext>();
            var httpContextAccessor = componentContext.Resolve<IHttpContextAccessor>();

            var requestId = httpContextAccessor.HttpContext.Request.Headers["x-request-id"];

            if (!string.IsNullOrWhiteSpace(requestId))
            {
                var message = context.Load<Message>();
                message.Headers["x-request-id"] = requestId.ToString();
            }

            await next();
        }
    }`

But in order for our steps to be able to resolve services from ComponentContext, I created an extension method which will enable the ComponentContextProviderStep to provide the ComponentContext for the rest of the pipeline. Like this:

`[StepDocumentation("Adds the component context to the incoming/outgoing step context, thus making it available for the rest of the pipeline.")]
    public class ComponentContextProviderStep : IIncomingStep, IOutgoingStep
    {
        readonly IComponentContext _context;

        /// <summary>
        /// Creates the step
        /// </summary>
        public ComponentContextProviderStep(IComponentContext context) =>
            _context = context ?? throw new ArgumentNullException(nameof(context));

        /// <summary>
        /// Saves the component context in the pipeline's context and invokes the rest of the pipeline
        /// </summary>
        public async Task Process(IncomingStepContext context, Func<Task> next)
        {
            context.Save(_context);
            await next();
        }

        /// <summary>
        /// Saves the component context in the pipeline's context and invokes the rest of the pipeline
        /// </summary>
        public async Task Process(OutgoingStepContext context, Func<Task> next)
        {
            context.Save(_context);
            await next();
        }
    }`

Also I played around a bit with the Rebus.Autofac package and managed to set up the ComponentContextProviderStep. Like this:

`public static void RegisterRebus(this ContainerBuilder containerBuilder, Func<RebusConfigurer, IComponentContext, RebusConfigurer> configure)
        {
            if (containerBuilder == null) throw new ArgumentNullException(nameof(containerBuilder));
            if (configure == null) throw new ArgumentNullException(nameof(configure));

            new AutofacHandlerActivator(containerBuilder, (configurer, context) => configure(
                configurer.Options(o => o.Decorate<IPipeline>(c =>
                {
                    var pipeline = c.Get<IPipeline>();
                    var step = new ComponentContextProviderStep(context.Resolve<IComponentContext>());

                    return new PipelineStepConcatenator(pipeline)
                                .OnReceive(step, PipelineAbsolutePosition.Front)
                                .OnSend(step, PipelineAbsolutePosition.Front);
                }))
                , context), startBus: true, enablePolymorphicDispatch: false);
        }`

I tested this and it works fine (I was able to resolve HttpContextAccessor in my test step) but I'm not sure if this is the right implementation.

mookid8000 commented 3 years ago

I tested this and it works fine (I was able to resolve HttpContextAccessor in my test step) but I'm not sure if this is the right implementation.

Cool! One thing, though: You are injecting IComponentContext into the ctor of ComponentContextProviderStep, but since Rebus' pipeline consists of singleton steps, they are built when the bus is started, and so the IComponentContext is resolved only once.

I don't know much about Autofac, but wouldn't it be better to resolve it in the Process method of the step?

Apart from that, I think your implementation looks really good, and it would be awesome if you would send a PR if you are interested in having it included in the next version šŸ™‚

Nemadjo94 commented 3 years ago

Hi! I'm glad you like my implementation, I would gladly send a PR. I just have to take some time first to re-read the old notes since I moved on to other things on the project in the meantime :)

I don't know much about Autofac either but I believe you're right, I'll give it a try and see how it goes.

mookid8000 commented 2 years ago

Hi @Nemadjo94 , did you get any further with this one?

Nemadjo94 commented 2 years ago

@mookid8000 Sorry, I didn't have the time to work on the PR since I moved to another project where we don't use Rebus :/

mookid8000 commented 2 years ago

Ok, fair enough šŸ™‚ feel free to come but if/when you get to use Rebus again šŸ˜