# Autofac
Autofac is a powerful and fast container, and is well supported by MassTransit. Nested lifetime scopes are used extensively to encapsulate dependencies and ensure clean object lifetime management. The following examples show the various ways that MassTransit can be configured, including the appropriate interfaces necessary.
A sample project for the container registration code is available on GitHub.
Requires NuGets
MassTransit
,MassTransit.AutoFac
, andMassTransit.RabbitMQ
TIP
Consumers should not depend upon IBus or IBusControl. A consumer should use the ConsumeContext instead, which has all of the same methods as IBus, but is scoped to the receive endpoint. This ensures that messages can be tracked between consumers and are sent from the proper address.
using System;
using System.Reflection;
using System.Threading.Tasks;
using Autofac;
using MassTransit;
namespace Example
{
public class UpdateCustomerAddressConsumer :
MassTransit.IConsumer<UpdateCustomerAddress>
{
public Task Consume(ConsumeContext<UpdateCustomerAddress> context)
{
//do stuff
}
}
class Program
{
public static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.AddMassTransit(x =>
{
// add a specific consumer
x.AddConsumer<UpdateCustomerAddressConsumer>();
// add all consumers in the specified assembly
x.AddConsumers(Assembly.GetExecutingAssembly());
// add consumers by type
x.AddConsumers(typeof(ConsumerOne), typeof(ConsumerTwo));
// add the bus to the container
x.AddBus(context => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host("localhost");
cfg.ReceiveEndpoint("customer_update", ec =>
{
// Configure a single consumer
ec.ConfigureConsumer<UpdateCustomerConsumer>(context);
// configure all consumers
ec.ConfigureConsumers(context);
// configure consumer by type
ec.ConfigureConsumer(typeof(ConsumerOne));
});
// or, configure the endpoints by convention
cfg.ConfigureEndpoints(context);
});
});
var container = builder.Build();
var bc = container.Resolve<IBusControl>();
bc.Start();
}
}
}
# Using Nested Container
MassTransit and Autofac give you an ability to reconfigure your container based on Consumer. It could be very powerful when you have different way to resolve your services depends on Consumer's type. It could be very helpful to build Multitenant applications.
builder.RegisterType<HttpTenantProvider>().As<ITenantProvider>();
builder.Register(context =>
{
var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
//configuration
cfg.ReceiveEndpoint("name", ec =>
{
ec.Consumer<YourConsumer>(context, "scope_name", (c, context) =>
{
c.RegisterInstance(new ConsumerTenantProvider(context)).As<ITenantProvider>();
//other configuration
});
})
}
}
# Using a Module
Autofac modules are great for encapsulating configuration, and that is equally true when using MassTransit. An example of using modules with Autofac is shown below.
class ConsumerModule :
Module
{
protected override void Load(ContainerBuilder builder)
{
// requires that AddMassTransit has been called prior to this
// module being loaded, maybe, I don't know for sure that this
// is even possible
builder.AddConsumer<UpdateCustomerAddressConsumer>();
builder.RegisterType<SqlCustomerRegistry>()
.As<ICustomerRegistry>();
}
}
class BusModule :
Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(context =>
{
var busSettings = context.Resolve<BusSettings>();
var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(busSettings.HostAddress, h =>
{
h.Username(busSettings.Username);
h.Password(busSettings.Password);
});
cfg.AddConsumersFromContainer(context);
cfg.ReceiveEndpoint(busSettings.QueueName, ec =>
{
ec.ConfigureConsumers(context);
})
});
})
.SingleInstance()
.As<IBusControl>()
.As<IBus>();
}
}
public IContainer CreateContainer()
{
var builder = new ContainerBuilder();
builder.RegisterModule<BusModule>();
builder.RegisterModule<ConsumerModule>();
return builder.Build();
}
# Registering State Machine Sagas
You can also register state machine sagas:
var builder = new ContainerBuilder();
// register everything else
// register saga state machines, assuming Saga1 and Saga2 are in different assemblies
builder.RegisterStateMachineSagas(typeof(Saga1).Assembly, typeof(Saga2).Assembly);
// registering saga state machines from current assembly
builder.RegisterStateMachineSagas(Assembly.GetExecutingAssembly());
// do not forget registering saga repositories
// see examples below
and load them from a contained when configuring the bus.
var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(busSettings.HostAddress, h =>
{
h.Username(busSettings.Username);
h.Password(busSettings.Password);
});
cfg.ReceiveEndpoint(busSettings.QueueName, ec =>
{
// loading consumers
ec.ConfigureConsumers(context);
// loading saga state machines
ec.ConfigureSagas(context);
})
});
# Saga persistence
Below you find samples of how to register different saga persistence implementations with Autofac.
Saga repositories must be registered as singletons (
SingleInstance()
).
# NHibernate
For NHibernate you can scan an assembly where your saga instance mappings are defined to find the mapping classes, and then give the list of mapping types as a parameter to the session factory provider.
Then, you instruct Autofac to use the session factory provider to get the ISession
instance. NHibernate saga repository is then registered as generic and since it only uses the ISession
, everything will just work.
var mappings = mappingsAssembly
.GetTypes()
.Where(t => t.BaseType != null && t.BaseType.IsGenericType &&
(t.BaseType.GetGenericTypeDefinition() == typeof(SagaClassMapping<>) ||
t.BaseType.GetGenericTypeDefinition() == typeof(ClassMapping<>)))
.ToArray();
builder.Register(c => new SqlServerSessionFactoryProvider(connString, mappings).GetSessionFactory())
.As<ISessionFactory>()
.SingleInstance();
builder.RegisterGeneric(typeof(NHibernateSagaRepository<>))
.As(typeof(ISagaRepository<>));
# Entity Framework
Entity Framework saga repository needs to have a context factory as a constructor parameter. This factory just returns a DbContext
instance, which should have the information about the saga instance class mapping.
You can register repository for your SagaDbContext
like this:
//Optimistic
builder.Register(c => EntityFrameworkSagaRepository<MySaga>.CreateOptimistic(
() => new YourSagaDbContext(connectionString)))
.As<ISagaRepository<MySaga>>().SingleInstance();
//Pessimistic
builder.Register(c => EntityFrameworkSagaRepository<MySaga>.CreatePessimistic(
() => new YourSagaDbContext(connectionString)))
.As<ISagaRepository<MySaga>>().SingleInstance();
You can use your own context implementation and register the repository as generic like this:
builder.Register(c => new AssemblyScanningSagaDbContext(typeof(MySagaClassMap).Assembly,
connectionString).As<DbContext>();
builder.RegisterGeneric(typeof(EntityFrameworkSagaRepository<>))
.As(typeof(ISagaRepository<>))
.SingleInstance();
builder.RegisterStateMachineSagas(typeof(MySaga).Assembly);
The example above uses the assembly scanning DbContext
implementation, which you can find in this gist.
# MongoDB
Preferred way to use MongoDB saga repository is providing IMongoDatabase
with IMongoDbSagaConsumeContextFactory
and ICollectionNameFormatter
as constructor parameters.
You can register repository like this:
//register default collection name formatter or you could use DotCaseCollectionNameFormatter (SimpleSaga -> simple.sagas). Also you can create your own
builder.Register<DefaultCollectionNameFormatter>()
.As<ICollectionNameFormatter>()
.SingleInstance()
builder.Register<MongoDbSagaConsumeContextFactory>()
.As<IMongoDbSagaConsumeContextFactory>()
.SingleInstance()
var database = new MongoClient("mongodb://127.0.0.1").GetDatabase("sagas");
builder.Register(c => new MongoDbSagaRepository<MySaga>(
database,
c.Resolve<IMongoDbSagaConsumeContextFactory>(),
c.Resolve<ICollectionNameFormatter>()
))
.As<ISagaRepository<MySaga>>()
.SingleInstance();