Nedo.AspNet.ApiContracts
1.1.2
dotnet add package Nedo.AspNet.ApiContracts --version 1.1.2
NuGet\Install-Package Nedo.AspNet.ApiContracts -Version 1.1.2
<PackageReference Include="Nedo.AspNet.ApiContracts" Version="1.1.2" />
<PackageVersion Include="Nedo.AspNet.ApiContracts" Version="1.1.2" />
<PackageReference Include="Nedo.AspNet.ApiContracts" />
paket add Nedo.AspNet.ApiContracts --version 1.1.2
#r "nuget: Nedo.AspNet.ApiContracts, 1.1.2"
#:package Nedo.AspNet.ApiContracts@1.1.2
#addin nuget:?package=Nedo.AspNet.ApiContracts&version=1.1.2
#tool nuget:?package=Nedo.AspNet.ApiContracts&version=1.1.2
Nedo.AspNet.ApiContracts
Standardized API contracts for ASP.NET applications. Provides consistent request/response envelopes, standard HTTP headers, Swagger integration, and built-in request validation via Nedo.AspNet.Request.Validation.
Installation
dotnet add package Nedo.AspNet.ApiContracts
Setup
using Nedo.AspNet.ApiContracts.Extensions;
using Nedo.AspNet.ApiContracts.Swagger;
var builder = WebApplication.CreateBuilder(args);
// Register ApiContracts + Request Validation
builder.Services.AddApiContracts();
builder.Services.AddControllers(options =>
{
// Let all validation flow through Nedo.AspNet.Request.Validation
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
});
builder.Services.AddSwaggerGen(options =>
{
options.OperationFilter<StandardHeaderFilter>();
options.AddStandardAuthorization();
});
All contract classes (FilterItem, OffsetPagination, SortItem, etc.) include built-in validation attributes. Custom DTOs can also use attributes from
Nedo.AspNet.Request.Validation.
1. Standard HTTP Headers
Headers are passed via HTTP headers, not in the request body. Use StandardHeaders constants for consistency.
Authorize Button (Swagger)
| Header | Description | Example |
|---|---|---|
Authorization |
Bearer JWT token | Bearer eyJhbGciOi... |
X-Role-Code |
Role identifier | admin, user |
Operation Parameters (per-endpoint)
| Header | Description | Default |
|---|---|---|
Accept-Language |
Language (BCP 47) | id-ID |
X-Timezone |
Timezone (IANA) | Asia/Jakarta |
X-Tenant-Id |
Tenant identifier | — |
X-Client-Id |
App identifier | — |
X-Client-Version |
App version | — |
X-Correlation-Id |
Cross-service tracing | — |
X-Request-Id |
Request tracing | — |
X-Idempotency-Key |
Safe retry key | — |
Swagger Setup
builder.Services.AddSwaggerGen(options =>
{
options.OperationFilter<StandardHeaderFilter>(); // Per-endpoint headers
options.AddStandardAuthorization(); // Authorize button
});
See docs/01-standard-headers.md for full details.
2. Query / Get
Request: GetRequest<TFilter> wraps a QueryRequest with pagination and sort.
{
"data": {
"filters": [
{ "field": "category", "operator": "Eq", "value": "Electronics" }
],
"search": "macbook",
"sort": [{ "field": "price", "direction": "Desc" }],
"pagination": { "page": 1, "page_size": 20 }
},
"options": { "show_error": true }
}
Response: BaseResponse<List<T>> with QueryResponseInfo and PaginationInfo:
{
"success": true,
"status_code": 200,
"code": "SUC-RTV-001",
"message": "Products retrieved",
"info": {
"transaction_id": "a1b2c3d4-...",
"timestamp": "2026-02-11T12:00:00Z",
"operation_type": "Query",
"correlation_id": "req-abc-123",
"language": "id-ID",
"timezone": "Asia/Jakarta",
"total_ms": 125.5,
"query_ms": 45.2,
"count_ms": 12.3,
"record_count": 2,
"has_next_page": true
},
"data": [{ "id": 1, "name": "MacBook Pro" }, { "id": 2, "name": "iPhone 16" }],
"pagination": {
"page": 1,
"page_size": 2,
"total_count": 10,
"total_pages": 5,
"has_next_page": true
},
"error": null
}
Advanced Query Features
| Feature | Class | Purpose |
|---|---|---|
| Filtering | FilterItem, FilterGroup |
Field-level and nested AND/OR filters |
| Sub-query | SubQueryFilter |
IN (SELECT ...) filters |
| Select | SelectClause |
Column selection, functions, expressions |
| Functions | SqlFunction, FunctionOperand |
CONCAT, UPPER, ROUND, CASE WHEN |
| Joins | JoinClause |
Inner/Left/Right/Cross joins |
| Includes | IncludeClause |
EF Core eager loading |
| Sorting | SortItem |
Field + direction (Asc/Desc) |
| Pagination | OffsetPagination, CursorPagination |
Page-based or keyset pagination |
| GroupBy | GroupByClause, AggregateItem |
Grouping with Count/Sum/Avg/Min/Max |
| Batch | BatchQueryRequest |
Multiple named queries in one request |
Filter operators: Eq, Neq, Gt, Gte, Lt, Lte, Contains, StartsWith, EndsWith, In, NotIn, Between, NotBetween, IsNull, IsNotNull, InSubQuery, etc.
3. Insert
Request: InsertRequest<TData>
{
"data": {
"name": "iPad Pro",
"price": 1099.99,
"category": "Electronics"
},
"options": { "rollback_on_failure": true }
}
Response: BaseResponse<T> with InsertResponseInfo:
{
"success": true,
"status_code": 201,
"code": "SUC-INS-001",
"message": "Product created",
"info": {
"transaction_id": "...",
"operation_type": "Insert",
"total_ms": 85.3,
"record_id": "42",
"insert_ms": 42.1
},
"data": { "id": 42, "name": "iPad Pro" },
"pagination": null,
"error": null
}
4. Update
Request: UpdateRequest<TData> — includes id field.
{
"id": "42",
"data": {
"name": "iPad Pro M4",
"price": 1299.99
}
}
Response: BaseResponse<T> with UpdateResponseInfo:
{
"success": true,
"status_code": 200,
"code": "SUC-UPD-001",
"message": "Product updated",
"info": {
"operation_type": "Update",
"total_ms": 60.2,
"record_id": "42",
"update_ms": 35.8,
"fields_changed": true
},
"data": { "id": 42, "name": "iPad Pro M4" },
"error": null
}
5. Upsert
Request: UpsertRequest<TData> — insert or update.
{
"data": {
"id": 42,
"name": "iPad Pro M4",
"price": 1299.99
}
}
Response: BaseResponse<T> with UpsertResponseInfo:
{
"success": true,
"status_code": 200,
"code": "SUC-UPS-002",
"message": "Product upserted",
"info": {
"operation_type": "Upsert",
"total_ms": 55.0,
"record_id": "42",
"upsert_ms": 30.5,
"was_inserted": false
},
"data": { "id": 42, "name": "iPad Pro M4" },
"error": null
}
6. Delete
Request: DeleteRequest<TData> — supports soft delete.
{
"data": 42,
"soft_delete": true
}
Response: BaseResponse<T> with DeleteResponseInfo:
{
"success": true,
"status_code": 200,
"code": "SUC-DEL-002",
"message": "Product deleted",
"info": {
"operation_type": "Delete",
"total_ms": 40.1,
"record_id": "42",
"delete_ms": 18.5,
"soft_delete": true
},
"data": null,
"error": null
}
7. Batch
Request: BatchRequest<TData> — multiple operations in one request.
{
"data": [
{ "operation": "Insert", "data": { "name": "Apple Watch", "price": 399.99 } },
{ "operation": "Update", "id": "42", "data": { "name": "iPad Pro M5" } },
{ "operation": "Delete", "id": "99" }
],
"options": { "rollback_on_failure": true }
}
Response: BatchResponse<T> with BatchResponseInfo:
{
"success": true,
"status_code": 200,
"code": "SUC-BAT-001",
"message": "Batch completed",
"info": {
"operation_type": "Batch",
"total_ms": 210.5,
"total_items": 3,
"success_count": 3,
"failure_count": 0,
"batch_ms": 180.2,
"transactional": true
},
"data": null,
"error": null
}
Supported operations: Insert, Update, Upsert, Delete.
8. Error Handling
All errors follow a consistent structure:
{
"success": false,
"status_code": 400,
"code": "VAL-001",
"message": "Validation failed",
"info": null,
"data": null,
"error": [
{
"data": { "name": "", "price": -10 },
"error_details": {
"form_errors": [
{
"code": "ERR-BIZ-001",
"message": "At least one field is required",
"fields": ["name", "price"]
}
],
"field_errors": [
{
"field": "name",
"items": [
{ "code": "VAL-001", "message": "Name is required" }
]
},
{
"field": "price",
"items": [
{ "code": "VAL-GEN-001", "message": "Price must be positive" }
]
}
]
}
}
]
}
| Class | Purpose |
|---|---|
ErrorEntry<TData> |
Wraps original input + error details |
ErrorDetails |
Contains form_errors and field_errors |
FormError |
Form-level error with code, message, and related fields |
FieldError |
Field path + list of error items (supports dot-notation: filters[2].value) |
FieldErrorItem |
Individual error with code and message |
Factory Methods
// Success
var response = BaseResponse<Product>.Ok(product, "Created");
// Paginated query
var response = BaseResponse<Product>.Paged(products, page: 1, pageSize: 10, totalCount: 100);
// Simple error
var response = BaseResponse<Product>.Fail("Not found", 404, "ERR-NOT-FOUND");
// Detailed validation error
var response = BaseResponse<Product>.Fail(errorEntries, 400, "VAL-001", "Validation failed");
9. ResponseInfo Reference
All response info classes inherit from BaseResponseInfo:
BaseResponseInfo (common fields)
| Field | Description |
|---|---|
transaction_id |
Unique transaction ID |
timestamp |
ISO 8601 timestamp |
operation_type |
Insert, Update, Delete, Query, etc. |
executed_by |
User who executed |
module_name |
Service/module name |
correlation_id |
Echoed from header |
request_id |
Echoed from header |
language |
Echoed from header |
timezone |
Echoed from header |
tenant_id |
Echoed from header |
client_id |
Echoed from header |
client_version |
Echoed from header |
total_ms |
Total processing time |
Pagination
| Field | Description |
|---|---|
page |
Current page (1-based) |
page_size |
Items per page |
total_count |
Total items across all pages |
total_pages |
Total pages |
has_next_page |
Whether more pages exist |
paginationisnullfor non-paginated responses (Insert, Update, Delete, etc.).
Operation-Specific Info Fields
| Class | Description | Extra Fields |
|---|---|---|
QueryResponseInfo |
Query/Get list operations | query_ms, count_ms, record_count, has_next_page |
GetResponseInfo |
Single record retrieval | record_id, query_ms |
InsertResponseInfo |
Insert operations | record_id, insert_ms |
UpdateResponseInfo |
Update operations | record_id, update_ms, fields_changed |
DeleteResponseInfo |
Delete operations | record_id, delete_ms, soft_delete |
UpsertResponseInfo |
Upsert operations | record_id, upsert_ms, was_inserted |
BatchResponseInfo |
Batch operations | total_items, success_count, failure_count, batch_ms, transactional |
10. Response Codes
Use ResponseCodes constants class. Format: {STATUS}-{OPERATION}-{SEQUENCE}
Success Codes
| Constant | Code | Description |
|---|---|---|
RetrievalSuccess |
SUC-RTV-001 |
Data retrieved successfully |
RetrievalEmpty |
SUC-RTV-002 |
No records found |
InsertSuccess |
SUC-INS-001 |
Record created |
UpdateSuccess |
SUC-UPD-001 |
Record updated |
UpdateNoChange |
SUC-UPD-002 |
No fields changed |
DeleteSuccess |
SUC-DEL-001 |
Record hard-deleted |
SoftDeleteSuccess |
SUC-DEL-002 |
Record soft-deleted |
UpsertInserted |
SUC-UPS-001 |
Upsert → inserted |
UpsertUpdated |
SUC-UPS-002 |
Upsert → updated |
BatchSuccess |
SUC-BAT-001 |
All items succeeded |
BatchPartial |
SUC-BAT-002 |
Partial success |
Error Codes (App-Level)
| Constant | Code | Description |
|---|---|---|
BusinessRuleViolation |
ERR-BIZ-001 |
Business rule violated |
DuplicateEntry |
ERR-BIZ-002 |
Duplicate data |
StateConflict |
ERR-BIZ-003 |
State conflict |
NotFound |
ERR-NF-001 |
Resource not found |
RecordNotFound |
ERR-NF-002 |
Record not found |
Unauthorized |
ERR-AUTH-001 |
Not authenticated |
Forbidden |
ERR-AUTH-002 |
Not authorized |
TokenExpired |
ERR-AUTH-003 |
Token expired |
InternalError |
ERR-SRV-001 |
Server error |
ServiceUnavailable |
ERR-SRV-002 |
Service unavailable |
Timeout |
ERR-SRV-003 |
Request timeout |
Validation Codes
Validation error codes (VAL-*) are provided by Nedo.AspNet.Request.Validation and are not in this library.
| Category | Code Range | Examples |
|---|---|---|
| Generic | VAL-GEN-001 – VAL-GEN-006 |
Required, MaxLength, MinLength |
| String | VAL-STR-001 – VAL-STR-017 |
CamelCase, Alpha, NoWhitespace |
| Numeric | VAL-NUM-001 – VAL-NUM-011 |
MinValue, Range, PositiveNumber |
| Date/Time | VAL-DAT-001 – VAL-DTM-014 |
DateRange, PastDate, Weekday |
| File | VAL-FIL-001 – VAL-FIL-006 |
FileType, MaxFileSize |
| Image | VAL-IMA-001 – VAL-IMA-012 |
ImageType, MaxResolution |
| IP Address | VAL-IPA-001 – VAL-IPA-026 |
IPv4, PrivateIP, CIDR |
| Phone | VAL-PHN-001 – VAL-PHN-003 |
PhoneFormat, CountryCode |
| Money | VAL-MON-001 – VAL-MON-007 |
CurrencyFormat, MinAmount |
| URL | VAL-URL-001 – VAL-URL-009 |
ValidUrl, SecureUrl |
Project Structure
src/Nedo.AspNet.ApiContracts/
├── Headers/
│ └── StandardHeaders.cs
├── Swagger/
│ ├── StandardHeaderFilter.cs
│ └── SwaggerExtensions.cs
├── Requests/
│ ├── BaseRequest.cs
│ ├── RequestOptions.cs
│ ├── GetRequest.cs
│ ├── InsertRequest.cs
│ ├── UpdateRequest.cs
│ ├── UpsertRequest.cs
│ ├── DeleteRequest.cs
│ └── BatchRequest.cs
├── Responses/
│ ├── BaseResponse.cs
│ ├── PaginationInfo.cs
│ ├── ResponseCodes.cs
│ ├── BatchResponse.cs
│ ├── Info/
│ │ ├── BaseResponseInfo.cs
│ │ ├── QueryResponseInfo.cs
│ │ ├── GetResponseInfo.cs
│ │ ├── InsertResponseInfo.cs
│ │ ├── UpdateResponseInfo.cs
│ │ ├── DeleteResponseInfo.cs
│ │ ├── UpsertResponseInfo.cs
│ │ └── BatchResponseInfo.cs
│ └── Errors/
│ ├── ErrorEntry.cs
│ ├── ErrorDetails.cs
│ ├── FormError.cs
│ ├── FieldError.cs
│ └── FieldErrorItem.cs
└── Queries/
├── QueryRequest.cs
├── QueryProfile.cs
├── BatchQueryRequest.cs
├── Filters/
│ ├── FilterItem.cs
│ ├── FilterGroup.cs
│ └── SubQueryFilter.cs
├── Selects/
│ ├── SelectClause.cs
│ ├── SqlFunction.cs
│ ├── FunctionOperand.cs
│ ├── InlineExpression.cs
│ └── IncludeClause.cs
├── Clauses/
│ ├── SortItem.cs
│ ├── JoinClause.cs
│ ├── GroupByClause.cs
│ └── AggregateItem.cs
└── Pagination/
├── OffsetPagination.cs
└── CursorPagination.cs
## Versioning & Release
This project uses **git tags** to control versioning. The GitLab CI/CD pipeline automatically builds and publishes NuGet packages when a tag is pushed.
### How It Works
1. The pipeline triggers **only on tags** (e.g. `v1.0.0`)
2. The `v` prefix is stripped to get the version number (`1.0.0`)
3. The package is built, packed, and published to **NuGet.org**
### How to Release a New Version
```bash
# 1. Commit and push your changes
git add .
git commit -m "feat: your changes"
git push origin main
# 2. Create a version tag
git tag -a v1.0.0 -m "Release v1.0.0
Added:
- Initial release of Nedo.AspNet.ApiContracts
- Standardized request/response envelopes
- Swagger integration with standard headers
Notes:
No breaking changes."
# 3. Push the tag (this triggers the CI/CD pipeline)
git push origin v1.0.0
Version Format
| Type | Example Tag | NuGet Version |
|---|---|---|
| Patch | v1.0.1 |
1.0.1 |
| Minor | v1.1.0 |
1.1.0 |
| Major | v2.0.0 |
2.0.0 |
| Prerelease | v1.1.0-beta.1 |
1.1.0-beta.1 |
The tag must start with v (e.g. v1.0.0, not 1.0.0).
Contributing
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/YourFeature) - Commit your changes (
git commit -m "Add awesome feature") - Push to your branch (
git push origin feature/YourFeature) - Open a pull request
Please ensure:
- All tests pass (
dotnet test) - Code follows project conventions
- Add unit tests for new features
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 was computed. 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. |
-
net9.0
- Nedo.AspNet.Request.Validation (>= 1.1.2)
- Swashbuckle.AspNetCore.Annotations (>= 10.1.2)
- Swashbuckle.AspNetCore.SwaggerGen (>= 10.1.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.