High-performance, source-generator powered mediator / messaging framework for .NET. Eliminates runtime reflection, minimizes boilerplate, and provides an extensible module + pipeline model for cross-cutting concerns (e.g., caching, auditing).
| CI | Branch | Status |
|---|---|---|
| Prod (Stable Publish) | master |
|
| Dev (Preview Publish) | dev |
|
| Validation (PR / Feature) | PRs -> dev / master |
| Package | Stable | Preview | Downloads | Description |
|---|---|---|---|---|
| Space.Abstraction | Core abstractions: attributes, contexts, contracts | |||
| Space.DependencyInjection | DI extensions + source generator bootstrap | |||
| Space.Modules.InMemoryCache | In-memory caching module + attribute integration |
dotnet add package Space.DependencyInjection (brings Space.Abstraction)Optional module:
dotnet add package Space.Modules.InMemoryCacheusing Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddSpace(opt =>
{
opt.NotificationDispatchType = NotificationDispatchType.Parallel; // or Sequential
});
services.AddSpaceInMemoryCache(); // if caching module needed
var provider = services.BuildServiceProvider();
var space = provider.GetRequiredService<ISpace>();
public sealed record UserLoginRequest(string UserName);
/*
The response type can be defined using IRequest<T>.
*/
public sealed record UserLoginRequest(string UserName) : IRequest<UserLoginResponse>
public sealed record UserLoginResponse(bool Success);
public class UserHandlers
{
[Handle]
public ValueTask<UserLoginResponse> Login(HandlerContext<UserLoginRequest> ctx)
=> ValueTask.FromResult(new UserLoginResponse(true));
}
/*
The same functionality can be implemented using the IHandler<UserLoginRequest, UserLoginResponse> interface.
The primary difference from the attribute-based approach is that the method name will be explicitly defined in the interface implementation.
*/
public class UserHandlers : IHandler<UserLoginRequest, UserLoginResponse>
{
public ValueTask<UserLoginResponse> Handle (UserLoginRequest request, CancellationToken cancellationToken)
=> ValueTask.FromResult(new UserLoginResponse (true));
}
var response = await space.Send<UserLoginResponse>(new UserLoginRequest("demo"));
/*
A more efficient solution can be achieved in the Send method by utilizing both the request and response types.
*/
var response = await space.Send<UserLoginRequest, UserLoginResponse>(new UserLoginRequest("demo"))public class PricingHandlers
{
[Handle(Name = "Default")] public ValueTask<PriceResult> GetDefault(HandlerContext<PriceQuery> ctx) => ...;
[Handle(Name = "Discounted")] public ValueTask<PriceResult> GetDiscounted(HandlerContext<PriceQuery> ctx) => ...;
}
var discounted = await space.Send<PriceResult>(new PriceQuery(...), handlerName: "Discounted");public class LoggingPipeline
{
[Pipeline(Order = 100)]
public async ValueTask<TResponse> Log<TRequest, TResponse>(PipelineContext<TRequest> ctx)
{
// pre-execution
var result = await ctx.Next(ctx);
// post-execution
return result;
}
}public sealed record UserLoggedIn(string UserName);
public class LoginNotifications
{
[Notification]
public ValueTask Log(NotificationContext<UserLoggedIn> ctx) => ValueTask.CompletedTask;
}
await space.Publish(new UserLoggedIn("demo"));public class UserQueries
{
[Handle]
[CacheModule(DurationSeconds = 60)]
public ValueTask<UserProfile?> GetUser(HandlerContext<UserId> ctx) => ...;
}[CacheModule] (from Space.Modules.InMemoryCache) inserts caching logic before user pipelines.
Space supports handlers, pipelines, notifications, and modules across multiple projects through a root aggregator model. This enables modular solutions where feature libraries can contain their own handlers without manual DI wiring.
Set exactly one project as the root aggregator (typically your host/composition root):
<PropertyGroup>
<SpaceGenerateRootAggregator>true</SpaceGenerateRootAggregator>
</PropertyGroup>All other handler libraries should either omit this property or set it to false:
<PropertyGroup>
<SpaceGenerateRootAggregator>false</SpaceGenerateRootAggregator>
</PropertyGroup>- Root project generates
DependencyInjectionExtensions.g.cswith automatic assembly discovery - Satellite libraries generate lightweight
SpaceAssemblyRegistration_<Assembly>.g.csfiles - At runtime, the root automatically discovers and registers handlers from all referenced assemblies
/MySolution
src/AppHost/ (root: SpaceGenerateRootAggregator=true)
src/Features/Users/ (satellite: handlers, pipelines)
src/Features/Billing/ (satellite: handlers, modules)
src/Infrastructure/ (satellite: notifications)
For complete details, migration guidance, and troubleshooting, see MultiProjectSetup.md.
- Zero runtime reflection for discovery (Roslyn source generator)
- Minimal boilerplate: annotate methods directly with
[Handle],[Pipeline],[Notification] - Named handlers (multiple strategies for same request/response)
- Orderable pipelines + early system module execution
- Extensible module model (e.g., cache) before user pipelines
- High-performance async signatures (
ValueTask) - Parallel or sequential notification dispatch
- Multi-targeting (netstandard2.0 + modern .NET)
Space front-loads cost at build time to reduce runtime overhead:
- Compile-time metadata (registrations, maps)
- No reflection-based runtime scanning
- Low allocation pathways (current & planned pooling)
Benchmarks (planned) will compare against other mediator libraries.
Primary docs in docs/:
| Topic | Link |
|---|---|
| Project Overview | ProjectDoc.en.md |
| Handlers | Handlers |
| Pipelines | Pipelines |
| Notifications | Notifications |
| Modules | Modules |
| Multi-Project Setup | MultiProjectSetup |
| Developer Recommendations | DeveloperRecommendations |
| Known Issues | KnownIssues |
| Planned Improvements | PlannedImprovements |
Per-package:
See GitHub Issues for:
- Planned improvements (attribute parameters, global defaults, Options pattern)
- Known issues (initial Lazy
ISpacenull, module scoping on named handlers)
Contributions welcome via issues & PRs.
master: tagged semantic versions (vX.Y.Z) ? stable NuGetdev: continuous preview (X.Y.(Patch+1)-preview.<run>)- Feature branches: validation build only
MIT.
APIs may evolve while early preview features stabilize. Track releases for changes.