---
title: "OP-04: Enable the ping route and discover pingable controllers"
summary: Registration-time route/application-part wiring and per-request reflection-based controller discovery.
status: draft
owner: SAIF Platform Team
last_reviewed: 2026-10-02
library: SAIF.NetCore.PingableService
reference_type: package
operation_id: OP-04
trigger: Host startup code calls WithPingableController(); then, each request, PingableController.Get calls IControllerRetriever.
---

# 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](op-01-ping-the-api.md) 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](../business-rules.md#br-13-withpingablecontroller-enables-route-and-controller-discovery)).
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](op-01-ping-the-api.md) step 4).
3. For each discovered controller type, resolve an instance from DI by concrete type via `IServiceProvider.GetService(type)` ([BR-14](../business-rules.md#br-14-controller-discovery-requires-controllers-registered-as-services)).
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](../business-rules.md#br-03-ping-set-defined-by-di-registration-and-mvc-application-parts)).

## Process flow

```mermaid
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](../business-rules.md#br-13-withpingablecontroller-enables-route-and-controller-discovery)).
- 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](op-01-ping-the-api.md) ([BR-14](../business-rules.md#br-14-controller-discovery-requires-controllers-registered-as-services)). 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](../security-authorization.md).

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Security and authorization](../security-authorization.md)

## 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`
