# 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

```bash
dotnet add package Kanject.Core.Api
```

## Register

```csharp
using Kanject.Core.Api.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddCoreExceptionHandlerMiddleware();   // consistent error envelope
builder.Services.AddTenantMiddleware();                 // x-tenant-id resolution

var 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:

```csharp
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 consistent `Response<T>` / `ApiResponseDataset<T>` envelope, with `PayloadMetadata` for pagination
- `AddCoreExceptionHandlerMiddleware` / `UseCoreExceptionHandlerMiddleware` — uncaught exceptions become a uniform JSON error response
- `AddTenantMiddleware` / `UseTenantMiddleware` — `x-tenant-id` resolved into the request-scoped tenant context
- Distributed as a public NuGet Community package (free under the $250K threshold)

> **Parameter Store config:** Boot-time AWS config hydration lives in the companion `Kanject.Core.Api.Aws.Extensions` package: `builder.AddAwsSystemManagerParameterStore()` layers Parameter Store values over `appsettings` once at startup, so the runtime never calls SSM again.

> **OpenAPI is opt-in:** The package doesn't generate OpenAPI itself. Add your OpenAPI package of choice and turn on `<GenerateDocumentationFile>` in the csproj if you want your XML doc comments in the schema — the `[ProducesResponseType(typeof(Response<…>))]` annotations above document each status code's envelope.

> **At Bean & Bark:** This is the baseline the Bean & Bark Orders API starts from — the [CLI series](https://www.kanject.com/docs/cli-bean-bark/) scaffolds a service of exactly this shape and takes it from empty repo to deployed stage, secrets and rollbacks included.

---
_Source: https://www.kanject.com/docs/core-api/ · Kanject Docs_
