Zooper.Bee
3.5.0
See the version list below for details.
dotnet add package Zooper.Bee --version 3.5.0
NuGet\Install-Package Zooper.Bee -Version 3.5.0
<PackageReference Include="Zooper.Bee" Version="3.5.0" />
<PackageVersion Include="Zooper.Bee" Version="3.5.0" />
<PackageReference Include="Zooper.Bee" />
paket add Zooper.Bee --version 3.5.0
#r "nuget: Zooper.Bee, 3.5.0"
#:package Zooper.Bee@3.5.0
#addin nuget:?package=Zooper.Bee&version=3.5.0
#tool nuget:?package=Zooper.Bee&version=3.5.0
Zooper.Bee
<img src="icon.png" alt="Zooper.Bee Logo" width="120" align="right"/>
A flexible and powerful railway-oriented programming library for .NET that allows you to define complex business processes with a fluent API.
Overview
Zooper.Bee lets you create railways that process requests and produce either successful results or meaningful errors. The library uses a builder pattern to construct railways with various execution patterns including sequential, conditional, parallel, and detached operations.
Key Concepts
- Railway: A sequence of operations that process a request to produce a result or error
- Request: The input data to the railway
- Payload: Data that passes through and gets modified by railway activities
- Success: The successful result of the railway
- Error: The errors result if the railway fails
Installation
dotnet add package Zooper.Bee
Getting Started
// Define a simple railway
var railway = Railway.Create<Request, Payload, SuccessResult, ErrorResult>(
// Factory function that creates the initial payload from the request
factory: request => new Payload { Data = request.Data },
// Selector function that creates the success result from the final payload
selector: payload => new SuccessResult { ProcessedData = payload.Data },
// Step execution phase
steps: s => s
.Validate(request =>
{
if (string.IsNullOrEmpty(request.Data))
return Option<ErrorResult>.Some(new ErrorResult { Message = "Data is required" });
return Option<ErrorResult>.None;
})
.Do(payload =>
{
payload.Data = payload.Data.ToUpper();
return Either<ErrorResult, Payload>.FromRight(payload);
})
);
// Execute the railway
var result = await railway.Execute(new Request { Data = "hello world" }, CancellationToken.None);
if (result.IsRight)
{
Console.WriteLine($"Success: {result.Right.ProcessedData}"); // Output: Success: HELLO WORLD
}
else
{
Console.WriteLine($"Error: {result.Left.Message}");
}
Building Railways
Railways are created with Railway.Create(), which takes two separate configuration lambdas:
guards— optional; declares guards and validations that run before the payload is createdsteps— required; declares all activities that transform the payload
This two-phase separation makes it structurally impossible to mix guard registration with step
registration. Guard() and Validate() are not available inside steps, and Do()/Group()/etc.
are not available inside guards.
var railway = Railway.Create<Request, Payload, Success, Error>(
factory: request => new Payload(request),
selector: payload => new Success(payload.Result),
guards: g => g
.Guard(request => /* auth check */)
.Validate(request => /* input validation */),
steps: s => s
.Do(payload => /* step 1 */)
.Group(null, g => g
.Do(payload => /* step 2a */)
.Do(payload => /* step 2b */))
.Do(payload => /* step 3 */)
);
When no guards are needed, omit the guards parameter:
var railway = Railway.Create<Request, Payload, Success, Error>(
factory: request => new Payload(request),
selector: payload => new Success(payload.Result),
steps: s => s
.Do(payload => /* ... */)
);
Validation
Validations run before any step and reject the request early when invalid.
// Asynchronous validation
.Validate(async (request, cancellationToken) =>
{
var isValid = await ValidateAsync(request, cancellationToken);
return isValid ? Option<ErrorResult>.None : Option<ErrorResult>.Some(new ErrorResult());
})
// Synchronous validation
.Validate(request =>
{
var isValid = Validate(request);
return isValid ? Option<ErrorResult>.None : Option<ErrorResult>.Some(new ErrorResult());
})
Guards
Guards check whether the railway is allowed to execute at all — authentication,
authorization, feature flags, etc. They always run before any step, regardless of
where they appear in the guards lambda.
guards: g => g
// Asynchronous guard
.Guard(async (request, cancellationToken) =>
{
var isAuthorized = await CheckAuthorizationAsync(request, cancellationToken);
return isAuthorized
? Either<ErrorResult, Unit>.FromRight(Unit.Value)
: Either<ErrorResult, Unit>.FromLeft(new ErrorResult { Message = "Unauthorized" });
})
// Synchronous guard
.Guard(request =>
{
var isAuthorized = CheckAuthorization(request);
return isAuthorized
? Either<ErrorResult, Unit>.FromRight(Unit.Value)
: Either<ErrorResult, Unit>.FromLeft(new ErrorResult { Message = "Unauthorized" });
})
Benefits of Guards
- Guards run before the payload is created, providing the earliest possible short-circuit
- The
guardsphase is structurally separate from thestepsphase — it is impossible to accidentally register a guard after a step - Common checks like authentication can be standardized and reused
Activities
Activities are the building blocks of a railway. They process the payload and can produce either a success (with the modified payload) or an error.
// Asynchronous activity
.Do(async (payload, cancellationToken) =>
{
var result = await ProcessAsync(payload, cancellationToken);
return Either<ErrorResult, Payload>.FromRight(result);
})
// Synchronous activity
.Do(payload =>
{
var result = Process(payload);
return Either<ErrorResult, Payload>.FromRight(result);
})
// Multiple activities
.DoAll(
payload => DoFirstThing(payload),
payload => DoSecondThing(payload),
payload => DoThirdThing(payload)
)
Conditional Activities
Activities that only execute if a condition is met.
.DoIf(
payload => payload.ShouldProcess, // Condition
payload =>
{
// Activity that only executes if the condition is true
payload.Data = Process(payload.Data);
return Either<ErrorResult, Payload>.FromRight(payload);
}
)
Groups
Organize related activities into logical groups. Groups can have conditions and always merge their results back to the main railway.
.Group(
payload => payload.ShouldProcessGroup, // Optional condition
group => group
.Do(payload => FirstActivity(payload))
.Do(payload => SecondActivity(payload))
.Do(payload => ThirdActivity(payload))
)
Contexts with Local State
Create a context with the local state that is accessible to all activities within the context. This helps encapsulate related operations.
.WithContext(
null, // No condition, always execute
payload => new LocalState { Counter = 0 }, // Create local state
context => context
.Do((payload, state) =>
{
state.Counter++;
return (payload, state);
})
.Do((payload, state) =>
{
payload.Result = $"Counted to {state.Counter}";
return (payload, state);
})
)
Parallel Execution
Execute multiple groups of activities in parallel and merge the results.
.Parallel(
null, // No condition, always execute
parallel => parallel
.Group(group => group
.Do(payload => { payload.Result1 = "Result 1"; return payload; })
)
.Group(group => group
.Do(payload => { payload.Result2 = "Result 2"; return payload; })
)
)
Detached Execution
Execute activities in the background without waiting for their completion. Results from detached activities are not merged back into the main railway.
.Detach(
null, // No condition, always execute
detached => detached
.Do(payload =>
{
// This runs in the background
LogActivity(payload);
return payload;
})
)
Parallel Detached Execution
Execute multiple groups of detached activities in parallel without waiting for completion.
.ParallelDetached(
null, // No condition, always execute
parallelDetached => parallelDetached
.Detached(detached => detached
.Do(payload => { LogActivity1(payload); return payload; })
)
.Detached(detached => detached
.Do(payload => { LogActivity2(payload); return payload; })
)
)
Finally Block
Activities that always execute, even if the railway fails.
.Finally(payload =>
{
// Cleanup or logging
CleanupResources(payload);
return Either<ErrorResult, Payload>.FromRight(payload);
})
Advanced Patterns
Error Handling
.Do(payload =>
{
try
{
var result = RiskyOperation(payload);
return Either<ErrorResult, Payload>.FromRight(result);
}
catch (Exception ex)
{
return Either<ErrorResult, Payload>.FromLeft(new ErrorResult { Message = ex.Message });
}
})
Conditional Branching
Use conditions to determine which path to take in a railway.
.Group(
payload => payload.Type == "TypeA",
group => group
.Do(payload => ProcessTypeA(payload))
)
.Group(
payload => payload.Type == "TypeB",
group => group
.Do(payload => ProcessTypeB(payload))
)
Dependency Injection Integration
Zooper.Bee integrates seamlessly with .NET's dependency injection system. You can register all railway components with a single extension method:
// In Startup.cs or Program.cs
services.AddRailways();
This will scan all assemblies and register:
- All railway validations
- All railway activities
- All concrete railway classes (classes ending with "Railway")
You can also register specific components:
// Register only validations
services.AddRailwayValidations();
// Register only activities
services.AddRailwayActivities();
// Specify which assemblies to scan
services.AddRailways(new[] { typeof(Program).Assembly });
// Specify service lifetime (Singleton, Scoped, Transient)
services.AddRailways(lifetime: ServiceLifetime.Singleton);
Performance Considerations
- Use
Parallelfor CPU-bound operations that can benefit from parallel execution - Use
Detachfor I/O operations that don't affect the main railway - Be mindful of resource contention in parallel operations
- Consider using
WithContextto maintain state between related activities
Best Practices
- Keep activities small and focused on a single responsibility
- Use descriptive names for your railway methods
- Group related activities together
- Handle errors at appropriate levels
- Use
Finallyfor cleanup operations - Validate requests early to fail fast
- Use contextual state to avoid passing too many parameters
Migration from RailwayBuilder to Railway.Create()
As of the latest version, RailwayBuilder and RailwayBuilderFactory are [Obsolete].
Use Railway.Create() instead.
Before
var railway = new RailwayBuilder<Request, Payload, Success, Error>(
request => new Payload(request),
payload => new Success(payload.Result))
.Guard(request => /* ... */)
.Validate(request => /* ... */)
.Do(payload => /* ... */)
.Group(null, g => g.Do(payload => /* ... */))
.Build();
After
var railway = Railway.Create<Request, Payload, Success, Error>(
factory: request => new Payload(request),
selector: payload => new Success(payload.Result),
guards: g => g
.Guard(request => /* ... */)
.Validate(request => /* ... */),
steps: s => s
.Do(payload => /* ... */)
.Group(null, g => g.Do(payload => /* ... */)));
Migration from Workflow to Railway
As of the latest version, all Workflow classes have been renamed to Railway to better reflect the railway-oriented programming pattern used by the library. The old Workflow names are preserved as [Obsolete] shims for backward compatibility.
What changed
| Old Name | New Name |
|---|---|
Workflow<TRequest, TSuccess, TError> |
Railway<TRequest, TSuccess, TError> |
WorkflowBuilder<...> |
RailwayBuilder<...> |
WorkflowBuilderFactory |
RailwayBuilderFactory |
CreateWorkflow<...>() |
CreateRailway<...>() |
IWorkflowStep |
IRailwayStep |
IWorkflowValidation |
IRailwayValidation |
IWorkflowGuard |
IRailwayGuard |
AddWorkflows() |
AddRailways() |
AddWorkflowSteps() |
AddRailwaySteps() |
Backward compatibility
All old type names and extension methods are still available but marked with [Obsolete]. Your existing code will continue to compile and work, but you will see deprecation warnings encouraging you to migrate to the new names.
How to migrate
- Replace all
Workflow<type references withRailway< - Replace
WorkflowBuilder<withRailwayBuilder< - Replace
WorkflowBuilderFactory.CreateWorkflow<withRailwayBuilderFactory.CreateRailway< - Replace DI registration calls (
AddWorkflows()→AddRailways(), etc.) - Update any interface implementations (
IWorkflowStep→IRailwayStep, etc.)
License
MIT License (Copyright details here)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Scrutor (>= 4.2.2)
- Zooper.Fox (>= 1.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Zooper.Bee:
| Package | Downloads |
|---|---|
|
Zooper.Bee.MediatR
A .NET library for building robust, functional railways (processing pipelines) using railway-oriented programming. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.1.0 | 42 | 5/31/2026 |
| 4.0.0 | 175 | 4/21/2026 |
| 3.5.0 | 203 | 4/1/2026 |
| 3.4.1 | 140 | 3/21/2026 |
| 3.4.0 | 154 | 3/10/2026 |
| 3.4.0-preview.12 | 61 | 3/10/2026 |
| 3.3.0 | 658 | 4/24/2025 |
| 3.3.0-preview.11 | 66 | 3/10/2026 |
| 3.3.0-preview.10 | 67 | 3/10/2026 |
| 3.2.1 | 267 | 4/24/2025 |
| 3.2.0 | 266 | 4/24/2025 |
| 3.1.0 | 217 | 4/23/2025 |
| 3.0.0 | 242 | 4/21/2025 |
| 2.2.0 | 233 | 4/21/2025 |
| 2.1.0 | 236 | 4/21/2025 |
| 2.0.0 | 161 | 4/19/2025 |
| 1.0.0 | 240 | 4/18/2025 |