Skip to content

OP-04: Enable the ping route and discover pingable controllers

Field Value
Operation ID OP-04
Trigger Registration time: host calls MvcBuilderExtensions.WithPingableController(). Request time: PingableController.Get calls IControllerRetriever (OP-01 step 4).
Inputs Registration: IMvcBuilder. Request: the snapshotted application parts.
Outputs Registration: PingableController is added as an MVC application part and routed. Request: a resolved list of pingable controller instances.
Primary components MvcBuilderExtensions, ControllerRetriever, IControllerRetriever
Entry point WithPingableController(); ControllerRetriever.GetControllers

Steps

  1. At registration time, WithPingableController() adds PingableController's assembly as an AssemblyPart so MVC's application-part system treats it as a controller source, and registers IControllerRetriever as a singleton snapshotting the current application parts (BR-13).
  2. At request time, ControllerRetriever calls Assembly.Load and GetTypes() over every snapshotted application part, with no caching, to find every type implementing both IPingableService and ControllerBase (OP-01 step 4).
  3. For each discovered controller type, resolve an instance from DI by concrete type via IServiceProvider.GetService(type) (BR-14).
  4. Return the resolved controller instances to PingableController.Get, which feeds them into the same concurrent ping projection as the DI-registered services (BR-03).

Process flow

flowchart TD
    A[Host calls WithPingableController] --> B[Add PingableController assembly as AssemblyPart]
    B --> C["Register IControllerRetriever singleton -<br>snapshots application parts"]
    C --> D[Each GET /Ping request]
    D --> E["Assembly.Load + GetTypes over snapshot - no caching"]
    E --> F["Filter types implementing IPingableService + ControllerBase"]
    F --> G[Resolve each type from DI by concrete type]
    G --> H[Return resolved controllers to PingableController.Get]

Failure and edge-case behavior

  • Without WithPingableController() (or an equivalent explicit application-part registration plus a direct IControllerRetriever registration), PingableController is never added as an application part and /Ping is not routed at all (BR-13).
  • Pingable controllers are resolved by concrete type via GetService(type), which requires the host to also call AddControllersAsServices() (or make an equivalent explicit DI registration for each pingable controller's concrete type) — but only when the host actually has pingable controllers to discover. Without that registration, GetService(type) returns null, and the subsequent call on the null result throws a NullReferenceException that surfaces as a 500 through the standard failure-wrapping in OP-01 (BR-14). A host with no pingable controllers, or one using only AddPingable*/direct DI registration for its IPingableService implementations, is unaffected by this prerequisite.
  • Scanning happens on every request with no caching, which is also documented as an availability/DoS consideration in Security and authorization.

Sources

  • src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/MvcBuilderExtensions.cs
  • src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Services/ControllerRetriever.cs
  • src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Services/IControllerRetriever.cs