ITrainDiscoveryService
Package Trax.MediatorNamespace Trax.Mediator.Services.TrainDiscoverySource ITrainDiscoveryService.cs, TrainDiscoveryService.cs, TrainRegistration.cs
Lists every IServiceTrain<TIn, TOut> registered in the service collection, one
TrainRegistration per train. The dashboard lists trains from it, the API builds its schema from
it, and ITrainExecutionService looks trains up by name in it. AddMediator() registers the
default implementation, TrainDiscoveryService, as a singleton; it reads the IServiceCollection,
so it also works before the container is built, and is resolved through the interface.
Signature
namespace Trax.Mediator.Services.TrainDiscovery;
public interface ITrainDiscoveryService
{
IReadOnlyList<TrainRegistration> DiscoverTrains();
}Members
| Member | Description |
|---|---|
DiscoverTrains() | Every registered train, as a cached list |
TrainRegistration | The record describing one train |
DiscoverTrains
Returns: IReadOnlyList<TrainRegistration>, one per train. Two trains that take the same input
type are both listed. The list is computed on the first call and the same list is returned after.
Throws: TrainException when two different classes are registered under one class service
type, such as AddTransient<BaseTrain, A>() and AddTransient<BaseTrain, B>(). A train is found by
the name of the type it is registered under, and the container runs only the last registration, so
the train that name describes would not be the train that runs. The same class registered twice is
listed once and is not refused. The startup checks call DiscoverTrains, so a host with such a
registration fails to start:
2 trains are registered under My.BaseTrain: My.A, My.B. A train is found by the name of the type it is registered under, and the container runs only the last registration, so the train that name describes would not be the train that runs. Register each train under its own interface or class.Before Trax.Mediator 1.23.2 discovery listed one train per input type. Trains that were hidden
behind another train with the same input type now appear in getTrains, in the startup checks and
in runs by name, including Trax.Scheduler's own JobDispatcherTrain and ManifestManagerTrain.
TrainRegistration
public class TrainRegistration
{
public required Type ServiceType { get; init; }
public required Type ImplementationType { get; init; }
public required Type InputType { get; init; }
public required Type OutputType { get; init; }
public required ServiceLifetime Lifetime { get; init; }
public required string ServiceTypeName { get; init; }
public required string ImplementationTypeName { get; init; }
public required string InputTypeName { get; init; }
public required string OutputTypeName { get; init; }
public required IReadOnlyList<string> RequiredPolicies { get; init; }
public required IReadOnlyList<string> RequiredRoles { get; init; }
public IReadOnlyList<IReadOnlyList<string>> RequiredRoleSets { get; init; }
public bool HasAuthorizeAttribute { get; init; }
public bool HasAllowAnonymousAttribute { get; init; }
public bool HasQueueSubjectKey { get; init; }
public required bool IsQuery { get; init; }
public required bool IsMutation { get; init; }
public required bool IsBroadcastEnabled { get; init; }
public bool IsRunProgressEnabled { get; init; }
public bool RunProgressAnswers { get; init; }
public required bool IsRemote { get; init; }
public string? GraphQLName { get; init; }
public string? GraphQLDescription { get; init; }
public string? GraphQLDeprecationReason { get; init; }
public required GraphQLOperation GraphQLOperations { get; init; }
public string? GraphQLNamespace { get; init; }
public int? MaxConcurrentRun { get; init; }
}Properties
| Property | Type | Description |
|---|---|---|
ServiceType | Type | The interface or class the train is registered under (for example IProcessOrderTrain). Its FullName is the train's canonical name |
ImplementationType | Type | The concrete class (for example ProcessOrderTrain) |
InputType | Type | The TInput of IServiceTrain<TInput, TOutput> |
OutputType | Type | The TOutput; Unit for a train with no output |
Lifetime | ServiceLifetime | The DI lifetime the train was registered with |
ServiceTypeName | string | Display name of ServiceType (for example IServiceTrain<OrderInput, OrderResult>), the friendly name a train can also be looked up by |
ImplementationTypeName | string | Display name of ImplementationType |
InputTypeName | string | Display name of InputType |
OutputTypeName | string | Display name of OutputType |
RequiredPolicies | IReadOnlyList<string> | Policy names from [TraxAuthorize] attributes on the implementation class. Empty when none |
RequiredRoles | IReadOnlyList<string> | Every role any [TraxAuthorize(Roles = "...")] attribute names, as declared: the union of the attributes, not the requirement. Empty when none |
RequiredRoleSets | IReadOnlyList<IReadOnlyList<string>> | The role requirement, described below. Added in Trax.Mediator 1.25.0 |
HasAuthorizeAttribute | bool | Whether the train carries any [TraxAuthorize], the bare form included |
HasAllowAnonymousAttribute | bool | Whether the train carries [TraxAllowAnonymous]. Carries no runtime gate of its own |
HasQueueSubjectKey | bool | Whether the implementation overrides QueueSubjectKey, directly or through a base class |
IsQuery | bool | Whether the train has [TraxQuery], which exposes it as a typed GraphQL query |
IsMutation | bool | Whether the train has [TraxMutation], which exposes it as typed GraphQL mutations |
IsBroadcastEnabled | bool | Whether the train has [TraxBroadcast], which broadcasts its lifecycle events to subscribers |
IsRunProgressEnabled | bool | Whether the train has [TraxRunProgress], which lets a run's starter follow it |
RunProgressAnswers | bool | The attribute's Answers: whether the starter sees a step's answer and decision track |
IsRemote | bool | Whether the implementation class has [TraxRemote]. A route set with ForTrain<T>() on the scheduler builder takes precedence; with no remote submitter configured the train runs locally |
GraphQLName | string? | The name from [TraxQuery(Name = ...)] or [TraxMutation(Name = ...)]; null means derived |
GraphQLDescription | string? | The description of the generated GraphQL fields |
GraphQLDeprecationReason | string? | When set, the generated fields are marked deprecated |
GraphQLOperations | GraphQLOperation | Which mutation operations (Run, Queue, or both) are generated. Applies only when IsMutation is true. Defaults to Run |
GraphQLNamespace | string? | The sub-namespace the train's GraphQL field is grouped under (for example discover { alerts { field } }) |
MaxConcurrentRun | int? | The limit from [TraxConcurrencyLimit], or null. ConcurrentRunLimit<T>() on the mediator builder takes precedence |
RequiredRoleSets holds one set per [TraxAuthorize] attribute that names roles, de-duplicated.
The caller must hold at least one role of every set, so separate attributes combine with AND, as
separate [Authorize] attributes do in ASP.NET Core; roles inside one attribute combine with OR.
Class Roles = "Admin" and interface Roles = "Support" is two sets, and a Support-only caller is
refused. On a registration built by hand without it, or with it set empty while RequiredRoles
names roles, it reads as RequiredRoles in a single set, so listed roles are never read as no
requirement.
On a GraphQL-exposed train, HasAllowAnonymousAttribute is the explicit "intentionally public"
marker that satisfies the exposure check, and it is mutually exclusive with
HasAuthorizeAttribute. HasQueueSubjectKey true means the train can stamp a subject key on its
queue entries, not that every entry has one: the override may return null for a given input. The
enqueue uses the same check to decide whether to ask the train for a key.
Behavior
DiscoverTrains builds the list as follows:
- Reads every
ServiceDescriptorin theIServiceCollectionand keeps those whose service type, or one of its interfaces, is a closedIServiceTrain<,>. - Pairs each train's two registrations.
AddScopedTraxRouteregisters bothTImplementationandTService, and the pair becomes one registration with the interface asServiceTypeand the class asImplementationType. The interface is the train's own (the one deriving fromIServiceTrain<,>, as AddMediator selects it), and the two are paired only when resolving the interface yields that class: the interface's descriptor names the class, or no other registered class implements the interface. A class registered with no interface of its own is listed under the class, and two different classes registered under the same class service type are refused (see DiscoverTrains). - Takes
InputTypeandOutputTypefrom the generic arguments ofServiceType. - Lists every train, including trains that share an input type. Pairing is per train, never per
input type, so a registration's requirements and attributes are always read from the class its
ServiceTyperesolves to. - Reads
[TraxAuthorize]from the implementation type intoRequiredPolicies,RequiredRolesandRequiredRoleSets(one set per attribute) and setsHasAuthorizeAttribute; reads[TraxAllowAnonymous], across the base chain and interfaces, intoHasAllowAnonymousAttribute. Discovery is permissive; the mutual-exclusion and exposure checks run at host startup. - Reads
[TraxQuery]and[TraxMutation]intoIsQuery,IsMutation,GraphQLName,GraphQLDescription,GraphQLDeprecationReason,GraphQLOperationsandGraphQLNamespace;[TraxBroadcast]intoIsBroadcastEnabled;[TraxRunProgress]intoIsRunProgressEnabledandRunProgressAnswers;[TraxRemote]intoIsRemote;[TraxConcurrencyLimit]intoMaxConcurrentRun. - Checks whether the implementation overrides
QueueSubjectKeyand setsHasQueueSubjectKey. - Caches the result for the lifetime of the service.
Example
[ApiController]
public class TrainListController(ITrainDiscoveryService discovery) : ControllerBase
{
[HttpGet("trains")]
public IActionResult GetTrains() =>
Ok(discovery.DiscoverTrains().Select(t => new
{
t.ServiceTypeName,
t.ImplementationTypeName,
t.InputTypeName,
t.OutputTypeName,
Lifetime = t.Lifetime.ToString(),
}));
}See also
- Train discovery and routing, how discovery and lookup by name fit together
- ITrainExecutionService, which looks trains up here
- AddMediator, which registers the trains