Kanject.Core.Api
3.2.8
Prefix Reserved
dotnet add package Kanject.Core.Api --version 3.2.8
NuGet\Install-Package Kanject.Core.Api -Version 3.2.8
<PackageReference Include="Kanject.Core.Api" Version="3.2.8" />
<PackageVersion Include="Kanject.Core.Api" Version="3.2.8" />
<PackageReference Include="Kanject.Core.Api" />
paket add Kanject.Core.Api --version 3.2.8
#r "nuget: Kanject.Core.Api, 3.2.8"
#:package Kanject.Core.Api@3.2.8
#addin nuget:?package=Kanject.Core.Api&version=3.2.8
#tool nuget:?package=Kanject.Core.Api&version=3.2.8
Kanject.Core.Api
Kanject.Core.Api supplies the HTTP-edge conventions shared by Kanject ASP.NET Core services: standardized response envelopes, controller helpers, exception-to-HTTP mapping, and request-scoped tenant resolution.
Use it to keep transport concerns at the edge. Controllers return one predictable response shape, domain/application services throw ApiServiceException for expected failures, and downstream services consume ITenantContext without depending on HttpContext.
Installation
dotnet add package Kanject.Core.Api
The package supports .NET 8, .NET 9, and .NET 10.
Quick start
using Kanject.Core.Api.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddCoreExceptionHandlerMiddleware();
builder.Services.AddTenantMiddleware();
builder.Services.AddScoped<OrderService>();
var app = builder.Build();
app.UseRouting();
app.UseAuthentication();
app.UseCoreExceptionHandlerMiddleware();
app.UseTenantMiddleware();
app.UseAuthorization();
app.MapControllers();
app.Run();
Pipeline order matters: authenticate first if tenant/user resolution depends on identity; run the exception middleware before components whose exceptions it should translate; populate the tenant context before invoking tenant-aware services.
Real-world controller
using Kanject.Core.Api.Abstractions.Models;
using Kanject.Core.Api.Controller;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/orders")]
public sealed class OrdersController(OrderService orders) : BaseController
{
/// <summary>Returns one page of orders for the tenant in the x-tenant-id header.</summary>
[HttpGet]
[ProducesResponseType(typeof(Kanject.Core.Api.Models.Response<ApiResponseDataset<OrderDto>>), 200)]
public async Task<IActionResult> ListAsync(
[FromQuery] int pageSize = 25,
[FromQuery] string? pageToken = null,
CancellationToken cancellationToken = default)
{
var page = await orders.ListAsync(pageSize, pageToken, cancellationToken);
var metadata = new PayloadMetadata(
pageSize: pageSize,
pageItemCount: page.Items.Count,
hasNextPage: page.NextToken is not null,
pageToken: page.NextToken,
custom: new Dictionary<string, object>
{
["resultSource"] = "orders"
});
return ApiResponse(page.Items, metadata, "Orders retrieved.");
}
}
BaseController is protected with the JWT bearer scheme by default. Use the normal ASP.NET Core [AllowAnonymous] attribute only for endpoints that are intentionally public.
Tenant-aware application service
AddTenantMiddleware() registers a scoped ITenantContext. UseTenantMiddleware() populates it from the canonical x-tenant-id request header.
using Kanject.Core.Api.Abstractions.Enums;
using Kanject.Core.Api.Abstractions.Exceptions;
using Kanject.Core.Tenancy;
public sealed class OrderService(ITenantContext tenant, IOrderRepository repository)
{
public Task<Order?> GetAsync(Guid id, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(tenant.TenantId))
{
throw new ApiServiceException(
"tenant",
"The x-tenant-id header is required.",
ApiResponseCode.ValidationError);
}
return repository.GetAsync(tenant.TenantId, id, cancellationToken);
}
}
The middleware intentionally leaves TenantId empty when the header is absent. This lets public or tenant-neutral endpoints coexist with tenant-aware ones; services that require a tenant must guard the boundary explicitly.
Response contract
Successful responses use the following JSON envelope:
{
"data": {},
"message": "Success",
"responseCode": "0",
"status": 200
}
Validation-capable responses may also include:
{
"data": null,
"message": "Postal code is required.",
"responseCode": "2",
"status": 0,
"validationMessages": ["Postal code is required."]
}
Controller helpers
| Helper | Result |
|---|---|
ApiResponse(payload, message, responseCode) |
HTTP 200 with Response<T> success envelope |
ApiResponse(items, metadata, message, responseCode) |
HTTP 200 with ApiResponseDataset<T> pagination envelope |
ApiErrorResponse(validationResults) |
HTTP 400 with validation details |
ApiErrorResponse(ModelState) |
HTTP 400 with model-state validation details |
ApiErrorResponse(message, code, statusCode) |
Chosen HTTP status with failure envelope |
GetModelStateValidationErrors() |
Semicolon-separated validation messages |
GetModelStateValidationErrorsAsList() |
Response containing all validation messages |
PayloadMetadata carries PageSize, PageItemCount, PageToken, HasNextPage, and an optional Custom dictionary. A non-empty page token automatically marks HasNextPage as true.
Exception middleware
Register and activate the default middleware:
services.AddCoreExceptionHandlerMiddleware();
app.UseCoreExceptionHandlerMiddleware();
Expected application failures should use ApiServiceException:
throw new ApiServiceException(
"orderId",
"The order does not exist.",
ApiResponseCode.NotFound);
The default mapper currently handles these response codes:
ApiResponseCode |
HTTP status |
|---|---|
Success |
200 |
ValidationError |
400 |
Unauthorized |
401 |
NotFound |
404 |
Error and all other values |
500 |
Unhandled exceptions are logged through the SDK request logger and returned as a generic error envelope. Do not rely on exception messages being returned to clients; production responses intentionally avoid leaking internal details.
Custom middleware
Register a custom IMiddleware when an application needs a wider status mapping, trace IDs, or another error format:
services.AddCoreExceptionHandlerMiddleware<CommerceExceptionMiddleware>();
app.UseMiddleware<CommerceExceptionMiddleware>();
The generic registration method registers the type; activate that same middleware type explicitly in the pipeline.
Public surface at a glance
| Type or extension | Responsibility |
|---|---|
BaseController |
Authenticated controller base and response/model-state helpers |
Response<T> |
API envelope with validation messages |
AddCoreExceptionHandlerMiddleware |
Registers default or custom exception middleware |
UseCoreExceptionHandlerMiddleware |
Activates the default exception translator |
AddTenantMiddleware |
Registers scoped tenant context and resolver middleware |
UseTenantMiddleware |
Resolves x-tenant-id for each request |
ITenantContext |
Downstream, HTTP-independent tenant contract |
OpenAPI and documentation guidance
The README defines package behavior, but endpoint documentation should remain close to controller code so ASP.NET Core/OpenAPI tooling can extract it. For informative generated docs:
- Enable XML documentation in the web project.
- Add XML
<summary>,<remarks>,<param>,<returns>, and<response code="...">comments to controllers and actions. - Add
[ProducesResponseType]for every success and failure shape. - Use concrete DTOs instead of anonymous response objects.
- Document the required
x-tenant-idheader globally or through an OpenAPI operation filter. - Keep business response codes distinct from HTTP status codes in examples and client guidance.
Recommended project settings:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
With Swashbuckle or NSwag, include the generated XML file and map action response types. A typical operation should describe both layers of status: for example, HTTP 404 plus responseCode: "5" for a missing order.
Testing guidance
- Integration-test middleware order and the complete JSON envelope, not only controller return types.
- Verify that two concurrent request scopes cannot observe one another's tenant IDs.
- Cover missing and whitespace-only tenant headers.
- Test every
ApiServiceExceptioncode actually used by the application; codes outside the default mapper resolve to HTTP 500. - Verify production responses do not expose stack traces or secret-bearing exception messages.
- Use
WebApplicationFactory<TEntryPoint>for the full request pipeline and a fixedITenantContextstub for isolated service tests.
Runnable example
The API sample is a multi-tenant order API with pagination, standardized envelopes, known validation and not-found exception paths, request cancellation, and copy-pasteable curl scenarios.
| 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
- Kanject.Core.Api.Abstractions (>= 3.6.6)
- Kanject.Core.Tenancy (>= 3.9.6)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.8)
-
net8.0
- Kanject.Core.Api.Abstractions (>= 3.6.6)
- Kanject.Core.Tenancy (>= 3.9.6)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.11)
-
net9.0
- Kanject.Core.Api.Abstractions (>= 3.6.6)
- Kanject.Core.Tenancy (>= 3.9.6)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 9.0.7)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.