|
| 1 | +--- |
| 2 | +sidebar_position: 7 |
| 3 | +title: Manual Composition |
| 4 | +--- |
| 5 | + |
| 6 | +# Manual Composition |
| 7 | + |
| 8 | +PipeForge is designed to integrate seamlessly with dependency injection, but it's flexible enough to support manual composition. This can be useful in testing scenarios, minimal environments, or when you need full control over pipeline configuration. |
| 9 | + |
| 10 | +The main drawbacks of this approach are that you'll be responsible for manually instantiating all dependencies for each step, and the resulting pipeline may not accurately reflect the behavior of your production configuration. |
| 11 | + |
| 12 | +## Creating a Pipeline |
| 13 | + |
| 14 | +Manual pipeline composition is straightforward using the fluent API exposed by `PipelineBuilder<T>`, which is returned from the static method `Pipeline.CreateFor<T>()`. You can optionally pass an `ILoggerFactory` to enable logging during execution by the resulting `PipelineRunner<T>`. |
| 15 | + |
| 16 | +Add steps to the pipeline in the desired execution order using the fluent, chainable methods: |
| 17 | + |
| 18 | +- `Add<TStep>()` - for steps with a parameterless constructor |
| 19 | +- `AddStep<TStep>(Func<TStep> factory)` - for steps that require constructor parameters |
| 20 | + |
| 21 | +```csharp title="Steps Used" |
| 22 | +private abstract class TestStep : PipelineStep<SampleContext> |
| 23 | +{ |
| 24 | + public override async Task InvokeAsync( |
| 25 | + SampleContext context, |
| 26 | + PipelineDelegate<SampleContext> next, |
| 27 | + CancellationToken cancellationToken = default) |
| 28 | + { |
| 29 | + context.AddStep(Name); |
| 30 | + await next(context, cancellationToken); |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +private class StepA : TestStep |
| 35 | +{ |
| 36 | + public StepA() => Name = "A"; |
| 37 | +} |
| 38 | + |
| 39 | +private class StepB : TestStep |
| 40 | +{ |
| 41 | + public StepB() => Name = "B"; |
| 42 | +} |
| 43 | + |
| 44 | +private class StepC : TestStep |
| 45 | +{ |
| 46 | + public StepC() => Name = "C"; |
| 47 | +} |
| 48 | + |
| 49 | +private class StepD : TestStep |
| 50 | +{ |
| 51 | + public StepD(string name) => Name = name; |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +Note that `StepD` lacks a parameterless constructor, which means it can't be added using `Add<TStep>()`. Instead, you'll need to use `AddStep<TStep>(Func<TStep>)` to manually supply its dependencies - for example, if the step requires a configuration value, a logger, or a service instance. |
| 56 | + |
| 57 | +```csharp title="Manual Pipeline Setup" |
| 58 | +var stepName = "Hello"; |
| 59 | +var pipeline = Pipeline.CreateFor<SampleContext>() |
| 60 | + .WithStep<StepA>() |
| 61 | + .WithStep<StepB>() |
| 62 | + .WithStep<StepC>() |
| 63 | + .WithStep(() => new StepD(stepName)) |
| 64 | + .Build(); |
| 65 | + |
| 66 | +var context = new SampleContext(); |
| 67 | +await pipeline.ExecuteAsync(context); |
| 68 | + |
| 69 | +Console.WriteLine(context); |
| 70 | +// Should be: |
| 71 | +// A,B,C,Hello |
| 72 | +``` |
| 73 | + |
| 74 | +## Advanced Scenarios |
| 75 | + |
| 76 | +In more advanced scenarios, the factory delegate can create a scope and resolve dependencies from the scoped service provider before constructing the step. This is especially useful when the step relies on services with scoped lifetimes, such as per-request context or transient infrastructure components. For example: |
| 77 | + |
| 78 | +``` |
| 79 | +builder.WithStep(() => |
| 80 | +{ |
| 81 | + using var scope = serviceProvider.CreateScope(); |
| 82 | + var scopedProvider = scope.ServiceProvider; |
| 83 | + var name = scopedProvider.GetRequiredService<IOptions<MySettings>>().Value.Name; |
| 84 | + return new StepD(name); |
| 85 | +}); |
| 86 | +``` |
| 87 | + |
| 88 | +## Conclusion |
| 89 | + |
| 90 | +Manual composition allows you to build and run a pipeline without relying on a DI container. While this approach is less common for production use, it provides maximum control for configuration, **testing** and minimal-host scenarios. |
0 commit comments