Orbitra 1.0.0
dotnet add package Orbitra --version 1.0.0
NuGet\Install-Package Orbitra -Version 1.0.0
<PackageReference Include="Orbitra" Version="1.0.0" />
<PackageVersion Include="Orbitra" Version="1.0.0" />
<PackageReference Include="Orbitra" />
paket add Orbitra --version 1.0.0
#r "nuget: Orbitra, 1.0.0"
#:package Orbitra@1.0.0
#addin nuget:?package=Orbitra&version=1.0.0
#tool nuget:?package=Orbitra&version=1.0.0
Orbitra (Core)
Orbitra.Core is the heart of the Orbitra ecosystem.
It provides:
- A source-generated dispatcher — zero reflection, zero runtime scanning
- The pipeline engine for cross-cutting behaviours
- Container-agnostic abstractions for wiring pipeline behaviours into any service provider
Orbitra is designed for performance, predictability, and type-safety — ideal for minimal APIs, ASP.NET Core, background services, console apps, games, and environments where reflection is not allowed or too slow.
What’s Included in Orbitra (Core)
| Capabilities | Provided by Core |
|---|---|
| Source-generated dispatcher | ✅ |
| Pipeline behaviour engine | ✅ |
| Request & handler abstractions | ✅ (Orbitra.Abstractions) |
| Container integration | ❌ Bring your own or use other Orbitra packages |
If you're integrating with Microsoft’s DI container, see
Orbitra.DependencyInjection.Microsoft (separate package).
Install
dotnet add package Orbitra
Core Concepts
Requests
All requests implement IRequest<TResult> (or markers ICommand<TResult> / IQuery<TResult>).
Handlers
Handlers implement IRequestHandler<TRequest, TResult> or (ICommandHandler<TRequest, TResult> / IQueryHandler<TRequest, TResult>).
Dispatch
Concrete implementation of IDispatcher is generated at compile time and routes requests directly to handlers with no reflection.
Pipelines
Pipelines are chains of cross-cutting behaviours that wrap handler execution:
- Logging
- Validation
- Metrics
- Feature flags
- Rate limiting
- Tracing
- Auditing
- …anything
Behaviours implement:
public interface IPipelineBehaviour<TRequest, TResult>
where TRequest : IRequest<TResult>
{
Task<TResult> HandleAsync(
TRequest request,
DispatchHandlerDelegate<TResult> next,
CancellationToken token = default);
}
Orbitra applies pipelines in order, like middleware — but type-safe, zero reflection, and fully controlled via fluent rules (EnabledWhen, Include, Exclude, When, Unless, OtherwiseAll).
Using Orbitra Without Any External DI Container
Orbitra.Core is container-agnostic.
All you need is:
- A place to register handlers
- A way to register open generic pipeline behaviours
- A service provider capable of resolving both
To support this, Orbitra exposes:
/// Minimal abstraction implemented by each container adapter
public interface IServiceRegistrator
{
void AddOpenGeneric(Type serviceOpenGeneric, Type implementation, TypeLifetime lifetime);
}
Example — Tiny custom container
This shows how to integrate Orbitra.Core without Microsoft.Extensions.DependencyInjection:
using System.Collections.Concurrent;
using Orbitra.Abstractions.Pipelines;
using Orbitra.Abstractions.Requests;
using Orbitra.Core;
using Orbitra.Core.Pipelines.Abstractions;
internal sealed class ServiceContainer : IServiceProvider, IServiceRegistrator
{
private readonly ConcurrentDictionary<Type, object> singletons = [];
private readonly List<Type> _behaviourFamilies = [];
public void AddOpenGeneric(Type serviceOpenGeneric, Type implementation, TypeLifetime lifetime)
{
if (!implementation.IsGenericTypeDefinition)
{
throw new InvalidOperationException("");
}
_behaviourFamilies.Add(implementation);
}
public void AddHandler<TReq, TRes>(IRequestHandler<TReq, TRes> handler)
where TReq : IRequest<TRes>
{
singletons[typeof(IRequestHandler<TReq, TRes>)] = handler;
}
public object? GetService(Type serviceType)
{
// Handle IEnumerable<...>
if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
var elem = serviceType.GetGenericArguments()[0];
if (elem.IsGenericType && elem.GetGenericTypeDefinition() == typeof(IPipelineBehaviour<,>))
{
var genericArgs = elem.GetGenericArguments(); // [TReq, TRes]
// Create array of correct type
var resultArray = Array.CreateInstance(elem, _behaviourFamilies.Count);
for (int i = 0; i < _behaviourFamilies.Count; i++)
{
var openGeneric = _behaviourFamilies[i]; // t.ex. WritelinePipelineBehaviour<,>
var closed = openGeneric.MakeGenericType(genericArgs); // WritelinePipelineBehaviour<TReq, TRes>
var instance = Activator.CreateInstance(closed)
?? throw new InvalidOperationException($"Could not create instance of {closed.FullName}.");
resultArray.SetValue(instance, i);
}
return resultArray;
}
}
// Handlers
return singletons.TryGetValue(serviceType, out var existing)
? existing
: null;
}
}
Putting it together
var container = new ServiceContainer();
// 1. Register handler manually
container.AddHandler(
new GetUserQueryHandler()
);
// 2. Create registry
var registry = new PipelineBehaviourRegistry();
// 3. Create DispatcherConfigurator by injecting the implementation of IServiceProvider, i.e ServiceContainer, and the registry.
// you can then start chaining your pipeline behaviours
var _ = new DispatcherConfigurator(container, registry)
.AddPipelineBehaviour(typeof(WritelinePipelineBehaviour<,>))
.WhenRequest<GetUserQuery>(static req => req.Id > 10);
// 4. Create dispatcher by injecting the implementation of IServiceRegistrator, i.e ServiceContainer, and the registry
var dispatcher = new Dispatcher(container, registry);
// 5. Dispatch a request
var result = await dispatcher.DispatchAsync(new GetUserQuery(42));
- Works in console apps
- Works in workers
- Works without DI
- Shows how containers plug into pipeline engine
Example: What a behaviour looks like
public sealed class WritelinePipelineBehaviour<TReq, TRes>
: IPipelineBehaviour<TReq, TRes>
where TReq : IRequest<TRes>
{
public async Task<TResult> HandleAsync(
TRequest request,
DispatchHandlerDelegate<TResult> next,
CancellationToken token = default)
{
Console.WriteLine($"Handling {typeof(TReq).Name}");
var result = await next();
Console.WriteLine($"Handled {typeof(TReq).Name}");
return result;
}
}
Pipeline Rules in Core
All rule primitives are part of Core:
| Rule | Meaning |
|---|---|
EnabledWhen(predicate) |
Enable / disable entire chain. If this is false, nothing in that chain runs regardless of other rules. |
Include<TRequest>() / Include(typeof(...), ...) |
Run only for selected requests |
Exclude<TRequest>() / Exclude(typeof(...), ...) |
Run for all except selected requests |
IncludeCommands() / IncludeQueries() |
Category-based includes |
ExcludeCommands() / ExcludeQueries() |
Category-based excludes |
When(predicate) |
Conditional activation |
Unless(predicate) |
Conditional skip |
And extensions:
| Extension | Use case |
|----------------------|----------|
| WhenRequestIs<T> | Run only for one specific type. |
| WhenRequest<T> | Run only for one type when the predicate is true. |
| UnlessRequestIs<T> | Run for all except this one type. |
| UnlessRequest<T> | Run for all except when request is this type and predicate is true. |
| OtherwiseAll | Fallback to run on everything |
These work identically whether using a custom container or Microsoft DI.
Predicates receive (Type requestType) => bool, (Type requestType, object request, IServiceProvider services) => bool or a simplified _ => bool, and async variances of these as well.
Minimal DI — how Microsoft’s adapter implements it
For comparison, this is what the Microsoft adapter does:
public sealed class MsDiServiceRegistrator(IServiceCollection services) : IServiceRegistrator
{
public void AddOpenGeneric(Type serviceOpenGeneric, Type implementation, TypeLifetime lifetime)
{
services.Add(
new ServiceDescriptor(
serviceOpenGeneric,
implementation,
MapLifetime(lifetime)
)
);
}
private static ServiceLifetime MapLifetime(TypeLifetime lifetime) =>
lifetime switch
{
TypeLifetime.Scoped => ServiceLifetime.Scoped,
TypeLifetime.Transient => ServiceLifetime.Transient,
TypeLifetime.Singleton => ServiceLifetime.Singleton,
_ => ServiceLifetime.Singleton
};
}
This is all that’s required to integrate Orbitra.Core with any DI container.
Performance
Because dispatching is source-generated, Orbitra avoids:
- Reflection
- Assembly scanning
- Dynamic invocation
- Boxing
- Hidden allocations
This results in:
- 1.3–5× faster dispatch wihtout predicates (with multiple predicates on all Orbitra pipelines MediatR is slightly faster, which is expected since predicate evaluation has to be done on each request)
- Far lower memory usage (2–4× less) (regardless of evaluating predicates or not)
- Stable throughput at scale
Benchmarks are included in the repository under /docs/benchmarks.
When to Use Orbitra.Core Directly
You want Core if:
- You’re building your own DI adapter
- You’re on a restricted runtime (mobile, IoT, game engine)
- You want full control over lifecycle and resolution
- You don’t use Microsoft.Extensions.DependencyInjection
If you are using Microsoft.Extensions.DependencyInjection, the recommended package is:
- Orbitra.DependencyInjection.Microsoft
Related Packages
| Package | Description |
|---|---|
| Orbitra.Abstractions | Lightweight runtime contracts for requests, handlers, and pipeline behaviours |
| Orbitra.DependencyInjection.Microsoft | Extensions for Microsoft.Extensions.DependencyInjection for seamless registration. |
Contributing
PRs and issues welcome!
Before merging, contributors must include the line:
I have read and agree to the Orbitra CLA in CLA.md
In the PR description or a PR comment.
License
Code © 2025 Robin Hörnkvist
Licensed under Apache License 2.0 — see LICENSE
Third-party notices — see NOTICE
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Orbitra.Abstractions (>= 1.0.0)
-
net9.0
- Orbitra.Abstractions (>= 1.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Orbitra:
| Package | Downloads |
|---|---|
|
Orbitra.DependencyInjection.Microsoft
Source-generated DI extensions to integrate Orbitra into ASP.NET Core |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 192 | 10/31/2025 |