AddServices

Stores DI services into Memory so that subsequent junctions can access them. Services are stored by their interface type (the generic type parameter), not their concrete type.

Has overloads for 1 through 7 services.

Signatures

protected Monad<TInput, TReturn> AddServices<T1>(T1 service)
protected Monad<TInput, TReturn> AddServices<T1, T2>(T1 s1, T2 s2)
protected Monad<TInput, TReturn> AddServices<T1, T2, T3>(T1 s1, T2 s2, T3 s3)
protected Monad<TInput, TReturn> AddServices<T1, T2, T3, T4>(T1 s1, T2 s2, T3 s3, T4 s4)
protected Monad<TInput, TReturn> AddServices<T1, T2, T3, T4, T5>(T1 s1, T2 s2, T3 s3, T4 s4, T5 s5)
protected Monad<TInput, TReturn> AddServices<T1, T2, T3, T4, T5, T6>(T1 s1, T2 s2, T3 s3, T4 s4, T5 s5, T6 s6)
protected Monad<TInput, TReturn> AddServices<T1, T2, T3, T4, T5, T6, T7>(T1 s1, T2 s2, T3 s3, T4 s4, T5 s5, T6 s6, T7 s7)

These are the train's own methods, which start a chain in Junctions(). Later in a chain, after a Chain or ShortCircuit, the call is MonadTask<TInput, TReturn>'s method of the same name, which takes the same arguments and returns MonadTask<TInput, TReturn>.

Type Parameters

Each T1 through T7 should be an interface type. The service is stored in Memory under this interface type, enabling junctions to resolve it by interface.

Parameters

Each service / s1..s7 is a service instance that implements the corresponding type parameter interface.

All services are required (non-null), and each has to exist when Junctions() runs. Passing null throws an Exception naming the service's type. While the startup check reads the chain, a null is recorded as a refusal instead, and the host will not start: a service field assigned later, in OnStarted for example, is not supported.

Returns

The chain, for fluent chaining: Monad<TInput, TReturn> from the train's method, MonadTask<TInput, TReturn> from a later link.

Example

public class ProcessOrderTrain(
    IPaymentGateway paymentGateway,
    IInventoryService inventoryService,
    INotificationService notificationService
) : ServiceTrain<OrderInput, OrderResult>
{
    protected override Task<Either<Exception, OrderResult>> Junctions() =>
        AddServices<IPaymentGateway, IInventoryService, INotificationService>(
                paymentGateway, inventoryService, notificationService)
            .Chain<ValidateInventory>()    // Can access IInventoryService from Memory
            .Chain<ChargePayment>()        // Can access IPaymentGateway from Memory
            .Chain<SendReceipt>().Resolve();         // Can access INotificationService from Memory
}

Behavior

  1. Stores each service under the type parameter in its own position: AddServices<IReader, IWriter>(store, store) fills both the IReader and the IWriter slot with store, whatever else store implements.
  2. The chain check records the same slots, so what it verifies is what the run fills.
  3. If a service is null, sets the train exception to a TrainException naming the type argument and, when several services are passed, its position (AddServices<IWriter> received null for the service at position 2 of 2. A service cannot be null.). None of that call's services is stored, and the train fails like any other invalid argument rather than throwing out of Junctions(). While a chain is being read, records a refusal instead.
  4. If a service's concrete type is not a class, sets the train exception. A struct passed as an interface is also refused while the chain is read.
  5. If a service does not implement the interface it was passed as, sets the train exception.

Moq Proxy Handling

AddServices has special handling for Moq mock objects. If a service is detected as a Moq proxy (e.g., Mock<IMyService>().Object), it's stored under the mocked interface type rather than the proxy's concrete type. This enables seamless testing with mocked dependencies.

Remarks

  • Use interface types as the generic parameters (AddServices<IMyService>(myService), not AddServices<MyService>(myService)).
  • Junctions resolve services from Memory by their interface type during construction. See Junctions for how constructor injection works.
  • For more than 7 services, split across multiple AddServices calls.