OdinCore 1.1.0

dotnet add package OdinCore --version 1.1.0
                    
NuGet\Install-Package OdinCore -Version 1.1.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="OdinCore" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OdinCore" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="OdinCore" />
                    
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 OdinCore --version 1.1.0
                    
#r "nuget: OdinCore, 1.1.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 OdinCore@1.1.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=OdinCore&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=OdinCore&version=1.1.0
                    
Install as a Cake Tool

OdinCore

OdinCore is a lightweight request-to-result pipeline for ASP.NET Core. It provides a simple handler model with built-in validation, dependency-injection auto-registration, caching, paging helpers, exception mapping, and HTTP result shaping — without requiring a mediator framework.

Built for real-world ASP.NET Core applications and now open source for the .NET community.

Features

  • Request → validation → execution → result pipeline
  • Synchronous and asynchronous handlers
  • CancellationToken support for async execution
  • DataAnnotations validation, including nested object graphs
  • Automatic handler registration through assembly scanning
  • Configurable DI lifetime per handler
  • Memory and distributed caching
  • Shared-cache helpers with get-or-create and invalidation support
  • Standard HTTP-oriented Result<T> model
  • ASP.NET Core IActionResult integration
  • Paging helpers for IQueryable<T>
  • Carry-over and running-total helpers for paged financial/reporting scenarios
  • Minimal external surface area

Requirements

  • .NET 10 or later
  • ASP.NET Core

Installation

Once the package is published to NuGet:

dotnet add package OdinCore

Or with Package Manager Console:

Install-Package OdinCore

Quick start

1. Create a request

using System.ComponentModel.DataAnnotations;

public sealed class CreateUserRequest
{
    [Required]
    public string Name { get; set; } = string.Empty;
}

2. Create a handler

using OdinCore;

public sealed class CreateUser : Odin<CreateUserRequest, int>
{
    protected override Result<int>? Execute()
    {
        // Your business logic
        var newUserId = 42;
        return OK(newUserId);
    }
}

For I/O work, prefer the asynchronous pipeline:

public sealed class GetUser : Odin<int, UserDto>
{
    protected override async Task<Result<UserDto>?> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        var user = await LoadUserAsync(request, cancellationToken);

        return user is null
            ? NotFound("User was not found.")
            : OK(user);
    }
}

3. Register handlers

using System.Reflection;
using OdinCore;

builder.Services.AddOdin(typeof(CreateUser).Assembly);

OdinCore scans the supplied assembly and automatically registers classes derived from Odin<,>.

Handlers are transient by default. You can override the lifetime:

using Microsoft.Extensions.DependencyInjection;
using OdinCore;

[ServiceLifetime(ServiceLifetime.Scoped)]
public sealed class CreateUser : Odin<CreateUserRequest, int>
{
    // ...
}

4. Use from an API endpoint

[HttpPost]
public async Task<IActionResult> Create(
    [FromBody] CreateUserRequest request,
    [FromServices] CreateUser handler,
    CancellationToken cancellationToken)
{
    return await handler.RunAsActionResultAsync(
        request,
        cancellationToken,
        this);
}

Pipeline

The async pipeline follows this order:

Validation
  ↓
Executing
  ↓
Cache lookup
  ↓
ExecuteAsync / Execute
  ↓
Cache save
  ↓
Executed
  ↓
OnSuccess / OnError
  ↓
Finally

Any stage that returns a result can short-circuit the remaining execution stages.

Main extension points

protected virtual Cache? EnableCaching();
protected virtual Result<TResponse>? Validation();
protected virtual Result<TResponse>? Executing();
protected virtual Result<TResponse>? Execute();
protected virtual Task<Result<TResponse>?> ExecuteAsync();
protected virtual Task<Result<TResponse>?> ExecuteAsync(CancellationToken cancellationToken);
protected virtual Result<TResponse> Executed(Result<TResponse>? result);
protected virtual void OnSuccess(Result<TResponse> result);
protected virtual void OnError(Result<TResponse> result);
protected virtual void Finally(Result<TResponse> result);
protected virtual Result<TResponse> UnhandledException(Exception ex);

