---
title: Service registration
summary: Services, options, startup base classes, middleware, and hosted services this library contributes to a consuming application.
status: draft
owner: SAIF Platform Team
last_reviewed: 2026-10-02
library: SAIF.NetCore.PingableService
reference_type: package
---

# Service registration

This library exposes three `IServiceCollection` extension methods (`AddPingableTransient<TService, TImplementation>`, `AddPingableScoped<TService, TImplementation>`, `AddPingableSingleton<TService, TImplementation>`) that dual-register an application's own pingable services, and one `IMvcBuilder` extension (`WithPingableController()`) that a host can call to enable the `GET /Ping` route and controller-based ping discovery in one step. `WithPingableController()` plus the host separately calling `AddControllersAsServices()` is the **supported convenience path**, not the only possible wiring: the `/Ping` route itself needs only `PingableController`'s assembly present as an MVC application part and an `IControllerRetriever` registered (both of which `WithPingableController()` provides in one call, and which an equivalent explicit application-part registration plus a direct `IControllerRetriever` registration can also satisfy), and `AddControllersAsServices()` is only required when the host has pingable *controllers* it wants resolved through the controller-discovery channel — plain DI-registered `IPingableService` services never need it. `SAIF.NetCore.ServiceExtensions`'s `BaseApiStartup.ConfigureMvcBuilder` makes both convenience calls automatically for any host that inherits it.

## Registration extension catalog

| Extension method | Hosting context | Parameters | Source |
| --- | --- | --- | --- |
| `AddPingableTransient<TService, TImplementation>()` | API, worker, or console host (any `IServiceCollection` consumer) | `this IServiceCollection services`; `TService : class, IPingableService`; `TImplementation : class, TService, IPingableService` | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs` |
| `AddPingableScoped<TService, TImplementation>()` | API, worker, or console host | `this IServiceCollection services`; same constraints as above | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs` |
| `AddPingableSingleton<TService, TImplementation>()` | API, worker, or console host | `this IServiceCollection services`; same constraints as above | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs` |
| `WithPingableController()` | API host (MVC) | `this IMvcBuilder builder` | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/MvcBuilderExtensions.cs` |

Each `AddPingable*` method returns `IServiceCollection` (via its final `.AddTransient`/`.AddScoped`/`.AddSingleton` call) and `WithPingableController()` returns `IMvcBuilder`, so all four extensions are chainable with the rest of the host's DI/MVC setup.

## Service registration catalog

