RestLib.EntityFrameworkCore 3.0.0

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

RestLib

3 lines to a consistent CRUD REST API

Build Coverage NuGet License

RestLib is a .NET 10 library for ASP.NET Core Minimal APIs that generates CRUD endpoints from your model and repository. It bakes in secure defaults, cursor-based pagination APIs, filtering, sorting, field selection, HATEOAS hypermedia links, OpenAPI metadata, and RFC 9457 Problem Details so you can ship consistent APIs faster. Some capabilities depend on the repository adapter and may have implementation-specific limits.

Table of Contents

Install

Install the core package:

dotnet add package RestLib

For demos, tests, and quick prototypes, add the optional in-memory adapter:

dotnet add package RestLib.InMemory

For production use with Entity Framework Core:

dotnet add package RestLib.EntityFrameworkCore

Quick Start

Create a new app:

dotnet new web -n MyApi
cd MyApi

Define a model:

public class Product
{
    public Guid Id { get; set; }
    public required string Name { get; set; }
    public decimal Price { get; set; }
}

Configure RestLib:

using RestLib;
using RestLib.InMemory;
using Scalar.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRestLib();
builder.Services.AddRestLibInMemory<Product, Guid>(p => p.Id, Guid.NewGuid);
builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();
app.MapScalarApiReference();

app.MapRestLib<Product, Guid>("/api/products", config =>
{
    config.AllowAnonymous();
});

app.Run();

AddRestLib is idempotent. The first successful call defines the application's global RestLib and JSON settings; later calls are no-ops and do not run their configuration delegates. Keep all global RestLib settings in the first call, even when application startup is split across modules.

ASP.NET Core's JsonOptions.SerializerOptions instance is also RestLib's canonical serializer configuration. Request binding, response writing, PATCH preview and persistence, field selection, the standard InMemory and EF Core registrations, and the default ETag generator therefore share the same naming policy, case sensitivity, metadata resolver, converters, and number-handling rules. Use ConfigureHttpJsonOptions after AddRestLib when an application needs to extend or override the RestLib defaults:

builder.Services.AddRestLib();
builder.Services.ConfigureHttpJsonOptions(httpJson =>
{
    httpJson.SerializerOptions.PropertyNameCaseInsensitive = false;
    httpJson.SerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
    httpJson.SerializerOptions.Converters.Add(
        new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower));
});

Standard AddRestLibInMemory registrations resolve that canonical instance lazily, so they can appear before or after AddRestLib. The explicitly named WithOptions InMemory overloads remain an intentional repository-local override for callers that need different PATCH rules.

Run the app and open the API reference at /scalar:

dotnet run

That gives you:

  • GET /api/products - list all with cursor pagination
  • GET /api/products/{id} - fetch a single resource
  • POST /api/products - create
  • PUT /api/products/{id} - replace
  • PATCH /api/products/{id} - partially update
  • DELETE /api/products/{id} - delete

PATCH applies JSON Merge Patch semantics (RFC 7396) to the resource's serialized representation. Nested objects are merged recursively, null removes the named member, and arrays or non-object values replace the previous value. Member names come from the effective System.Text.Json contract, so naming policies, [JsonPropertyName], metadata resolvers, ignored members, member converters, and number-handling rules are consistent across preview, InMemory, and EF Core persistence. Canonical JSON names are authoritative. For backward compatibility, legacy CLR, snake_case, and camelCase aliases are accepted only when PropertyNameCaseInsensitive is enabled. JSON number tokens are preserved without conversion through double. Because RestLib endpoints expose typed object resources, the root patch document must be a JSON object.

Quick Start (folder convention)

For JSON-driven resources, the recommended path is one file per resource under Models/.

Program.cs:

using RestLib;
using RestLib.InMemory;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRestLib();
builder.Services.AddRestLibInMemory<Product, Guid>(p => p.Id, Guid.NewGuid);
builder.Services.AddRestLibFromFolder("Models");

var app = builder.Build();
app.MapJsonResources();
app.Run();

Single-file variant:

builder.Services.AddJsonResourceFromFile<Product, Guid>("Models/Products.json");

Two-model single-file variant:

builder.Services.AddJsonResourceFromFile<ProductDto, ProductEntity, Guid>("Models/Products.json");

Models/Products.json:

