Annotations & Self-Documenting Code

Signature Feature

Kanject treats your C# source as the single source of truth. Attributes and XML comments drive OpenAPI schemas, event catalogs, permission policies, analytics dimensions, and client SDKs — so your API docs, your event hub, and your insights dashboards never drift from the code that actually runs.

Write once
A single /// summary and [Attribute] set describes the contract, the docs, and the analytics schema.
Validated at build
Missing docs or malformed annotations fail the build — not production.
Discoverable
Every emitted event, every endpoint, every permission appears in the generated OpenAPI + event catalog.
Type-safe
Generated TypeScript clients and event consumers keep producers and consumers in lockstep.

Annotated controller

XML comments become OpenAPI descriptions. [ProducesResponseType] becomes schemas. [EmitsEvent] registers this endpoint as a producer in the event catalog — automatically.

csharp
using Kanject.Core.ApiV2;
using Kanject.Insights.Abstractions.Attributes;

/// <summary>
///     Manages product listings in the marketplace.
/// </summary>
/// <remarks>
///     All endpoints require a valid user token. Admin operations
///     require the <c>Marketplace.Admin</c> permission.
/// </remarks>
[ApiController]
[Route("api/v1/products")]
[Produces("application/json")]
public class ProductsController(IProductService products) : ControllerBase
{
    /// <summary>
    ///     Create a new product listing.
    /// </summary>
    /// <param name="request">The product details supplied by the seller.</param>
    /// <returns>The newly created product with its server-assigned id.</returns>
    /// <response code="201">Product was created successfully.</response>
    /// <response code="400">Validation failed — see the error payload.</response>
    /// <response code="401">The caller is not authenticated.</response>
    [HttpPost]
    [Authorize(Policy = KanjectPolicies.AuthenticatedUser)]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ValidationProblem), StatusCodes.Status400BadRequest)]
    [EmitsEvent("product_created")]        // ← Kanject annotation
    public async Task<ActionResult<ProductDto>> CreateAsync(
        [FromBody] CreateProductRequest request)
    {
        var product = await products.CreateAsync(request, User.GetId());
        return CreatedAtAction(nameof(GetAsync),
            new { id = product.Id }, product);
    }
}

Self-documenting events

[EventTopic] registers the type with the Kanject Event Hub; [EventField] marks which properties are aggregatable, groupable, or identity keys. This is what powers the Insights platform — your analytics schema is your event class.

csharp
using Kanject.EventHub.Abstractions.Attributes;

/// <summary>
///     Emitted when a buyer completes a checkout.
/// </summary>
/// <remarks>
///     Subscribed to by the notifications, analytics, and
///     wallet services. Do not change field names once published.
/// </remarks>
[EventTopic("order_created", version: 2)]
public record OrderCreatedEvent
{
    /// <summary>The order's unique identifier.</summary>
    [EventField, Required]
    public Guid OrderId { get; init; }

    /// <summary>Total amount charged, in the order's currency.</summary>
    [EventField(Aggregatable = true), Currency]
    public decimal TotalPayable { get; init; }

    /// <summary>Product type — used for cohort and segmentation analytics.</summary>
    [EventField(GroupBy = true)]
    public string ProductType { get; init; } = default!;

    /// <summary>The user who placed the order.</summary>
    [EventField(Identity = true)]
    public Guid UserId { get; init; }
}
Pro Tip
Flip on <GenerateDocumentationFile>true</GenerateDocumentationFile> in your .csproj and treat missing XML comments as warnings-as-errors. Your codebase stays self-documenting forever.