Skip to main content
Version: 2.4.1

Dependency Injection

RCommon is built entirely on Microsoft.Extensions.DependencyInjection. It does not introduce its own container or wrap the standard one. Every service RCommon registers follows the same lifetime conventions you already know from ASP.NET Core.

How RCommon registers services

When you call AddRCommon(), the RCommonBuilder receives the application's IServiceCollection and calls AddSingleton, AddScoped, and AddTransient on it directly. By the time builder.Build() is called, all RCommon services are in the same container as your application services — no separate container, no child scope complications.

// All of this is standard Microsoft DI
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRCommon()
.WithPersistence<EFCorePerisistenceBuilder>(ef => { ... })
.WithMediator<MediatRBuilder>(m => { ... });

var app = builder.Build();

Service lifetimes used by RCommon

Understanding which lifetime each service uses helps you reason about when instances are created and shared.

ServiceLifetimeReason
EventSubscriptionManagerSingletonSubscription metadata is static; built once at startup
IEventBus (InMemoryEventBus)SingletonStateless dispatcher; safe to share across requests
IEventRouter (InMemoryTransactionalEventRouter)ScopedTied to the unit of work scope; one per HTTP request
IEntityEventTracker (InMemoryEntityEventTracker)ScopedAccumulates domain events during a single unit of work
ISubscriber<TEvent> handlerScopedEvent handlers may depend on scoped services like repositories
ILinqRepository<T> / IReadOnlyRepository<T> / IWriteOnlyRepository<T>TransientStateless wrappers around the scoped DbContext
IAggregateRepository<T, TId>TransientSame as above
IUnitOfWorkTransientNew scope created each time a unit of work is opened
IUnitOfWorkFactoryTransientLightweight factory; safe as transient
IDataStoreFactoryTransientResolves the correct DbContext by name; delegates to the scoped DbContext
IGuidGenerator (sequential)TransientStateless; new instance per call
IGuidGenerator (simple)ScopedRegistered as scoped by WithSimpleGuidGenerator
ISystemTimeTransientStateless clock wrapper
ICommonFactory<T>ScopedDelegates to scoped service resolution

Injecting RCommon services

Repositories

After calling WithPersistence<EFCorePerisistenceBuilder>, the open-generic repository interfaces are available for injection anywhere:

public class OrderService
{
private readonly ILinqRepository<Order> _orders;

public OrderService(ILinqRepository<Order> orders)
{
_orders = orders;
}

public async Task<ICollection<Order>> GetPendingOrdersAsync()
{
return await _orders.FindAsync(o => o.Status == OrderStatus.Pending);
}
}

The DI container resolves ILinqRepository<Order> to EFCoreRepository<Order> which in turn receives the scoped AppDbContext through constructor injection.

IEventBus

Inject IEventBus wherever you need to publish events in-process. It is a singleton, so it can be injected into any lifetime.

public class OrderApplicationService
{
private readonly ILinqRepository<Order> _orders;
private readonly IEventBus _eventBus;

public OrderApplicationService(
ILinqRepository<Order> orders,
IEventBus eventBus)
{
_orders = orders;
_eventBus = eventBus;
}

public async Task PlaceOrderAsync(PlaceOrderRequest request)
{
var order = new Order(request.CustomerId, request.Items);
await _orders.AddAsync(order);
await _eventBus.PublishAsync(new OrderPlacedEvent(order.Id));
}
}

ISystemTime

Inject ISystemTime instead of calling DateTime.UtcNow directly so that tests can control the clock:

public class AuditService
{
private readonly ISystemTime _clock;

public AuditService(ISystemTime clock)
{
_clock = clock;
}

public DateTime GetCurrentTime() => _clock.Now;
}

IGuidGenerator

Inject IGuidGenerator to generate identifiers that are safe to use as primary keys:

public class ProductFactory
{
private readonly IGuidGenerator _guidGenerator;

public ProductFactory(IGuidGenerator guidGenerator)
{
_guidGenerator = guidGenerator;
}

public Product Create(string name, decimal price)
{
return new Product
{
Id = _guidGenerator.Create(),
Name = name,
Price = price
};
}
}

ICommonFactory<T>

ICommonFactory<T> is a DI-aware factory that resolves instances of T from the container on demand. Register it with WithCommonFactory and then inject it where deferred or conditional resolution is needed:

// Registration
builder.Services.AddRCommon()
.WithCommonFactory<IEmailSender, SmtpEmailSender>();

// Usage
public class NotificationService
{
private readonly ICommonFactory<IEmailSender> _emailFactory;

public NotificationService(ICommonFactory<IEmailSender> emailFactory)
{
_emailFactory = emailFactory;
}

public async Task SendAsync(string to, string subject, string body)
{
var sender = _emailFactory.Create();
await sender.SendAsync(to, subject, body);
}
}

Service registration patterns

Registering your own services alongside RCommon

RCommon does not prevent you from registering services before or after AddRCommon(). Standard registrations work exactly as expected:

builder.Services.AddScoped<IOrderValidator, OrderValidator>();

builder.Services.AddRCommon()
.WithPersistence<EFCorePerisistenceBuilder>(ef => { ... });

builder.Services.AddScoped<INotificationService, EmailNotificationService>();

Named data stores

When you register multiple DbContexts (for example a write model and a read model), each gets a name:

.WithPersistence<EFCorePerisistenceBuilder>(ef =>
{
ef.AddDbContext<WriteDbContext>("WriteDb", options =>
options.UseSqlServer(writeConnectionString));

ef.AddDbContext<ReadDbContext>("ReadDb", options =>
options.UseSqlServer(readConnectionString));

ef.SetDefaultDataStore(ds =>
ds.DefaultDataStoreName = "WriteDb");
})

A repository can be directed to use a specific data store by setting DataStoreName on the repository instance before use. The IDataStoreFactory resolves the correct DbContext by name at runtime.

Verifying registrations

During development you can dump the full service registration list to the console:

Console.WriteLine(builder.Services.GenerateServiceDescriptorsString());

To check for duplicate registrations — which can cause subtle bugs — use:

Console.WriteLine(builder.Services.GeneratePossibleDuplicatesServiceDescriptorsString());

Both methods are extension methods on IServiceCollection provided by RCommon.Core.

RCommonRCommon