{
  "$schema": "https://raw.githubusercontent.com/Adrian01987/RestLib/main/schemas/restlib-resource.schema.json",
  "EntityType": "Product, MyApi",
  "Name": "products",
  "Route": "/api/products",
  "AllowAnonymousAll": true,
  "Validation": {
    "Name": { "Required": true },
    "Price": { "Min": 0.01 }
  }
}

See docs/guides/json-resources.md for the full walkthrough.

Separate API and DB models

When you want to expose a DTO but persist a different model, register a mapper and use the three-type overload:

builder.Services.AddRestLibMapper<ProductDto, ProductEntity, ProductMapper>();

app.MapRestLib<ProductDto, ProductEntity, Guid>("/api/products", config =>
{
    config.AllowAnonymous();
    config.AllowFiltering(p => p.CategoryId);
    config.AllowSorting(p => p.Name, p => p.Price);
    config.AllowFieldSelection(p => p.Id, p => p.Name, p => p.Price);
    config.AllowSearch(p => p.Name, p => p.Description);
});

JSON resources can declare the same pattern with a Mapping section:

{
  "EntityType": "ProductDto, MyApi",
  "Name": "products",
  "Route": "/api/products",
  "Mapping": {
    "DbType": "ProductEntity, MyApi",
    "Mapper": "ProductMapper"
  },
  "Filtering": ["CategoryId"],
  "Sorting": ["Name", "Price"],
  "FieldSelection": ["Id", "Name", "Price"],
  "Search": ["Name", "Description"]
}

RestLib can opt resources into a simple OR-of-contains search across configured string properties. The default query parameter is q, and JSON resources support the same feature with configurable search options.

Search is intentionally limited to lightweight collection queries rather than full-text indexing, ranking, or fuzzy matching. Terms are literal and case-insensitive by default; EF Core Unicode and case-sensitive behavior ultimately follows the configured provider and database collation. For full fluent and JSON examples, nested-path usage, and the strict Mapping.Auto shortcut, see docs/guides/query-features.md.

Composite keys

RestLib supports ordered two-part composite keys through RestLibCompositeKey<TFirst, TSecond> for both fluent and JSON-backed resources. Item routes use two path segments such as /api/tenant-products/{tenantId}/{sku}.

Item-route and batch-envelope keys are authoritative for PUT updates, and resource-key fields are immutable under JSON Merge Patch. This identity rule also applies to scalar alternate keys. InMemory resources whose key type does not implement a natural total order must register an IComparer<TKey> so collection ordering and pagination tie-breaking remain deterministic.

For complete fluent setup, JSON Key examples, and related query behavior, see docs/guides/query-features.md.

EF Core Quick Start

For a database-backed path, register your DbContext, call AddRestLibEfCore<AppDbContext, TEntity, TKey>(), and map the same RestLib endpoints over your EF Core model. RestLib handles endpoint wiring and repository behavior, while your application continues to own schema creation and migrations.

The sample app uses EnsureCreated() as a zero-setup demo path, but production EF Core apps should use normal migrations and Database.Migrate(). For the full setup and migration workflow, see docs/guides/ef-core-migrations.md.

Why RestLib

Every backend project starts the same way: define a model, write CRUD endpoints, add validation, handle errors, set up pagination, wire Swagger, and repeat for every entity.

RestLib removes that repetition while keeping the parts that matter explicit:

  • Proper REST semantics inspired by the Zalando REST API Guidelines
  • Secure-by-default endpoints with per-operation opt-out
  • Machine-readable RFC 9457 Problem Details responses
  • Opt-in HATEOAS hypermedia links (Richardson Maturity Model Level 3)
  • Hook-based extensibility instead of controller inheritance
  • OpenAPI metadata and package-ready defaults out of the box

Choosing RestLib

RestLib is a good fit when you want many resource-oriented endpoints to share one well-documented API contract without hand-writing the same CRUD, pagination, validation, OpenAPI, and error-handling code repeatedly.

Scenario Recommended path
Prototype, demo, or test fixture RestLib.InMemory with fluent endpoint configuration
Typical database-backed API RestLib.EntityFrameworkCore over your existing DbContext
Configuration-heavy resource catalog JSON resources loaded from Models/ or appsettings.json
Public DTO differs from persistence model Two-model resources plus an explicit IRestLibMapper
Workflow, orchestration, or cross-resource transaction Custom Minimal API endpoint beside generated RestLib resources
Provider-specific search, deep authorization scoping, or complex query semantics Custom repository or custom endpoint

