Skip to content

Attribute-Based Routing (RTTI)

Kittox features an attribute-based routing system inspired by MARS / WiRL, the popular Delphi REST libraries. Handler classes are decorated with custom Delphi attributes; the framework discovers them via RTTI at startup and dispatches HTTP requests to the matching method automatically.

This architecture enables:

  • Extensibility — applications register custom URL handlers without modifying the framework
  • Modularity — each feature (chart, calendar, map) lives in its own unit with zero coupling to the core
  • Enterprise separation — enterprise modules can be included or excluded by simply adding/removing a uses reference

How it works

1. Declare a handler class

A handler is a plain Delphi class decorated with [TKXPath] on the class (base path) and on each public method (sub-path). HTTP method attributes ([TKXGET], [TKXPOST]) specify which requests the method handles.

pascal
unit Kitto.Web.Handler.Chart;

{$RTTI EXPLICIT METHODS([vcPublic, vcPublished])
       PROPERTIES([vcPublic, vcPublished])}

type
  [TKXPath('/kx/view/{ViewName}')]
  TKXChartHandler = class
  public
    [TKXPath('/chart-data')]
    [TKXGET]
    procedure HandleChartData(
      [TKXPathParam('ViewName')] const AViewName: string;
      [TKXContext] ADataView: TKDataView);
  end;

RTTI directive required

The {$RTTI EXPLICIT ...} directive is mandatory in every handler unit. Without it, Delphi does not emit attribute metadata for methods and parameters, and the routing engine cannot discover the handler.

2. Register the handler

Registration happens in the initialization section — the same pattern used by TKXControllerRegistry for UI controllers:

pascal
initialization
  TKXResourceRegistry.Instance.RegisterResource(TKXChartHandler);

finalization
  TKXResourceRegistry.Instance.UnregisterResource(TKXChartHandler);

At registration time, TKXResourceRegistry scans the class via RTTI:

  1. Reads [TKXPath] from the class → base path (/kx/view/{ViewName})
  2. For each public method with [TKXPath] + [TKXGET/POST] → creates a TKXMethodInfo
  3. For each parameter → reads [TKXPathParam], [TKXQueryParam], [TKXFormParam], or [TKXContext]
  4. Sorts all registered handlers by specificity (more literal segments = higher priority)

3. Request dispatch

When a request arrives, the TKXActivation engine (created per-request):

  1. Matches the URL against all registered path templates — literal segments match exactly, {Param} segments capture any value
  2. Fills method parameters from the appropriate source:
    • [TKXPathParam('ViewName')] → extracted from the URL path
    • [TKXQueryParam('key')] → from the query string (?key=...)
    • [TKXFormParam('_op')] → from the POST body
    • [TKXContext] → resolved by type from the injection registry
  3. Invokes the method via TRttiMethod.Invoke
  4. Cleans up — the handler instance is created and destroyed within the same request

Attributes reference

AttributeTargetDescription
[TKXPath('/path/{Param}')]Class, MethodURL path template with {Param} placeholders
[TKXGET]MethodResponds to HTTP GET
[TKXPOST]MethodResponds to HTTP POST
[TKXANYAttribute]MethodResponds to any HTTP method
[TKXPathParam('Name')]ParameterExtracts {Name} from the URL path
[TKXQueryParam('Name')]ParameterExtracts ?Name=value from the query string
[TKXFormParam('Name')]ParameterExtracts from POST body (falls back to query string)
[TKXContext]ParameterDependency injection by type

Dependency injection

Parameters decorated with [TKXContext] are resolved by their Delphi type from TKXInjectionRegistry. The framework pre-registers providers for common types:

Parameter typeInjected value
TKWebRequestCurrent thread-local request
TKWebResponseCurrent thread-local response
TKWebSessionCurrent thread-local session
TKConfigApplication configuration singleton
TKAuthenticatorCurrent authenticator
TKDataViewResolved from {ViewName} path parameter
TKViewTableMainTable of the resolved TKDataView

