Project.Base is a .NET boilerplate/scaffolding library for building REST API applications with a clean layered architecture. It provides a fully generic framework — abstract controllers, services, repositories, converters, and validators — so you only need to inherit from the base classes and implement a few methods to have a complete CRUD API with validation, pagination, dynamic search, and DTO auto-conversion.
To start using Project.Base in your own application, you need to:
- Install via NuGet (see below), or
- Clone this repository and add references to the 5 projects.
- For each entity you want to expose as an API, create:
- A Domain Entity inheriting from
BaseObjectWithId - A DTO inheriting from
DtoBase - A Converter inheriting from
DefaultConverter<TObj, TDto> - A Validator inheriting from
BaseAbstractValidator<TObject> - A Repository inheriting from
GenericRepository<TObjeto> - A Service inheriting from
BaseService<TObject, TDto> - A Controller inheriting from
AbstractController<TDto>
- A Domain Entity inheriting from
You can install the packages individually or together depending on your needs:
Each layer is available as a separate NuGet package:
# Core enumerators and models
dotnet add package Project.Base.Enumerators
# DTOs and data contracts
dotnet add package Project.Base.Contracts
# Business logic, services, and validators
dotnet add package Project.Base.Domain
# Entity Framework repository implementations
dotnet add package Project.Base.Repository
# ASP.NET Core Web API base controller
dotnet add package Project.Base.WebApiAlternatively, install the complete package:
dotnet add package Project.BaseOr via Package Manager Console:
Install-Package Project.Base
- Project.Base.Enumerators: No dependencies
- Project.Base.Contracts: Depends on
Project.Base.Enumerators - Project.Base.Domain: Depends on
Project.Base.Contracts,FluentValidation,Mapster - Project.Base.Repository: Depends on
Project.Base.Domain,Microsoft.EntityFrameworkCore,Microsoft.AspNetCore.Identity.EntityFrameworkCore - Project.Base.WebApi: Depends on
Project.Base.Domain,Swashbuckle.AspNetCore
- .NET 10.0 SDK (LTS) or later
- An IDE supporting .NET (Visual Studio 2022, JetBrains Rider, VS Code)
- A relational database (Entity Framework Core supports SQL Server, PostgreSQL, SQLite, In-Memory, etc.)
public class Product : BaseObjectWithId
{
public required string Name { get; set; }
public decimal Price { get; set; }
public string? Description { get; set; }
}public class ProductDto : DtoBase
{
public required string Name { get; set; }
public decimal Price { get; set; }
public string? Description { get; set; }
}public class ProductConverter : DefaultConverter<Product, ProductDto>
{
// Mapster handles the mapping automatically.
// Override Convert() / Convert(TDto) if you need custom mapping rules.
}public class ProductValidator : BaseAbstractValidator<Product>
{
public override void AssignCommonValidations()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(100);
RuleFor(x => x.Price)
.GreaterThan(0);
}
}public class ProductRepository : BaseObjectWithIdRepository<Product>
{
public ProductRepository(DbContext context) : base(context) { }
// ListWithSearchTerm is already implemented with dynamic search
// across all string properties. Override only if you need custom logic.
}public class ProductService : BaseService<Product, ProductDto>
{
public ProductService(IProductRepository repository)
: base(repository) { }
protected override IBaseAbstractValidator<Product> Validator()
=> new ProductValidator();
protected override IDefaultConverter<Product, ProductDto> Converter()
=> new ProductConverter();
}[Route("api/[controller]")]
[ApiController]
public class ProductController : AbstractController<ProductDto>
{
public ProductController(IBaseService<ProductDto> service)
: base(service) { }
// Declare the methods with 'new' to expose then
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status429TooManyRequests)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public new async Task<ActionResult<DtoOutput<TDto>>> FindById(Guid id)
{
return base.FindById(id)
}
}Your controller automatically exposes these endpoints (inherited from AbstractController<TDto>):
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/{controller} |
Insert a new entity (body: DTO) |
PUT |
/api/{controller} |
Update an existing entity (body: DTO) |
DELETE |
/api/{controller}?id={guid} |
Delete an entity by ID |
GET |
/api/{controller}/findbyid?id={guid} |
Find a single entity by ID |
GET |
/api/{controller}/findall |
Find all entities |
GET |
/api/{controller}/find?page=1&limit=10&order=0 |
Paginated search |
All responses are wrapped in DtoOutput<TDto>:
{
"success": true,
"resultSet": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Sample Product",
"price": 29.99,
"description": "A sample"
}
],
"page": 1,
"pageSize": 10,
"totalCount": 1
}On validation failure:
{
"success": false,
"resultSet": [
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "",
"price": -1,
"description": null
}
],
"validationFails": [
{
"message": "Name must not be empty.",
"property": "Name",
"isImpeditive": true
},
{
"message": "Price must be greater than 0.",
"property": "Price",
"isImpeditive": true
}
]
}The ListWithSearchTerm method in GenericRepository automatically searches across all string properties of your entity when no searchTarget is specified. To search a specific field, pass searchTarget as a query parameter.
Use the Find endpoint with query parameters:
GET /api/product/find?page=1&limit=20&order=0&searchTarget=Name&searchTerm=sample
| Parameter | Type | Description |
|---|---|---|
page |
int | Page number (default: 1) |
limit |
int | Items per page |
order |
EnumOrder |
0 = DESCENDING, 1 = ASCENDING |
searchTarget |
string? | Property name to filter on |
searchTerm |
string? | Text to search for |
This project uses GitHub Actions to automatically build and publish NuGet packages. The workflow is triggered when you create a new Git tag in the format v*.*.*.
-
Tag the release:
git tag v1.0.1 git push origin v1.0.1
-
Workflow automatically:
- Builds the solution in Release mode
- Runs all unit tests
- Packs all 5 projects as individual NuGet packages:
Project.Base.EnumeratorsProject.Base.ContractsProject.Base.DomainProject.Base.RepositoryProject.Base.WebApi
- Publishes to NuGet.org
The workflow is defined in .github/workflows/nuget-publish.yml and uses NuGet OIDC authentication for secure publishing.
- Issues: Open an issue on the GitHub repository
- Discussions: Start a conversation in the GitHub Discussions
- Contributing: Pull requests are welcome. Please ensure unit tests pass before submitting.