Prefer custom endpoints for commands that are not naturally resource CRUD: checkout, payment capture, state-machine transitions, report generation, and other workflows where the route should express a business action instead of exposing a generic persistence operation. Generated RestLib endpoints can still coexist with those custom routes.

Features

Secure by Default

All endpoints require authorization unless you opt out per operation:

app.MapRestLib<Product, Guid>("/api/products", config =>
{
    config.AllowAnonymous(RestLibOperation.GetAll, RestLibOperation.GetById);
    config.RequirePolicy(RestLibOperation.Delete, "AdminOnly");
});

Standards-Compliant Responses

RestLib follows the Zalando REST API Guidelines and uses RFC 9457 Problem Details for error payloads.

  • snake_case JSON properties
  • Cursor-based pagination API (forward-only by design; the EF Core adapter uses keyset cursors for supported stable sorts and falls back to encoded offsets otherwise — see ADR-001)
  • Structured validation and error responses
  • Consistent HTTP status codes
{
  "type": "/problems/not-found",
  "title": "Resource Not Found",
  "status": 404,
  "detail": "Product with ID '999' does not exist.",
  "instance": "/api/products/999"
}

Advanced Filtering

RestLib supports allow-listed query-string filtering for scalar properties and nested reference-property paths. Equality filters use direct query parameters, while range, string, and membership operators use bracket syntax such as price[gte] and name[contains]. Relational operators use a portable numeric/DateTime type baseline, and partial-string values are literal and case-insensitive rather than SQL patterns.

For complete fluent setup, operator coverage, nested-path examples, and JSON equivalents, see docs/guides/query-features.md.

Sorting

Sorting uses an allow-list of sortable properties with optional default ordering. Query names use snake_case, directions are asc or desc, and nested reference-property paths use dotted names such as customer.name.

For fluent examples, multi-sort patterns, nested-path rules, and JSON configuration, see docs/guides/query-features.md.

Rate Limiting

Integrate with ASP.NET Core's rate limiting middleware to throttle requests per operation:

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("read-policy", o => { o.PermitLimit = 100; o.Window = TimeSpan.FromMinutes(1); });
    options.AddFixedWindowLimiter("write-policy", o => { o.PermitLimit = 20; o.Window = TimeSpan.FromMinutes(1); });
});

app.UseRateLimiter();

app.MapRestLib<Product, Guid>("/api/products", config =>
{
    config.UseRateLimiting("read-policy", RestLibOperation.GetAll, RestLibOperation.GetById);
    config.UseRateLimiting("write-policy", RestLibOperation.Create, RestLibOperation.Update);
});

Set a default policy and exempt specific operations:

config.UseRateLimiting("default-policy");
config.DisableRateLimiting(RestLibOperation.GetById);

Rate limiting is opt-in. RestLib applies the named policy to endpoints; the application defines and registers the actual policies.

All enabled batch actions share POST /batch. Authorization is evaluated after the envelope action is parsed, so BatchCreate, BatchUpdate, BatchPatch, and BatchDelete can use different authorization policies. Rate limiting remains ASP.NET Core endpoint metadata and therefore must be identical for every batch action enabled on the shared route. Endpoint mapping fails fast when enabled batch actions resolve to different rate-limit policies or exemptions; enable a single batch action when it needs its own rate-limit policy.

Field Selection

Field selection lets clients request sparse fieldsets such as ?fields=id,name,price instead of returning the full entity every time. Unknown or disallowed fields return a 400 Problem Details response, and nested reference-property paths are supported with snake_case dotted query names.

Configured query aliases remain the request vocabulary, while projected response keys use the canonical paths from the effective JSON contract. This includes names supplied by [JsonPropertyName] or a metadata resolver, and sparse member values honor member-level and option-registered converters. Explicitly allow-listing a field is also an explicit decision to expose it: a selected member is returned even when normal full-object serialization would omit it through an ignore rule, a default/null omission rule, or a null intermediate value. Converter-owned object representations remain authoritative because their JSON shape cannot be reconstructed safely from CLR members.

For full examples, JSON configuration, nested sparse output behavior, and interactions with filtering/sorting/pagination, see docs/guides/query-features.md.

Nested object responses (opt-in)

Sparse field selection defaults to flat dotted keys for backward compatibility, but resources can opt into rebuilt nested objects when that response shape is a better fit for clients. The configured shape is stable regardless of how many fields are selected or which internal projection strategy is used.

For the fluent and JSON opt-in shapes plus concrete examples of both response modes, see docs/guides/query-features.md.

Batch Operations

Batch endpoints support create, update, patch, and delete actions over multiple resources in one request. Responses report per-item status, returning 200 only when every item succeeds and 207 Multi-Status when one or more items fail (including when all items fail). Once the request envelope and its non-empty items array are accepted, the response contains one entry per array member in original order. A member that cannot be decoded for the requested action receives an indexed 400 /problems/bad-request result while valid siblings continue; malformed JSON or an invalid envelope still receives one top-level 400 response. Update and patch omissions are associated by resource key rather than by shifted list position. RestLib validates custom bulk-repository results before after-persist processing and sends unsafe associations through per-item error handling, which defaults to internal failures, without retrying a write that may already have committed.

For request examples, batch limits, and hook/validation behavior, see docs/guides/extensibility-and-operations.md.

Enable HATEOAS to add CRUD-aware _links metadata to entity responses. RestLib can also merge in custom link relations through IHateoasLinkProvider<TEntity, TKey> when you want to point clients at related resources. HATEOAS-enabled collections require either a conventional Id property of the resource key type or an explicit config.KeySelector; link enrichment never removes an entity from a collection response.

Generated pagination and built-in HATEOAS URLs, and create Location headers, include ASP.NET Core's normalized Request.PathBase. When hosting behind a reverse proxy, configure forwarded-header middleware for trusted proxies before routing so Scheme, Host, and PathBase represent the public URL; restrict forwarded hosts with AllowedHosts. RestLib does not inspect raw X-Forwarded-* headers, and custom link-provider URLs remain application-owned.

For full response examples and custom link-provider registration, see docs/guides/extensibility-and-operations.md.

Select Operations

RestLib lets you expose only the CRUD operations you want and combine generated endpoints with custom routes when needed. The same resource surface can be declared fluently, loaded from JSON files, or bound from appsettings.json.

For full examples covering operation allow-lists, JSON registration, the new UnifiedTypeResolver, and two-model folder loading, see docs/guides/extensibility-and-operations.md.

Extensible via Hooks

Hooks provide strongly typed pipeline customization without controller inheritance or framework subclassing. JSON can select named hooks per operation while C# keeps the actual behavior implementation and test surface.

For fluent and named-hook examples, see docs/guides/extensibility-and-operations.md.

Persistence-Agnostic

RestLib stays persistence-agnostic: use the in-memory adapter, the EF Core adapter, or your own repository implementation behind the same endpoint surface.

Custom adapters can report a client-correctable PATCH document through the core PatchValidationException contract. Its message is returned as validation detail for direct and individually processed PATCH operations, so it must be safe to disclose. Both built-in adapters use this boundary for adapter-detected invalid or immutable fields.

For a custom repository example and guidance on where the EF Core adapter fits, see docs/guides/extensibility-and-operations.md.

EF Core Adapter

The official EF Core adapter wires RestLib over a DbContext without changing your EF Core model ownership. It supports filtering, sorting, counting, pagination, batch operations, and conditional field-selection projection pushdown, with some adapter-specific limits.

For registration examples and the broader adapter overview, see docs/guides/extensibility-and-operations.md. For production migration workflow, see docs/guides/ef-core-migrations.md.

Current EF Core Adapter Limitations
  • At most two key parts - the adapter supports scalar keys and ordered two-part composite keys via RestLibCompositeKey<TFirst, TSecond>. Keys with more than two parts are not supported.
  • Keyset pagination with offset fallback - the EF Core adapter uses last-seen sort values plus the key for supported stable sorts, but falls back to encoded offset cursors for unsupported sort shapes.
  • Projection pushdown is opt-in and conditional - when enabled, EF Core pushes down direct scalar field selections and still includes key/filter/sort columns in SQL. Nested field selections currently fall back to post-fetch projection, and any non-projectable selection still falls back when HATEOAS, ETag, or hooks are active.
  • Nested query paths are reference-only - filtering, sorting, and field selection support dot-separated nested reference-property paths such as customer.email. Collection-valued paths are not supported. Field selection uses dotted output keys by default, while the Nested response opt-in consistently rebuilds nested objects. EF PATCH selects direct mapped entity properties, but applies recursive JSON Merge Patch semantics inside a mapped JSON/value-converted property.
  • Constraint mapping is provider-limited - database constraint classification still relies primarily on exception-message inspection and is not yet specialized per provider.

Use the adapter when you want the standard RestLib endpoint surface over a typical EF Core model, including two-part composite-key resources. Expect to write a custom repository if you need keys with more than two parts, deep/navigational query semantics, or SQL-level projection beyond direct scalar property selection.

Versioning

RestLib integrates with common ASP.NET Core versioning patterns via route groups, including URL prefixes and Asp.Versioning.Http when you need header, query-string, or media-type versioning.

For complete examples of URL-prefix versioning, prefix-less overloads, and Asp.Versioning.Http integration, see docs/guides/extensibility-and-operations.md.

URL prefix versioning
Prefix-less overload on a route group
With Asp.Versioning.Http

Performance

RestLib adds minimal overhead compared to hand-written Minimal APIs. In some cases, it is faster due to optimized code paths.

Operation Raw API RestLib Overhead Memory
GET by ID 170.5 us 217.4 us +27% +4%
GET all 313.4 us 271.7 us -13% +14%
POST 265.5 us 384.8 us +45% +13%
PUT 232.1 us 282.0 us +22% +13%

Benchmarks were run on .NET 10.0.5 with 100 seeded items on Linux (Ubuntu 24.04). Absolute times are higher than typical due to running without process priority elevation — focus on the relative overhead (Ratio column) rather than raw microseconds. Re-run with cd benchmarks/RestLib.Benchmarks && dotnet run -c Release to get numbers for your hardware.

<details> <summary>Full benchmark results</summary>

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Intel Core i3-8130U CPU 2.20GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.201
  [Host]     : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v3
  DefaultJob : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v3

| Method                                      | Categories     | Mean     | Error    | StdDev    | Median   | Ratio | RatioSD | Allocated | Alloc Ratio |
|-------------------------------------------- |--------------- |---------:|---------:|----------:|---------:|------:|--------:|----------:|------------:|
| 'Raw Minimal API - POST'                    | Create         | 265.5 us | 30.76 us |  89.74 us | 260.1 us |  1.12 |    0.56 |  12.46 KB |        1.00 |
| 'RestLib - POST'                            | Create         | 384.8 us | 71.82 us | 203.73 us | 326.7 us |  1.63 |    1.07 |  14.10 KB |        1.13 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'Raw Minimal API - GET all'                 | GetAll         | 313.4 us | 39.61 us | 111.73 us | 285.8 us |  1.12 |    0.54 |  16.74 KB |        1.00 |
| 'RestLib - GET all'                         | GetAll         | 271.7 us | 31.49 us |  92.86 us | 246.1 us |  0.97 |    0.46 |  19.05 KB |        1.14 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'RestLib - GET all (no fields)'             | GetAll_Fields  | 289.6 us | 41.97 us | 121.78 us | 261.4 us |  1.18 |    0.70 |  19.07 KB |        1.00 |
| 'RestLib - GET all (?fields=id,name)'       | GetAll_Fields  | 475.3 us | 51.86 us | 149.62 us | 448.8 us |  1.93 |    0.99 |  39.98 KB |        2.10 |
| 'RestLib - GET all (?fields=id,name,price)' | GetAll_Fields  | 563.9 us | 88.23 us | 255.96 us | 498.5 us |  2.29 |    1.42 |  43.43 KB |        2.28 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'Raw Minimal API - GET by ID'               | GetById        | 170.5 us | 26.94 us |  79.43 us | 149.6 us |  1.26 |    0.93 |   9.76 KB |        1.00 |
| 'RestLib - GET by ID'                       | GetById        | 217.4 us | 29.76 us |  82.47 us | 203.9 us |  1.61 |    1.07 |  10.13 KB |        1.04 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'RestLib - GET by ID (no fields)'           | GetById_Fields | 124.3 us | 14.85 us |  42.84 us | 112.1 us |  1.12 |    0.55 |  10.21 KB |        1.00 |
| 'RestLib - GET by ID (?fields=id,name)'     | GetById_Fields | 162.7 us | 16.38 us |  46.47 us | 149.7 us |  1.46 |    0.65 |  12.22 KB |        1.20 |
|                                             |                |          |          |           |          |       |         |           |             |
| 'Raw Minimal API - PUT'                     | Update         | 232.1 us | 24.69 us |  72.81 us | 221.1 us |  1.10 |    0.51 |  12.78 KB |        1.00 |
| 'RestLib - PUT'                             | Update         | 282.0 us | 37.88 us | 108.06 us | 258.5 us |  1.34 |    0.69 |  14.44 KB |        1.13 |

