Orbitra 1.0.0

dotnet add package Orbitra --version 1.0.0
                    
NuGet\Install-Package Orbitra -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Orbitra" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Orbitra" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Orbitra" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Orbitra --version 1.0.0
                    
#r "nuget: Orbitra, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Orbitra@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Orbitra&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Orbitra&version=1.0.0
                    
Install as a Cake Tool

Orbitra (Core)

License: Apache-2.0 NuGet - Orbitra

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:

  1. A place to register handlers
  2. A way to register open generic pipeline behaviours
  3. 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

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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