简体   繁体   中英

Masstransit configuring a generic consumer

I'm in the process of integrating a messaging broker within our existing application using MassTransit. We had already implemented a kind of command handler that had generic implementations, like this:

public class MyCommandHandler: CommandHandlerBase<MyCommand>

Now it was relatively easy to make a generic Consumer that would do some boiler plating and would hand off the work to the ready command handler, requested from the DI container.

public class CommandConsumer<TCommand> : IConsumer<TCommand>

Which I could then easily register through the Microsoft DI thusly:

cfg.AddConsumer<CommandConsumer<MyCommand>>(x => some configuration...);

This all worked great, so I moved on to the next step, namely to extract the consumer registration(s) to a common helper method and this is where I'm a bit stumped. The method (currently) looks a bit like this

public static IServiceCollection ConfigureMassTransit(this IServiceCollection services, params Type[] consumerTypes)
{
        return 
            services.AddMassTransit(cfg =>
            {
                foreach (var consumerType in consumerTypes)
                {
                    cfg.AddConsumer(consumerType);
                }
                // or cfg.AddConsumers(consumerTypes);
                cfg.AddBus(context => Bus.Factory.CreateUsingRabbitMq(config =>
                {
                    var host = config.Host("localhost", "/",
                        h =>
                        {
                            h.Username("guest");
                            h.Password("guest");
                        });
                    config.ConfigureEndpoints(context);
                }));

            });
    }

which would be called as services.ConfigureMassTransit(typeof(CommandConsumer<MyCommand>)); This again works, but what I can't figure out is how to add the additional configuration to the registration; the overload that takes an Action is only available when using the generic signature, which you can't use directly when you only have the Type available. I tried adding a marker class CommandConsumer: IConsumer to the CommandConsumer<TCommand> and making CommandConsumerDefinition: ConsumerDefinition<CommandConsumer> , and changing the above to cfg.AddConsumer(consumerType, typeof(CommandConsumerDefinition)); , but that doesn't work as the ConfigureConsumer override is never hit.

How am I supposed to add additional configuration to a Consumer for which I don't know the type at compile time?

Chris' answer put me on the path to a working solution. Making the CommandConsumerDefinition generic allowed me to use reflection to construct both types in the same way at runtime. This allowed MassTransit to wire up the configuration in the expected manner.

In the end, I also used a "marker" attribute that would hold the type of the command contract, so they could be discovered rather than having to be entered as a parameter at startup.

public static IServiceCollectionConfigurator ConfigureMassTransitConsumers(this IServiceCollectionConfigurator serviceConfigurator, Assembly assembly)
    {
        foreach (var type in assembly.GetTypes())
        {
            var attributes = type.GetCustomAttributes(typeof(RegisterCommandConsumerAttribute), false);
            if (attributes.Length <= 0) continue;
            foreach (var attribute in attributes)
            {
                if (attribute is RegisterCommandConsumerAttribute registerCommandConsumerAttribute)
                {
                    Type consumerType = typeof(CommandConsumer<>).MakeGenericType(registerCommandConsumerAttribute.CommandType);
                    Type consumerDefinitionType = typeof(CommandConsumerDefinition<>).MakeGenericType(registerCommandConsumerAttribute.CommandType);
                    serviceConfigurator.AddConsumer(consumerType, consumerDefinitionType);
                }
            }
        }
        return serviceConfigurator;
    }

Because of the automatic discovery, we were already in the realm of reflection, so this seemed like an acceptable solution. This way we can have generic consumers and definitions without having to add a new class for every command contract we have.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM