LabEG.NeedleCrud 0.5.1

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

LabEG.NeedleCrud

NuGet Downloads .NET

High-performance CRUD library for ASP.NET Core with advanced filtering, sorting, pagination, and eager loading through URL query parameters.

Installation

dotnet add package LabEG.NeedleCrud

Quick Start

1. Register Services

builder.Services.AddScoped(typeof(ICrudDbRepository<,,>), typeof(CrudDbRepository<,,>));
builder.Services.AddScoped(typeof(ICrudDbService<,,>), typeof(CrudDbService<,,>));

2. Add Middleware

app.UseNeedleCrudExceptionHandler();

3. Create Controller

[Route("api/books")]
public class BooksController(ICrudDbService<MyDbContext, Book, Guid> service)
    : CrudController<Book, Guid>(service)
{
}

Features

  • Complete CRUD operations - Create, Read, Update, Delete
  • Advanced filtering - filter=name~like~John,age~>=~18
  • Multi-field sorting - sort=name~asc,date~desc
  • Pagination with metadata - pageSize=20&pageNumber=1
  • Eager loading - graph={"author":null,"reviews":null}
  • High performance - Expression Trees, Span<T>, optimized queries
  • Multi-targeting - .NET 8.0, 9.0, 10.0

Usage Examples

Basic Operations

# Create
POST /api/books
{
  "title": "1984",
  "pageCount": 328
}

# Get all
GET /api/books

# Get by ID
GET /api/books/{id}

# Update
PUT /api/books/{id}
{
  "title": "Nineteen Eighty-Four",
  "pageCount": 328
}

# Delete
DELETE /api/books/{id}

Advanced Queries

# Filtering
GET /api/books/paged?filter=pageCount~>=~300,isAvailable~=~true

# Sorting
GET /api/books/paged?sort=title~asc,publishDate~desc

# Sort by navigation property (level 2) — by author name
GET /api/books/paged?sort=author.name~asc,title~asc

# Sort by navigation property (level 3) — reviews by book's author country
GET /api/reviews/paged?sort=book.author.country~asc,book.title~asc

# Pagination
GET /api/books/paged?pageSize=20&pageNumber=1

# Eager loading
GET /api/books/paged?graph={"author":null,"category":null}

# Filter by navigation property (level 2) — author's country
GET /api/books/paged?filter=author.country~=~UK

# Filter by navigation property (level 2) — category name
GET /api/books/paged?filter=category.name~=~Fiction

# Filter by navigation property (level 3) — reviews for books by a given author
GET /api/reviews/paged?filter=book.author.name~ilike~Tolkien

# Filter by navigation property (level 3) — loans for fiction books
GET /api/loans/paged?filter=book.category.name~=~Fiction&graph={"book":null,"user":null}

# Combined
GET /api/books/paged?pageSize=20&filter=pageCount~>=~300&sort=title~asc&graph={"author":null}

Filter Operators

Operator Description Example
= Equals status~=~Active
> Greater than price~>~100
>= Greater or equal rating~>=~4.5
< Less than stock~<~10
<= Less or equal age~<=~18
like Contains (case-sensitive) name~like~John
ilike Contains (case-insensitive) email~ilike~gmail

Entity Requirements

Entities must implement IEntity<TId>:

public class Book : IEntity<Guid>
{
    public Guid Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public int PageCount { get; set; }

    // Navigation properties for eager loading
    public Author? Author { get; set; }
    public ICollection<Review>? Reviews { get; set; }
}

Entity Design Guide

NeedleCrud, like GraphQL, exposes your entity model directly as the API surface. Every public property becomes filterable and sortable; every navigation property becomes a graph node for eager loading. Designing entities deliberately is therefore essential.

Key rules

# Rule Why
1 Implement IEntity<TId> with a simple PK (Guid or int) Required by the library; composite keys are not supported
2 Never expose sensitive fields as public properties All public properties appear in responses and are filterable
3 Declare explicit FK scalar properties (AuthorId) alongside navigations (Author?) NeedleCrud's nested filter/sort paths require navigable property names
4 Mark inverse navigation properties with [JsonIgnore] Prevents circular reference cycles during JSON serialisation
5 Use scalar types (string, int, bool, DateTime, …) for filtered/sorted fields Only scalar values are usable in filter/sort expressions
6 Keep the graph shallow (2–3 levels max) Each Include level adds a database JOIN
7 Initialise collection navigations: = [] Avoids null-reference exceptions in EF Core and serialisation
8 Use ICollection<T> for collection navigations, not List<T> EF Core convention; avoids unintentional List<T> coupling

Minimal example

public class Book : IEntity<Guid>
{
    public Guid Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public int PageCount { get; set; }

    // Explicit FK scalar + nullable navigation
    public Guid AuthorId { get; set; }
    public Author? Author { get; set; }

    public ICollection<Review> Reviews { get; set; } = [];
}

public class Author : IEntity<Guid>
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;

    [JsonIgnore]  // prevents circular reference: Author → Book → Author → …
    public ICollection<Book> Books { get; set; } = [];
}

Further reading

Topic Resource
EF Core data modelling EF Core – Creating and Configuring a Model
EF Core relationships EF Core – Relationships
EF Core performance EF Core – Performance
Circular references in JSON System.Text.Json – Preserve references
REST API design Azure Architecture – API design
GraphQL schema design (conceptual parallel) GraphQL – Best Practices

Custom Logic

Custom Controller

public class BooksController : CrudController<Book, Guid>
{
    public BooksController(ICrudDbService<LibraryDbContext, Book, Guid> service)
        : base(service) { }

    public override async Task<Book> Create([FromBody] Book entity)
    {
        // Add custom validation
        if (entity.PageCount <= 0)
            throw new ValidationException("Invalid page count");

        return await base.Create(entity);
    }

    // Add custom endpoints
    [HttpGet("bestsellers")]
    public async Task<Book[]> GetBestsellers()
    {
        var query = new PagedListQuery(
            pageSize: 10,
            filter: "rating~>=~4.5",
            sort: "soldCopies~desc"
        );
        var result = await Service.GetPaged(query);
        return result.Items;
    }
}

Custom Service

public class BookService : CrudDbService<LibraryDbContext, Book, Guid>
{
    public BookService(ICrudDbRepository<LibraryDbContext, Book, Guid> repository)
        : base(repository) { }

    public override async Task<Book> Create(Book entity)
    {
        entity.CreatedDate = DateTime.UtcNow;
        return await base.Create(entity);
    }
}

// Register custom service
builder.Services.AddScoped<ICrudDbService<LibraryDbContext, Book, Guid>, BookService>();

Architecture

CrudController<TEntity, TId>      ← REST API endpoints
    ↓
CrudDbService<TContext, T, TId>   ← Business logic
    ↓
CrudDbRepository<TContext, T, TId> ← Data access with EF Core

🔒 Security

NeedleCrud exposes all CRUD operations automatically. Use standard ASP.NET Core mechanisms to protect them — the library does not interfere with the auth pipeline.

Rate Limiting

Limit how many requests a single client can make using the built-in .NET rate limiter (no extra packages needed):

// Program.cs
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", opt =>
    {
        opt.Window = TimeSpan.FromMinutes(1);
        opt.PermitLimit = 100;   // 100 req/min per client
        opt.QueueLimit = 0;
    });

    options.OnRejected = async (context, ct) =>
    {
        context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
        await context.HttpContext.Response.WriteAsync("Too many requests.", ct);
    };
});

// ...
app.UseRateLimiter();

You can implement this policy yourself — use a fixed-window, sliding-window, token-bucket, or concurrency limiter depending on your needs. See the official docs for all available options.

Authentication & Authorization

NeedleCrud controllers are plain ASP.NET Core controllers — apply [Authorize] just like any other controller:

// Require any authenticated user
[Authorize]
[Route("api/books")]
public class BooksController(ICrudDbService<MyDbContext, Book, Guid> service)
    : CrudController<Book, Guid>(service) { }

// Require a specific role
[Authorize(Roles = "Admin,Librarian")]
[Route("api/loans")]
public class LoansController(ICrudDbService<MyDbContext, Loan, Guid> service)
    : CrudController<Loan, Guid>(service) { }

// Named authorization policy
[Authorize(Policy = "LibraryStaff")]
[Route("api/users")]
public class UsersController(ICrudDbService<MyDbContext, User, Guid> service)
    : CrudController<User, Guid>(service) { }

Mix anonymous reads with protected writes by overriding individual actions:

[Authorize]
[Route("api/books")]
public class BooksController(ICrudDbService<MyDbContext, Book, Guid> service)
    : CrudController<Book, Guid>(service)
{
    [AllowAnonymous]
    public override Task<Book[]> GetAll() => base.GetAll();

    [AllowAnonymous]
    public override Task<Book> GetById([FromRoute] Guid id) => base.GetById(id);

    [AllowAnonymous]
    public override Task<PagedList<Book>> GetPaged([FromQuery] PagedListQuery query)
        => base.GetPaged(query);

    // Create / Update / Delete inherit [Authorize] from the class
}

Public APIs — Quick Reference

Concern Recommended approach
Unauthenticated access [Authorize] on controller / action
Role-based access [Authorize(Roles = "...")]
Policy-based access [Authorize(Policy = "...")]
Brute-force / DDoS AddRateLimiter() + app.UseRateLimiter()
Large data exports Enforce pageSize limit in a custom service
HTTPS app.UseHttpsRedirection()
CORS AddCors() + app.UseCors()

Tip: See LabEG.NeedleCrud.Sample/Program.cs for ready-to-uncomment examples of rate limiting and JWT Bearer authentication.

Performance

  • Expression Trees - Compiled queries for filters and sorting
  • Span<T> - Zero-allocation string parsing
  • Optimized EF Core - Efficient database queries
  • See benchmarks for detailed metrics

Try It Out

Run the sample Docker container:

docker pull labeg/needlecrud-sample:latest
docker run -p 8080:8080 labeg/needlecrud-sample:latest

Open http://localhost:8080 for interactive Swagger UI.

Documentation

Support

License

MIT License - see LICENSE for details.


Made with ❤️ by LabEG

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 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
0.5.1 88 4/4/2026
0.5.0 86 3/5/2026
0.4.0 89 3/3/2026
0.3.1 86 3/2/2026
0.3.0 88 3/2/2026
0.2.0 93 2/28/2026
0.1.0 87 2/24/2026

Initial release with support for .NET 8.0, 9.0, and 10.0