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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Kanject.Core.Api" Version="3.2.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Kanject.Core.Api" Version="3.2.8" />
                    
Directory.Packages.props
<PackageReference Include="Kanject.Core.Api" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Kanject.Core.Api --version 3.2.8
                    
#r "nuget: Kanject.Core.Api, 3.2.8"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Kanject.Core.Api@3.2.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Kanject.Core.Api&version=3.2.8
                    
Install as a Cake Addin
#tool nuget:?package=Kanject.Core.Api&version=3.2.8
                    
Install as a Cake Tool

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:

  1. Enable XML documentation in the web project.
  2. Add XML <summary>, <remarks>, <param>, <returns>, and <response code="..."> comments to controllers and actions.
  3. Add [ProducesResponseType] for every success and failure shape.
  4. Use concrete DTOs instead of anonymous response objects.
  5. Document the required x-tenant-id header globally or through an OpenAPI operation filter.
  6. 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 ApiServiceException code 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 fixed ITenantContext stub 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.2.8 29 7/18/2026
3.2.7 117 7/13/2026
3.2.6 91 7/11/2026
3.2.5 85 7/11/2026
3.2.4 91 7/10/2026
3.2.3 90 7/9/2026
3.2.2 88 7/9/2026