Applications can register custom providers:

pascal
TKXInjectionRegistry.Instance.RegisterProvider(TypeInfo(TMyService),
  function(const AType: TRttiType;
    const AActivation: IKXActivationContext): TValue
  begin
    Result := TValue.From<TMyService>(TMyService.Create);
  end);

Dynamic script/CSS injection

Enterprise modules that require client-side libraries (e.g., Chart.js, EventCalendar) register them via TKXScriptRegistry in their initialization section:

pascal
// In Kitto.Html.ChartPanel.pas
initialization
  TKXControllerRegistry.Instance.RegisterClass('ChartPanel',
    TKXChartPanelController);
  TKXScriptRegistry.Instance.RegisterScript('/js/chart.umd.min.js');
pascal
// In Kitto.Html.CalendarPanel.pas
initialization
  TKXControllerRegistry.Instance.RegisterClass('CalendarPanel',
    TKXCalendarPanelController);
  TKXScriptRegistry.Instance.RegisterStylesheet('/css/event-calendar.min.css');
  TKXScriptRegistry.Instance.RegisterScript('/js/event-calendar.min.js');

The page template (_Page.html) emits the registered tags via TemplatePro placeholders (dynamicStyles and dynamicScripts). If the enterprise module is not included in UseKitto.pas, no scripts are registered and no tags are emitted.

Custom application handlers

Applications can define their own URL handlers following the same pattern:

pascal
unit MyApp.Handler.Report;

{$RTTI EXPLICIT METHODS([vcPublic, vcPublished])
       PROPERTIES([vcPublic, vcPublished])}

type
  [TKXPath('/api/report/{ReportName}')]
  TMyReportHandler = class
  public
    [TKXPath('/generate')]
    [TKXPOST]
    procedure Generate(
      [TKXPathParam('ReportName')] const AName: string;
      [TKXFormParam('format')] const AFormat: string;
      [TKXContext] AConfig: TKConfig);
  end;

implementation

uses Kitto.Web.Routing.Registry;

procedure TMyReportHandler.Generate(const AName, AFormat: string;
  AConfig: TKConfig);
begin
  // Custom report generation logic
end;

initialization
  TKXResourceRegistry.Instance.RegisterResource(TMyReportHandler);

finalization
  TKXResourceRegistry.Instance.UnregisterResource(TMyReportHandler);

Include the unit in your application's UseKitto.pas or .dpr file — no framework modification required.

Module structure

The UseKitto.pas pattern controls which modules are active:

pascal
unit UseKitto;

interface

uses
  Kitto.Html.All,          // Core controllers (Apache 2.0)
  Kitto.Web.Enterprise,    // Enterprise modules (AGPL-3.0 / Commercial)
  Kitto.Auth.DB;           // Pluggable authenticator

implementation
end.

Without Kitto.Web.Enterprise:

  • No Chart.js, EventCalendar, or Google Maps JS loaded in the browser
  • No enterprise data handlers registered
  • No enterprise controller classes available
  • Application compiles and runs with core features only

Architecture units

UnitDescription
Kitto.Web.Routing.AttributesAttribute classes (TKXPath, TKXGET, etc.)
Kitto.Web.Routing.RegistryTKXResourceRegistry — scans RTTI, caches method info
Kitto.Web.Routing.InjectionTKXInjectionRegistry — resolves [TKXContext] by type
Kitto.Web.Routing.ActivationTKXActivation — URL matching, parameter filling, RTTI invoke
Kitto.Web.Routing.RouteTKXRoutingRoute — bridge into the existing route chain
Kitto.Web.Routing.ProvidersDefault injection providers (Request, Session, Config, etc.)
Kitto.Web.Routing.ScriptsTKXScriptRegistry — dynamic JS/CSS injection
Kitto.Web.Routing.SessionIKXSessionProvider — session transport abstraction (cookie/JWT)

See also

Released under Apache License, Version 2.0.