Search code examples
c#dependency-injectionfactory-patternasp.net-core-3.1

.Using a Factory Pattern with NET Core 3.1 Dependency Injection


I have a .NET Core 3.1 API that may either be using RabbitMq or Azure Service Bus. The choice will be determined via a configuration parameter. Since the configuration to use is a runtime decision, I wish to use a factory pattern along with .NET Core's dependency injection. I found an article at https://medium.com/@mailbox.viksharma/factory-pattern-using-built-in-dependency-injection-of-asp-net-core-f91bd3b58665, but cannot get the factory to work. Any help will be greatly appreciated.

The issue is occurring within the Factory class due to IServiceProvider. I am receiving the error System.NullReferenceException: Object reference not set to an instance of an object. from the attempt to GetService.

Factory class

public class MessageServiceFactory
{
    readonly IServiceProvider serviceProvider;

    public MessageServiceFactory(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    public IMessagingService GetMessagingService()
    {
        var messageProvider = ConfigProvider.GetConfig("MessageService", "Messaging_Service");

        switch(messageProvider)
        {
            case "AzureServiceBus": return (IMessagingService)serviceProvider.GetService(typeof(MassTransitAzureServiceBusMessagingService));
            case "RabbitMq": return (IMessagingService)serviceProvider.GetService(typeof(MassTransitRabbitMqMessagingService));
                default: throw new ArgumentException("Invalid message service");
        };
    }
}

Service Interface

public interface IMessagingService
{
    Task Publish(object payload);
}

RabbitMq Concrete Implementation

public class MassTransitRabbitMqMessagingService : IMessagingService
{
    readonly IMassTransitRabbitMqTransport massTransitTransport;

    public MassTransitRabbitMqMessagingService(IMassTransitRabbitMqTransport massTransitTransport)
    {
        //transport bus config already happens in massTransitTransport constructor
        this.massTransitTransport = massTransitTransport;
    }

    public async Task Publish(object payload)
    {
        ....
    }
}

ConfigureServices in Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(Configuration);

        services.AddScoped<IMassTransitRabbitMqTransport, MassTransitRabbitMqTransport>();
        services.AddScoped<IMassTransitAzureServiceBusTransport, MassTransitAzureServiceBusTransport>();

        services.AddScoped<MessageServiceFactory>();

        services.AddScoped<IMessagingService, MassTransitAzureServiceBusMessagingService>(s => s.GetService<MassTransitAzureServiceBusMessagingService>());
        services.AddScoped<IMessagingService, MassTransitRabbitMqMessagingService>(s => s.GetService<MassTransitRabbitMqMessagingService>());

        services.AddControllers();
    }

Controller

[ApiController]
[Route("api/[controller]")]
public class ListenerController : ControllerBase
{
    readonly ILogger<ListenerController> logger;
    readonly MessageServiceFactory messageFactory;

    public ListenerController(
        ILogger<ListenerController> logger,
        MessageServiceFactory messageFactory)
    {
        this.logger = logger;
        this.messageFactory = messageFactory;
    }

    [HttpPost]
    public async Task<IActionResult> Post()
    {
        var payload = new
        {
            ...
        };

        await messageFactory.GetMessagingService().Publish(payload);

        return Ok(
            new GDMSResponse()
            {
                ProcessedDate = DateTime.Now,
                SuccessFlag = true
            }
        );
    }
}

Solution

  • This requires restart after changing the configuration, but I see no problem in doing it like this.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(Configuration);
    
        services.AddScoped<IMassTransitRabbitMqTransport, MassTransitRabbitMqTransport>();
        services.AddScoped<IMassTransitAzureServiceBusTransport, MassTransitAzureServiceBusTransport>();
    
        var messageProvider = Configuration.GetConfig("MessageService", "Messaging_Service");
        switch(messageProvider)
        {
            case "AzureServiceBus": 
                services.AddScoped<IMessagingService, MassTransitAzureServiceBusMessagingService>();
                break;
            case "RabbitMq": 
                services.AddScoped<IMessagingService, MassTransitRabbitMqMessagingService>();
                break;
            default: 
                throw new ArgumentException("Invalid message service");
        };
    
        services.AddControllers();
    }
    

    Other note

    I noticed that you supplied both the concrete type and a factory:

    services.AddScoped<IMessagingService, MassTransitAzureServiceBusMessagingService>(s => s.GetService<MassTransitAzureServiceBusMessagingService>());

    I think it should be:

    services.AddScoped<IMessagingService>(s => s.GetService<MassTransitAzureServiceBusMessagingService>());

    Not sure it it makes a difference.

    UPDATE Jan 2021 Recently I had to do this myself and came up with this solution:

    public static IServiceCollection ConfigureEventBus(this IServiceCollection services, IConfiguration configuration)
    {
        var queueSettings = new QueueSettings();
        configuration.GetSection("QueueSettings").Bind(queueSettings);
    
        if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
        {
            services.AddMassTransit(x =>
            {
                x.UsingAzureServiceBus((context, cfg) =>
                {
                    cfg.Host(queueSettings.HostName);
                });
            });
        }
        else
        {
            services.AddMassTransit(x =>
            {
                x.UsingRabbitMq((context, cfg) =>
                {
                    cfg.ConfigureEndpoints(context);
                    cfg.Host(queueSettings.HostName, queueSettings.VirtualHost, h =>
                    {
                        h.Username(queueSettings.UserName);
                        h.Password(queueSettings.Password);
                    });
                });
            });
        }
    
        services.AddMassTransitHostedService();
        services.AddSingleton<IEventBus, MassTransitEventBus>();
    
        return services;
    }