Skip to main content
Version: 2.4.1

Configuration & Bootstrapping

RCommon uses a single fluent builder entry point. All configuration happens at application startup before the DI container is built.

The AddRCommon() extension method​

AddRCommon() is an extension method on IServiceCollection defined in RCommon.Core. Calling it creates an RCommonBuilder, registers the core framework services, and returns an IRCommonBuilder for further configuration.

using RCommon;

// In Program.cs or a DI registration method
builder.Services.AddRCommon();

Calling AddRCommon() always registers these services regardless of what else is configured:

  • EventSubscriptionManager (singleton) — tracks event-to-producer subscriptions
  • IEventBus backed by InMemoryEventBus (singleton) — in-process publish/subscribe
  • IEventRouter backed by InMemoryTransactionalEventRouter (scoped) — coordinates domain events with the unit of work
  • CachingOptions configured with caching disabled by default

Builder chain​

The return value of AddRCommon() is IRCommonBuilder. Every With* method on the builder returns the same IRCommonBuilder instance so calls can be chained:

builder.Services.AddRCommon()
.WithSequentialGuidGenerator(...)
.WithDateTimeSystem(...)
.WithPersistence<EFCorePerisistenceBuilder>(...)
.WithUnitOfWork<DefaultUnitOfWorkBuilder>(...)
.WithMediator<MediatRBuilder>(...)
.WithEventHandling<InMemoryEventBusBuilder>(...);

Every With* call is independent and opt-in. Omitting a call means those services are not registered.

Core builder methods​

WithSequentialGuidGenerator​

Registers IGuidGenerator as a SequentialGuidGenerator. Sequential GUIDs are ordered in a way that reduces index fragmentation in SQL databases.

.WithSequentialGuidGenerator(options =>
{
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString;
})

SequentialGuidType values:

ValueDescription
SequentialAsStringSequential in string representation — compatible with MySQL and PostgreSQL
SequentialAsBinarySequential in binary representation — compatible with Oracle
SequentialAtEndSequential at the end — compatible with SQL Server

Only one GUID generator may be configured. Calling WithSequentialGuidGenerator or WithSimpleGuidGenerator a second time throws RCommonBuilderException.

WithSimpleGuidGenerator​

Registers IGuidGenerator as a SimpleGuidGenerator that wraps Guid.NewGuid().

.WithSimpleGuidGenerator()

WithDateTimeSystem​

Registers ISystemTime as SystemTime. Inject ISystemTime wherever you need the current time so that tests can substitute a known value.

.WithDateTimeSystem(options =>
{
options.Kind = DateTimeKind.Utc;
})

Only one date/time system may be configured per builder instance.

WithCommonFactory​

Registers a service type alongside a DI-aware ICommonFactory<TService> that can create instances through the container.

.WithCommonFactory<IMyService, MyServiceImpl>()

Persistence configuration​

WithPersistence<T> accepts any type that implements IPersistenceBuilder. The built-in options are EFCorePerisistenceBuilder, DapperPersistenceBuilder, and Linq2DbPersistenceBuilder.

.WithPersistence<EFCorePerisistenceBuilder>(ef =>
{
ef.AddDbContext<AppDbContext>("AppDb", options =>
{
options.UseSqlServer(connectionString);
});
ef.SetDefaultDataStore(ds =>
{
ds.DefaultDataStoreName = "AppDb";
});
})

Multiple DbContexts can be registered with different names. The name is used to resolve the correct context at runtime via IDataStoreFactory. Use SetDefaultDataStore to declare which context a repository uses when no explicit data store name is specified on the repository instance.

Unit of work configuration​

WithUnitOfWork<T> accepts any type that implements IUnitOfWorkBuilder. Use DefaultUnitOfWorkBuilder for the standard System.Transactions-based implementation.

.WithUnitOfWork<DefaultUnitOfWorkBuilder>(uow =>
{
uow.SetOptions(options =>
{
options.AutoCompleteScope = true;
options.DefaultIsolation = IsolationLevel.ReadCommitted;
});
})

AutoCompleteScope causes the unit of work scope to commit automatically when the outermost scope completes without an exception. Set it to false if you need explicit Complete() calls.

Mediator configuration​

WithMediator<T> accepts any type that implements IMediatorBuilder. Use MediatRBuilder for MediatR integration.

.WithMediator<MediatRBuilder>(mediator =>
{
mediator.AddRequest<CreateOrderCommand, CreateOrderCommandHandler>();
mediator.AddRequest<GetOrderQuery, OrderDto, GetOrderQueryHandler>();

mediator.Configure(config =>
{
config.RegisterServicesFromAssemblies(typeof(ApplicationAssemblyMarker).Assembly);
});

mediator.AddLoggingToRequestPipeline();
mediator.AddUnitOfWorkToRequestPipeline();
})

Event handling configuration​

WithEventHandling<T> accepts any type that implements IEventHandlingBuilder. Use InMemoryEventBusBuilder for in-process events.

.WithEventHandling<InMemoryEventBusBuilder>(eventHandling =>
{
eventHandling.AddSubscriber<OrderCreatedEvent, OrderCreatedEventHandler>();
eventHandling.AddSubscriber<OrderCancelledEvent, OrderCancelledEventHandler>();
})

Each AddSubscriber<TEvent, THandler> call registers the handler as a scoped ISubscriber<TEvent> and records the event-to-producer subscription in the EventSubscriptionManager so the IEventRouter knows which producers handle which events.

Complete example​

The following shows a realistic Program.cs configuration for a web API that uses EF Core persistence, MediatR, FluentValidation, and in-process events:

using RCommon;
using RCommon.Persistence.EFCore;
using RCommon.Persistence.Transactions;
using RCommon.Mediator.MediatR;
using RCommon.EventHandling;
using RCommon.FluentValidation;
using Microsoft.EntityFrameworkCore;
using System.Transactions;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRCommon()
.WithSequentialGuidGenerator(guid =>
guid.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString)
.WithDateTimeSystem(dt =>
dt.Kind = DateTimeKind.Utc)
.WithPersistence<EFCorePerisistenceBuilder>(ef =>
{
ef.AddDbContext<AppDbContext>("AppDb", options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")));
ef.SetDefaultDataStore(ds =>
ds.DefaultDataStoreName = "AppDb");
})
.WithUnitOfWork<DefaultUnitOfWorkBuilder>(uow =>
{
uow.SetOptions(options =>
{
options.AutoCompleteScope = true;
options.DefaultIsolation = IsolationLevel.ReadCommitted;
});
})
.WithMediator<MediatRBuilder>(mediator =>
{
mediator.Configure(config =>
config.RegisterServicesFromAssemblies(typeof(ApplicationAssemblyMarker).Assembly));
mediator.AddLoggingToRequestPipeline();
mediator.AddUnitOfWorkToRequestPipeline();
})
.WithEventHandling<InMemoryEventBusBuilder>(events =>
{
events.AddSubscriber<OrderCreatedEvent, OrderCreatedEventHandler>();
})
.WithValidation<FluentValidationBuilder>(validation =>
{
validation.AddValidatorsFromAssemblyContaining<ApplicationAssemblyMarker>();
});

Diagnostics​

During development you can print all registered services to the console to verify configuration:

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

This produces a sorted list of every ServiceDescriptor in the container, which is useful for spotting missing or duplicate registrations.

RCommonRCommon