Result model

OdinCore maps result status codes to HTTP responses.

Common helpers available inside handlers include:

return OK(data);
return OKMessage("Saved successfully.");
return BadRequest("Invalid request.");
return Forbidden("Access denied.");
return NotFound("Resource not found.");
return UnprocessableContent("Unable to process request.");
return BadGateway("Upstream service failed.");

Result<T> exposes the response data and errors while success is determined by the HTTP status range.

Validation

Requests are validated automatically using DataAnnotations before execution.

public sealed class RegisterRequest
{
    [Required]
    [EmailAddress]
    public string Email { get; set; } = string.Empty;
}

Nested request objects and collections are validated as an object graph.

You can also call Validate(model) from a handler when validating another object.

Caching

Caching is opt-in per handler by overriding EnableCaching().

Memory cache

public sealed class GetProduct : Odin<int, ProductDto>
{
    private readonly IMemoryCache _cache;

    public GetProduct(IMemoryCache cache)
    {
        _cache = cache;
    }

    protected override Cache? EnableCaching()
        => new Cache(
            _cache,
            $"product:{request}",
            TimeSpan.FromMinutes(5));
}

Distributed cache

protected override Cache? EnableCaching()
    => new Cache(
        _distributedCache,
        $"product:{request}",
        TimeSpan.FromMinutes(5));

Use sliding: true when sliding expiration is preferred.

Shared cache data

The same Cache abstraction can also be used outside the handler-result pipeline for data shared by multiple handlers:

var cache = new Cache(
    memoryCache,
    $"coding:{databaseId}",
    TimeSpan.FromHours(2));

var coding = cache.GetOrCreate(() => LoadCoding(databaseId));

Async factories are supported as well:

var coding = await cache.GetOrCreateAsync(
    ct => LoadCodingAsync(databaseId, ct),
    cancellationToken);

Invalidate the shared entry after a successful mutation:

cache.Remove();
// or
await cache.RemoveAsync(cancellationToken);

These helpers work with both memory and distributed cache providers.

Paging

Implement IPagedRequest:

using OdinCore.Tools;

public sealed class ProductListRequest : IPagedRequest
{
    public int? page { get; set; }
    public int? pageSize { get; set; }
}

Then page an IQueryable<T>:

var result = query.ToPaged(request);

The result contains page metadata and the current page items.

For financial/reporting scenarios, OdinCore also supports brought-forward and total sums through paging selectors and carry-over helpers.

Custom application base handler

Large applications often benefit from a project-specific base class:

public abstract class AppHandler<TRequest, TResponse>
    : Odin<TRequest, TResponse>
{
    protected override Result<TResponse> UnhandledException(Exception ex)
    {
        // Application logging / telemetry
        return base.UnhandledException(ex);
    }
}

Application handlers can then inherit AppHandler<,> while OdinCore remains independent from domain code.

Building locally

git clone https://github.com/Amindada021/OdinCore.git
cd OdinCore
dotnet restore
dotnet build -c Release
dotnet pack -c Release

NuGet

NuGet package ID:

OdinCore

Project repository:

https://github.com/Amindada021/OdinCore

Contributing

Ideas, bug reports, documentation improvements, performance work, tests, and pull requests are welcome.

Please read CONTRIBUTING.md before opening a pull request.

If you have an idea but are not ready to implement it, open a GitHub issue and describe the use case. Discussion around API design and real-world usage is especially welcome.

Philosophy

OdinCore aims to stay small and understandable. New features should solve recurring application concerns without turning the library into a full application framework.

The project values:

  • predictable behavior
  • small APIs
  • low ceremony
  • async-first application code
  • backward compatibility
  • measurable performance
  • useful community feedback

License

OdinCore is open source under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 836 9/15/2026
1.0.0 148 9/11/2026