| Service type | Implementation | Lifetime | Registered by | Condition | Source |
| --- | --- | --- | --- | --- | --- |
| `TService` | `TImplementation` | Transient | `AddPingableTransient<TService, TImplementation>` | Always (per call) | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs` |
| `IPingableService` | factory: `x => (TImplementation)x.GetRequiredService<TService>()` | Transient | `AddPingableTransient<TService, TImplementation>` | Always (per call) | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs` |
| `TService` | `TImplementation` | Scoped | `AddPingableScoped<TService, TImplementation>` | Always (per call) | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs` |
| `IPingableService` | factory: `x => (TImplementation)x.GetRequiredService<TService>()` | Scoped | `AddPingableScoped<TService, TImplementation>` | Always (per call) | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs` |
| `TService` | `TImplementation` | Singleton | `AddPingableSingleton<TService, TImplementation>` | Always (per call) | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs` |
| `IPingableService` | factory: `x => (TImplementation)x.GetRequiredService<TService>()` | Singleton | `AddPingableSingleton<TService, TImplementation>` | Always (per call) | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs` |
| `IControllerRetriever` | factory: `x => new ControllerRetriever(names)`, where `names` is the list of `ApplicationPartManager.ApplicationParts` names snapshotted at the time `WithPingableController()` runs | Singleton | `WithPingableController()` | Always, once per call | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/MvcBuilderExtensions.cs` |

The `IPingableService` factory registration always resolves through the parallel `TService` registration and casts the result to `TImplementation`, so for Transient the two registrations yield independent instances per resolution, while for Scoped and Singleton the `IPingableService` entry shares the identical instance that `TService` resolves to within that scope or for the application's lifetime — there is no separate forwarding factory a host must supply ([BR-15](business-rules.md#br-15-ipingableservice-resolution-casts-through-tservice-registration)). `WithPingableController()` also adds this library's assembly as an MVC `AssemblyPart` (not a DI registration) so `PingableController` itself is discoverable by MVC's application-part system; see [OP-04](operations/op-04-enable-route-and-discover-controllers.md).

## Host endpoint mapping

| Route | Verb | Controller / action | Authorization | Registered by | Source |
| --- | --- | --- | --- | --- | --- |
| `/Ping` (optionally `?guid=<runId>`) | `GET` | `PingableController.Get` | `[AllowAnonymous]` (none) | `WithPingableController()` (application part) + standard ASP.NET Core MVC routing | `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Controllers/PingableController.cs` |

`WithPingableController()` does not itself call MVC's route-mapping; it makes `PingableController` discoverable to MVC by adding its assembly as an `AssemblyPart`. Standard MVC attribute routing (`[Route("Ping")]`, `[HttpGet]` on `PingableController`) then maps `GET /Ping` once the host's own `MapControllers()`/`UseEndpoints()` pipeline runs.

## Options configuration

None. This library defines no `IOptions<T>`/`IConfigureOptions<T>` type and reads no configuration of its own.

## Startup base classes

None directly in this library. However, `SAIF.NetCore.ServiceExtensions`'s `BaseApiStartup.ConfigureMvcBuilder` calls `builder.WithPingableController().AddControllersAsServices()` for every host that inherits it, which satisfies both prerequisites below automatically (`src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/BaseApiStartup.cs`).

## Feature flags

None.

## Middleware pipeline

None. This library adds no request-pipeline middleware; it only contributes an MVC application part and a controller.

## Hosted services and health checks

None.

## Controller-as-service prerequisite

`ControllerRetriever.GetControllers` resolves each discovered pingable-controller type from DI **by concrete type** (`provider.GetService(type)`), not through MVC's controller-activation path. This only succeeds, for a host that has pingable controllers to discover, if the host has separately called `AddControllersAsServices()` (or made an equivalent explicit DI registration for each pingable controller's concrete type) — `WithPingableController()` does not do this for the host, and does not call it for its own `PingableController` either. Without it, resolution returns `null`, and the subsequent call on that `null` result throws a `NullReferenceException` that surfaces as a 500 via the standard failure wrapping in [OP-01](operations/op-01-ping-the-api.md) ([BR-14](business-rules.md#br-14-controller-discovery-requires-controllers-registered-as-services)). This prerequisite applies only to the controller-discovery channel; a host with no pingable controllers, or one that registers pingable services exclusively through `AddPingable*`/direct DI registration, is unaffected by it. `BaseApiStartup.ConfigureMvcBuilder` pairs the two convenience calls (`WithPingableController().AddControllersAsServices()`), so hosts built on it satisfy this prerequisite without extra code.

## Required-but-not-registered services

| Service type | Consumer | Expected source |
| --- | --- | --- |
| `ISaifLogService` (optional) | `PingableController` constructor (`provider.GetService<ISaifLogService>()`) | Host application, if logging is desired; absence is tolerated — the controller falls back to a `null` logger ([BR-12](business-rules.md#br-12-logging-is-optional)) |
| `AddControllersAsServices()` registration behavior | `ControllerRetriever.GetControllers` | Host application's MVC builder configuration (see [Controller-as-service prerequisite](#controller-as-service-prerequisite) above) |

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Security and authorization](security-authorization.md)
- [API reference](api/index.md)

## Sources
- `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/IServiceCollectionExtensions.cs`
- `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Extensions/MvcBuilderExtensions.cs`
- `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Controllers/PingableController.cs`
- `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Services/ControllerRetriever.cs`
- `src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/Services/IControllerRetriever.cs`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/BaseApiStartup.cs`
