LabEG.NeedleCrud
0.5.1
dotnet add package LabEG.NeedleCrud --version 0.5.1
NuGet\Install-Package LabEG.NeedleCrud -Version 0.5.1
<PackageReference Include="LabEG.NeedleCrud" Version="0.5.1" />
<PackageVersion Include="LabEG.NeedleCrud" Version="0.5.1" />
<PackageReference Include="LabEG.NeedleCrud" />
paket add LabEG.NeedleCrud --version 0.5.1
#r "nuget: LabEG.NeedleCrud, 0.5.1"
#:package LabEG.NeedleCrud@0.5.1
#addin nuget:?package=LabEG.NeedleCrud&version=0.5.1
#tool nuget:?package=LabEG.NeedleCrud&version=0.5.1
LabEG.NeedleCrud
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.csfor 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 | 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 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. |
-
net10.0
- Microsoft.EntityFrameworkCore (>= 10.0.5)
-
net8.0
- Microsoft.EntityFrameworkCore (>= 8.0.25)
-
net9.0
- Microsoft.EntityFrameworkCore (>= 9.0.14)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Initial release with support for .NET 8.0, 9.0, and 10.0