</details>

Learn More

Guides

Architecture Decisions

Key decisions are documented as Architecture Decision Records:

ADR Decision
ADR-001 Cursor-based pagination over offset
ADR-002 Authorization required by default
ADR-003 Minimal APIs over controllers
ADR-004 snake_case JSON naming
ADR-005 RFC 9457 Problem Details for errors
ADR-006 Operation allowlists and denylists
ADR-007 Hybrid field projection strategy
ADR-008 Batch operations with partial success
ADR-009 Allow-list sorting with default sort
ADR-010 API versioning via route groups
ADR-011 Query parameter filtering
ADR-012 Hook pipeline for extensibility
ADR-013 Filter operators beyond equality
ADR-014 ETag support for caching and concurrency
ADR-015 Data Annotation validation
ADR-016 JSON resource configuration
ADR-017 Rate limiting integration
ADR-018 PATCH JsonElement coupling acknowledgement
ADR-019 HATEOAS hypermedia links
ADR-020 Structured logging
ADR-021 EF Core repository adapter
ADR-022 Per-file JSON resources
ADR-023 JSON validation rules
ADR-024 Two-model resources and the mapping seam
ADR-025 Two-part composite key support
ADR-026 EF Core projection pushdown
ADR-027 Row-level security is application-owned
ADR-028 Unit of work is application-owned
ADR-030 Single-owner internal orchestration seams
ADR-031 Bounded EF planning, batch-key, and built-in mapping caches
ADR-032 Domain Problem Details are application-owned
ADR-033 InMemory concurrency, entity ownership, and cancellation

Packages

Package Description NuGet
RestLib Core library RestLib
RestLib.InMemory In-memory repository for testing and prototyping RestLib.InMemory
RestLib.EntityFrameworkCore EF Core repository adapter for production databases RestLib.EntityFrameworkCore

Requirements

  • .NET 10.0 or later
  • ASP.NET Core Minimal APIs

Known Limitations

  • Forward-only cursor pagination — cursors support forward traversal only; there is no backward/previous-page navigation.

  • Cursor contract, adapter-specific implementation — RestLib exposes an opaque cursor API. The InMemory adapter uses validated non-negative offsets/indexes. The EF Core adapter uses keyset cursors only for non-nullable scalar sorts with supported relational comparisons and falls back to validated offsets for nullable, enum, Boolean, or otherwise unsupported sort shapes.

  • Field selection pushdown is adapter-dependent — the core endpoint layer can project after retrieval for any repository. The EF Core adapter can opt into conditional SQL projection pushdown for direct scalar fields via EnableProjectionPushdown; nested selections and requests using HATEOAS, ETags, or hooks fall back to materialized projection.

  • Nested query paths are reference-only — filtering, sorting, and field selection support dotted nested reference-property paths such as customer.email, but collection-valued paths are not supported. Field selection returns dotted keys by default and can opt into a stable rebuilt nested-object response shape.

  • Built-in search is intentionally limited — RestLib supports configured OR-of-contains search across string fields, but it does not provide full-text indexing, ranking, fuzzy matching, or provider-specific search features.

  • Row-level scoping is application-owned — apply tenant/user scoping in EF Core global query filters, database policies, or custom repositories before generated endpoints query data.

  • Cross-resource transactions are application-owned — use custom endpoints or transactional repositories for workflows such as checkout, payment capture, or multi-resource state changes.

  • InMemory concurrency is shallow — concurrent repository calls are coordinated and batch storage commits are indivisible, but entities are retained and returned by reference. Mutable entity internals are not synchronized or cloned, and cancellation is cooperative around an atomic batch commit. See ADR-033.

  • No CORS configuration — RestLib does not configure CORS. If your API is consumed by browsers, add ASP.NET Core's built-in CORS middleware:

    builder.Services.AddCors(options =>
    {
        options.AddDefaultPolicy(policy => policy
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());
    });
    app.UseCors();
    

    See the ASP.NET Core CORS documentation for production-ready policies.

Contributing

Contributions are welcome. Read the contributing guide.

License

This project is licensed under the MIT License. See the license.

Acknowledgments

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.

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
3.0.0 94 8/10/2026
2.5.3 125 6/16/2026
2.5.2 111 5/27/2026
2.5.1 113 5/18/2026
2.5.0 118 5/13/2026
2.4.0 109 5/13/2026
2.3.0 117 4/16/2026