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.
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.
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.