Builder.cs: Dependency Configuration

I can’t take credit for this pattern but some co-workers of mine showed me this and I absolutely fell in love with this pattern. Instead of creating an extensions folder and adding all of the configuration classes and methods.. Why not just create a single class called Builder.cs and put it in the root? I started trying this out on projects and I now find it to be a very good way of organizing dependency injection and configuration. It’s a common pattern that can easily be used across multiple projects.

Builder.cs
namespace Microsoft.Extensions.DependencyInjection;

public static class Builder
{
    public static IServiceCollection AddApplicationConfiguration(this IServiceCollection services, IConfiguration configuration)
    {
        // factories
        services.AddTransient<ISmsFactory, SmsFactoryService>();

        // inject: sms
        services.AddTransient<ITwilioService, TwilioService>();

        // inject: factories
        services.AddTransient<IEmailFactory, EmailFactoryService>();
        services.AddTransient<ISmsFactory, SmsFactoryService>();

        return services;
    }
}

Making Builder Methods Extensible Using Actions

This is a very useful trick especially if we’re developing NuGet packages that are being reshared and may need standard configuration with the ability to override.

Here’s a configuration extension for MassTransit that allows overriding default values.

   public static IServiceCollection ConfigureMassTransit(this IServiceCollection services, MassTransitConfiguration masstransitConfig,
       Action<IServiceCollectionBusConfigurator> actionMasstransit = null,
       Action<IBusRegistrationContext, IServiceBusBusFactoryConfigurator> serviceBusAction = null,
       Action<IBusRegistrationContext, IRabbitMqBusFactoryConfigurator> rabbitMqAction = null,
       Action<IBusRegistrationContext, IAmazonSqsBusFactoryConfigurator> amazonSqsAction = null)
   {
       services.AddMassTransit(x =>
       {
           if (masstransitConfig.Service == MassTransitServiceType.AzureServiceBus)
           {
               actionMasstransit?.Invoke(x);

               x.UsingAzureServiceBus((context, cfg) =>
               {
                   cfg.Host(masstransitConfig.AzureServiceBus.ConnectionString);

                   serviceBusAction?.Invoke(context, cfg);
               });
           }
           else if (masstransitConfig.Service == MassTransitServiceType.RabbitMq)
           {
               actionMasstransit?.Invoke(x);

               x.UsingRabbitMq((context, cfg) =>
               {
                   cfg.Host(masstransitConfig.RabbitMq.Host, masstransitConfig.RabbitMq.VirtualHost, h =>
                   {
                       h.Username(masstransitConfig.RabbitMq.Username);
                       h.Password(masstransitConfig.RabbitMq.Password);
                   });

                   rabbitMqAction?.Invoke(context, cfg);
               });
           }
           else if (masstransitConfig.Service == MassTransitServiceType.AmazonSqs)
           {
               actionMasstransit?.Invoke(x);

               x.UsingAmazonSqs((context, cfg) =>
               {
                   cfg.Host(masstransitConfig.AmazonSqs.Host, h =>
                   {
                       h.AccessKey(masstransitConfig.AmazonSqs.AccessKey);
                       h.SecretKey(masstransitConfig.AmazonSqs.SecretKey);

                       // specify a scope for all queues
                       h.Scope(masstransitConfig.AmazonSqs.Scope);

                       // scope top ics as well
                       h.EnableScopedTopics();
                   });

                   amazonSqsAction?.Invoke(context, cfg);
               });
           }
       });

       return services;
   }