Kanject.Core.Api
ASP.NET Core building blocks that give every Kanject HTTP service the same baseline: a consistent Response<T> envelope from BaseController, exception-to-error middleware, and x-tenant-id tenant resolution. Free under the Community licence.
Install
dotnet add package Kanject.Core.Api Register
using Kanject.Core.Api.Extensions;var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllers();builder.Services.AddCoreExceptionHandlerMiddleware(); // consistent error envelopebuilder.Services.AddTenantMiddleware(); // x-tenant-id resolutionvar app = builder.Build();app.UseRouting();app.UseCoreExceptionHandlerMiddleware();app.UseTenantMiddleware();app.MapControllers();app.Run(); Two middlewares, registered as services and then used in the pipeline: CoreExceptionHandler turns uncaught exceptions into a consistent JSON error envelope, and TenantMiddleware resolves the caller's tenant from the x-tenant-id header into the request context.
Controllers
Derive from BaseController and return through ApiResponse(...), which wraps your payload (and optional PayloadMetadata for pagination) in the shared Response<T> envelope. Errors come back in the same Response<string> shape, so every endpoint — success or failure — has one predictable body:
using Kanject.Core.Api.Controller;using Kanject.Core.Api.Models;[ApiController][Route("api/orders")]public sealed class OrdersController(OrderService orders) : BaseController{ [HttpGet] [ProducesResponseType(typeof(Response<ApiResponseDataset<OrderDto>>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Response<string>), StatusCodes.Status400BadRequest)] public async Task<IActionResult> ListAsync( [FromQuery] int pageSize = 20, [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); // BaseController.ApiResponse wraps the payload in Response<T>. return ApiResponse(page.Items, metadata, "Orders retrieved."); }} What ships with it
BaseController+ApiResponse(...)→ a consistentResponse<T>/ApiResponseDataset<T>envelope, withPayloadMetadatafor paginationAddCoreExceptionHandlerMiddleware/UseCoreExceptionHandlerMiddleware— uncaught exceptions become a uniform JSON error responseAddTenantMiddleware/UseTenantMiddleware—x-tenant-idresolved into the request-scoped tenant context- Distributed as a public NuGet Community package (free under the $250K threshold)