├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── LICENSE ├── NetWorkflow.Extensions ├── Extensions.cs └── NetWorkflow.Extensions.csproj ├── NetWorkflow.Tests ├── Examples │ ├── ConditionalParallelWorkflow.cs │ ├── ConditionalStopWorkflow.cs │ ├── ConditionalThrowWorkflow.cs │ ├── ConditionalWorkflow.cs │ ├── HelloWorldWorkflow.cs │ ├── ParallelWorkflow.cs │ └── RetryExample.cs ├── NetWorkflow.Tests.csproj ├── Usings.cs ├── WorkflowScheduler_Tests.cs ├── WorkflowTime_Tests.cs └── Workflow_Tests.cs ├── NetWorkflow.sln ├── NetWorkflow ├── Exceptions │ ├── WorkflowInvalidValueException.cs │ ├── WorkflowMaxRetryException.cs │ ├── WorkflowNoConditionMetException.cs │ └── WorkflowStoppedException.cs ├── Executors │ ├── WorkflowConditionalExecutor.cs │ ├── WorkflowMoveNextExecutor.cs │ ├── WorkflowParallelExecutor.cs │ ├── WorkflowStepAsyncExecutor.cs │ └── WorkflowStepExecutor.cs ├── Interfaces │ ├── IWorkflow.cs │ ├── IWorkflowBuilder.cs │ ├── IWorkflowBuilderConditional.cs │ ├── IWorkflowBuilderConditionalFinal.cs │ ├── IWorkflowBuilderConditionalFinalAggregate.cs │ ├── IWorkflowBuilderConditionalNext.cs │ ├── IWorkflowBuilderNext.cs │ ├── IWorkflowExecutor.cs │ └── IWorkflowStep.cs ├── NetWorkflow.csproj ├── Scheduler │ ├── WorkflowScheduler.cs │ ├── WorkflowSchedulerConfiguration.cs │ └── WorkflowTime.cs ├── Workflow.cs ├── WorkflowBuilder.cs ├── WorkflowOptions.cs └── WorkflowResult.cs └── README.md /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build-job: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Setup .NET 7.x 11 | uses: actions/setup-dotnet@v1 12 | with: 13 | dotnet-version: 7.x 14 | - name: Restore dependencies 15 | run: dotnet restore 16 | - name: Build 17 | run: | 18 | dotnet build NetWorkflow.sln \ 19 | -c Release 20 | - name: Test net7.0 21 | run: | 22 | dotnet test NetWorkflow.Tests/NetWorkflow.Tests.csproj \ 23 | -c Release --no-build --no-restore \ 24 | -f net7.0 \ 25 | -v normal \ 26 | --filter "TestCategory!=very.slow" 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .vs 4 | obj 5 | bin -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Travis Arndt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NetWorkflow.Extensions/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NetWorkflow.Scheduler; 3 | 4 | namespace NetWorkflow.Extensions 5 | { 6 | public static class Extensions 7 | { 8 | /// 9 | /// Adds a transient Workflow of type TWorkflow to the IOC container 10 | /// 11 | /// The type of Workflow to register. 12 | /// The IServiceCollection to register the Workflow to. 13 | public static IServiceCollection AddWorkflow(this IServiceCollection services, Func func) 14 | where TWorkflow : class, IWorkflow 15 | { 16 | return services.AddTransient(x => func.Invoke()); 17 | } 18 | 19 | /// 20 | /// Adds a transient Workflow of type TWorkflow with an implementation of TImplementation to the IOC container 21 | /// 22 | /// The type of Workflow to register. 23 | /// The implementation Workflow type to resolve to. 24 | /// The IServiceCollection to register the Workflow to. 25 | public static IServiceCollection AddWorkflow(this IServiceCollection services, Func func) 26 | where TWorkflow : class, IWorkflow 27 | where TImplementation : class, TWorkflow 28 | { 29 | return services.AddTransient(x => func.Invoke()); 30 | } 31 | 32 | /// 33 | /// Adds a transient WorkflowScheduler to the IOC container 34 | /// 35 | /// The type of Workflow the WorkflowScheduler uses. 36 | /// The IServiceCollection to register the WorkflowScheduler to. 37 | public static IServiceCollection AddWorkflowScheduler(this IServiceCollection services, Func> func) 38 | where TWorkflow : class, IWorkflow 39 | { 40 | return services.AddTransient(x => func.Invoke()); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /NetWorkflow.Extensions/NetWorkflow.Extensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Travis Arndt 5 | net6.0 6 | enable 7 | disable 8 | NetWorkflow.Extensions 9 | NetWorklow.Extensions extends the NetWorkflow library. 10 | https://github.com/Tmarndt1/NetWorkflow 11 | https://github.com/Tmarndt1/NetWorkflow 12 | LICENSE 13 | Fluent;NetWorkflow;Net;Workflow;Extensions; 14 | 3.0.0 15 | 16 | 17 | 18 | 19 | True 20 | \ 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/Examples/ConditionalParallelWorkflow.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace NetWorkflow.Tests.Examples 3 | { 4 | public class ConditionalParallelWorkflow : Workflow 5 | { 6 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 7 | builder 8 | .StartWith(() => new FirstStep()) 9 | .Parallel(() => new IWorkflowStepAsync[] 10 | { 11 | new Step2(50), 12 | new Step2(100), 13 | }) 14 | .Then(() => new FlattenStep()) 15 | .If(x => x.Contains(",")) 16 | .Do(() => new ConditionalStep(1)) 17 | .ElseIf(x => x == "Failed") 18 | .Do(() => new ConditionalStep(-1)) 19 | .EndIf() 20 | .ThenAsync(() => new FinalStepAsync()); 21 | 22 | 23 | private class Step2 : IWorkflowStepAsync 24 | { 25 | private readonly int _delay; 26 | 27 | public Step2(int delay) 28 | { 29 | _delay = delay; 30 | } 31 | 32 | public Task RunAsync(string args, CancellationToken token = default) 33 | { 34 | return Task.Run(() => 35 | { 36 | Thread.Sleep(_delay); 37 | 38 | return $"{nameof(Step2)} ran"; 39 | }, token); 40 | } 41 | } 42 | 43 | private class FirstStep : IWorkflowStep 44 | { 45 | public string Run(CancellationToken token = default) 46 | { 47 | return "Failed"; 48 | } 49 | } 50 | private class FlattenStep : IWorkflowStep, string> 51 | { 52 | public string Run(IEnumerable args, CancellationToken token = default) 53 | { 54 | return string.Join(", ", args); 55 | } 56 | } 57 | 58 | private class ConditionalStep : IWorkflowStep 59 | { 60 | private readonly int _result; 61 | 62 | public ConditionalStep(int result) 63 | { 64 | _result = result; 65 | } 66 | 67 | public int Run(string args, CancellationToken token = default) 68 | { 69 | return _result; 70 | } 71 | } 72 | 73 | private class FinalStepAsync : IWorkflowStepAsync 74 | { 75 | public Task RunAsync(object args, CancellationToken token = default) 76 | { 77 | return Task.FromResult((int)args); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/Examples/ConditionalStopWorkflow.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace NetWorkflow.Tests.Examples 3 | { 4 | public class ConditionalStopWorkflow : Workflow 5 | { 6 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 7 | builder 8 | .StartWith(() => new FirstStep()) 9 | .If(x => x) 10 | .Do(() => new ConditionalStep()) 11 | .ElseIf(x => !x) 12 | .Stop() 13 | .EndIf() 14 | .Then(() => new FinalStep()); 15 | 16 | 17 | private class FirstStep : IWorkflowStep 18 | { 19 | public bool Run(CancellationToken token = default) 20 | { 21 | return false; 22 | } 23 | } 24 | 25 | private class ConditionalStep : IWorkflowStep 26 | { 27 | public bool Run(bool args, CancellationToken token = default) 28 | { 29 | return args; 30 | } 31 | } 32 | 33 | private class FinalStep : IWorkflowStep 34 | { 35 | public object Run(object args, CancellationToken token = default) 36 | { 37 | return (bool)args ? new object() : null; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/Examples/ConditionalThrowWorkflow.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace NetWorkflow.Tests.Examples 3 | { 4 | public class ConditionalThrowWorkflow : Workflow 5 | { 6 | private readonly bool _throwInStep = false; 7 | 8 | public ConditionalThrowWorkflow() { } 9 | 10 | public ConditionalThrowWorkflow(WorkflowOptions options) : base(options) { } 11 | 12 | public ConditionalThrowWorkflow(bool throwInStep) 13 | { 14 | _throwInStep = throwInStep; 15 | } 16 | 17 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 18 | builder 19 | .StartWith(() => new FirstStep(_throwInStep)) 20 | .If(x => x) 21 | .Throw(() => new InvalidOperationException("Invalid operation")) 22 | .ElseIf(x => !x) 23 | .Stop() 24 | .EndIf() 25 | .Then(() => new FinalStep()); 26 | 27 | 28 | private class FirstStep : IWorkflowStep 29 | { 30 | private readonly bool _throwInStep; 31 | 32 | public FirstStep(bool throwInStep) 33 | { 34 | _throwInStep = throwInStep; 35 | } 36 | 37 | public bool Run(CancellationToken token = default) 38 | { 39 | if (_throwInStep) throw new InvalidOperationException("Invalid operation"); 40 | 41 | return true; 42 | } 43 | } 44 | 45 | private class FinalStep : IWorkflowStep 46 | { 47 | public object Run(object args, CancellationToken token = default) 48 | { 49 | return (bool)args ? new object() : null; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/Examples/ConditionalWorkflow.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace NetWorkflow.Tests.Examples 3 | { 4 | public class ConditionalWorkflow : Workflow 5 | { 6 | private readonly string _message = "Success"; 7 | 8 | public ConditionalWorkflow() { } 9 | 10 | public ConditionalWorkflow(string message) 11 | { 12 | _message = message; 13 | } 14 | 15 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 16 | builder 17 | .StartWith(() => new FirstStep(_message)) 18 | .If(x => x == "Success") 19 | .Do(() => new ConditionalStep(1)) 20 | .ElseIf(x => x == "Failed") 21 | .Do(() => new ConditionalStep(-1)) 22 | .Else() 23 | .Throw(() => new InvalidOperationException("TEST")) 24 | .EndIf() 25 | .Then(() => new FinalStep()); 26 | 27 | 28 | private class FirstStep : IWorkflowStep 29 | { 30 | private readonly string _message; 31 | 32 | internal FirstStep(string message) 33 | { 34 | _message = message; 35 | } 36 | 37 | public string Run(CancellationToken token = default) 38 | { 39 | return _message; 40 | } 41 | } 42 | 43 | private class ConditionalStep : IWorkflowStep 44 | { 45 | private readonly int _result; 46 | 47 | public ConditionalStep(int result) 48 | { 49 | _result = result; 50 | } 51 | 52 | public int Run(string args, CancellationToken token = default) 53 | { 54 | return _result; 55 | } 56 | } 57 | 58 | private class FinalStep : IWorkflowStep 59 | { 60 | public int Run(object args, CancellationToken token = default) 61 | { 62 | return (int)args; 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/Examples/HelloWorldWorkflow.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace NetWorkflow.Tests.Examples 3 | { 4 | public class HelloWorldWorkflow : Workflow 5 | { 6 | private const string _helloWorld = "HelloWorld"; 7 | 8 | private readonly Action _callback; 9 | 10 | public HelloWorldWorkflow() { } 11 | 12 | public HelloWorldWorkflow(Action callback) 13 | { 14 | _callback = callback; 15 | } 16 | 17 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 18 | builder 19 | .StartWith(() => new HelloWorld(_callback)) 20 | .Then(() => new GoodbyeWorld(_callback)); 21 | 22 | private class HelloWorld : IWorkflowStep 23 | { 24 | private readonly Action _callback; 25 | 26 | public HelloWorld() { } 27 | 28 | public HelloWorld(Action callback) 29 | { 30 | _callback = callback; 31 | } 32 | 33 | public string Run(CancellationToken token = default) 34 | { 35 | _callback?.Invoke($"{nameof(HelloWorld)} ran"); 36 | 37 | return _helloWorld; 38 | } 39 | } 40 | 41 | private class GoodbyeWorld : IWorkflowStep 42 | { 43 | private readonly Action _callback; 44 | 45 | public GoodbyeWorld() { } 46 | 47 | public GoodbyeWorld(Action callback) 48 | { 49 | _callback = callback; 50 | } 51 | 52 | public bool Run(string args, CancellationToken token = default) 53 | { 54 | _callback?.Invoke($"{nameof(GoodbyeWorld)} ran"); 55 | 56 | return args == _helloWorld; 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/Examples/ParallelWorkflow.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace NetWorkflow.Tests.Examples 3 | { 4 | public class ParallelWorkflow : Workflow 5 | { 6 | private readonly bool _throw = false; 7 | 8 | public ParallelWorkflow(bool throwWithin) 9 | { 10 | _throw = throwWithin; 11 | } 12 | 13 | public ParallelWorkflow(CancellationTokenSource tokenSource) 14 | { 15 | Task.Delay(10).ContinueWith(t => 16 | { 17 | tokenSource.Cancel(); 18 | }); 19 | } 20 | 21 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 22 | builder 23 | .StartWith(() => new Step1()) 24 | .Parallel(() => new IWorkflowStepAsync[] 25 | { 26 | new Step2(100, _throw), 27 | new Step2(50, _throw), 28 | }) 29 | .ThenAsync(() => new Step3()) 30 | .ThenAsync(() => new Step4()); 31 | 32 | private class Step1 : IWorkflowStep 33 | { 34 | public Guid Run(CancellationToken token = default) 35 | { 36 | return Guid.NewGuid(); 37 | } 38 | } 39 | 40 | private class Step2 : IWorkflowStepAsync 41 | { 42 | private readonly int _delay; 43 | 44 | private readonly bool _throw = false; 45 | 46 | public Step2(int delay) 47 | { 48 | _delay = delay; 49 | } 50 | 51 | public Step2(int delay, bool throwWithin) 52 | { 53 | _delay = delay; 54 | 55 | _throw = throwWithin; 56 | } 57 | 58 | public Task RunAsync(Guid args, CancellationToken token = default) 59 | { 60 | return Task.Delay(_delay, token).ContinueWith(t => 61 | { 62 | if (_throw) throw new InvalidOperationException("A test exception"); 63 | 64 | return $"{nameof(Step2)} ran with delay {_delay}"; 65 | }, token); 66 | } 67 | } 68 | 69 | private class Step3 : IWorkflowStepAsync, string> 70 | { 71 | public Task RunAsync(IEnumerable args, CancellationToken token = default) 72 | { 73 | if (args.Count() < 1) return Task.FromResult(string.Empty); 74 | 75 | return Task.FromResult($"{nameof(Step3)} ran"); 76 | } 77 | } 78 | 79 | private class Step4 : IWorkflowStepAsync 80 | { 81 | public Task RunAsync(string args, CancellationToken token = default) 82 | { 83 | return Task.FromResult(args == $"{nameof(Step3)} ran"); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/Examples/RetryExample.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace NetWorkflow.Tests.Examples 3 | { 4 | public class RetryWorkflow : Workflow 5 | { 6 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 7 | builder 8 | .StartWith(() => new FirstStep()) 9 | .If(x => x == "Success") 10 | .Do(() => new ConditionalStep(1)) 11 | .Else() 12 | .Retry(TimeSpan.FromMilliseconds(10)) 13 | .EndIf() 14 | .Then(() => new FinalStep()); 15 | 16 | public class FirstStep : IWorkflowStep 17 | { 18 | public static int RanCount; 19 | 20 | public string Run(CancellationToken token = default) 21 | { 22 | RanCount++; 23 | 24 | return "Failed"; 25 | } 26 | } 27 | 28 | private class ConditionalStep : IWorkflowStep 29 | { 30 | private readonly int _result; 31 | 32 | public ConditionalStep(int result) 33 | { 34 | _result = result; 35 | } 36 | 37 | public int Run(string args, CancellationToken token = default) 38 | { 39 | return _result; 40 | } 41 | } 42 | 43 | private class FinalStep : IWorkflowStep 44 | { 45 | public int Run(object args, CancellationToken token = default) 46 | { 47 | return (int)args; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/NetWorkflow.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Travis Arndt 5 | net7.0 6 | enable 7 | disable 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | #if NET6_0_OR_GREATER 2 | global using Xunit; 3 | #endif -------------------------------------------------------------------------------- /NetWorkflow.Tests/WorkflowScheduler_Tests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NetWorkflow.Extensions; 3 | using NetWorkflow.Scheduler; 4 | using NetWorkflow.Tests.Examples; 5 | 6 | namespace NetWorkflow.Tests 7 | { 8 | public class WorkflowScheduler_Tests 9 | { 10 | [Fact] 11 | public void No_Options_Success() 12 | { 13 | // Arrange 14 | bool hit = false; 15 | 16 | // Act 17 | try 18 | { 19 | _ = new WorkflowScheduler(() => new HelloWorldWorkflow(), null); 20 | 21 | hit = true; 22 | } 23 | catch (Exception ex) 24 | { 25 | // Assert 26 | Assert.IsType(ex); 27 | } 28 | 29 | // Assert 30 | Assert.False(hit); 31 | } 32 | 33 | [Fact] 34 | public void No_Factory_Success() 35 | { 36 | // Arrange 37 | 38 | 39 | bool hit = false; 40 | 41 | // Act 42 | try 43 | { 44 | _ = new WorkflowScheduler(null, config => config.ExecuteAt = WorkflowTime.AtFrequency(TimeSpan.FromMilliseconds(200))); 45 | 46 | hit = true; 47 | } 48 | catch (Exception ex) 49 | { 50 | // Assert 51 | Assert.IsType(ex); 52 | } 53 | 54 | // Assert 55 | Assert.False(hit); 56 | } 57 | 58 | [Fact] 59 | public void Frequency_Success() 60 | { 61 | // Arrange 62 | int count = 0; 63 | 64 | var scheduler = new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 65 | { 66 | config.ExecuteAt = WorkflowTime.AtFrequency(TimeSpan.FromMilliseconds(50)); 67 | config.OnExecuted = (result) => count++; 68 | }); 69 | 70 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 71 | 72 | // Act 73 | scheduler.StartAsync(tokenSource.Token); 74 | 75 | // Assert 76 | Thread.Sleep(TimeSpan.FromMilliseconds(149)); 77 | 78 | tokenSource.Cancel(); 79 | 80 | Assert.Equal(2, count); 81 | } 82 | 83 | [Fact] 84 | public void Frequency_Max_Count_Success() 85 | { 86 | // Arrange 87 | int count = 0; 88 | 89 | var scheduler = new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 90 | { 91 | config.ExecuteAt = WorkflowTime.AtFrequency(TimeSpan.FromMilliseconds(50)).Until(2); 92 | config.OnExecuted = (WorkflowResult result) => count++; 93 | }); 94 | 95 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 96 | 97 | // Act 98 | scheduler.StartAsync(tokenSource.Token); 99 | 100 | // Assert 101 | Thread.Sleep(TimeSpan.FromMilliseconds(200)); 102 | 103 | tokenSource.Cancel(); 104 | 105 | Assert.Equal(2, count); 106 | } 107 | 108 | [Fact] 109 | public void AtTime_Minute_Success() 110 | { 111 | // Arrange 112 | int count = 0; 113 | 114 | var scheduler = new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 115 | { 116 | config.ExecuteAt = WorkflowTime.AtMinute(DateTime.Now.Minute); 117 | config.OnExecuted = (result) => count++; 118 | }); 119 | 120 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 121 | 122 | // Act 123 | scheduler.StartAsync(tokenSource.Token); 124 | 125 | // Assert 126 | Thread.Sleep(1000); // Must sleep for a second 127 | 128 | tokenSource.Cancel(); 129 | 130 | Assert.Equal(1, count); // Count should be two because both WorkflowStep's increment 131 | } 132 | 133 | [Fact] 134 | public void AtTime_Hour_Success() 135 | { 136 | // Arrange 137 | int count = 0; 138 | 139 | DateTime now = DateTime.Now; 140 | 141 | var scheduler = new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 142 | { 143 | config.ExecuteAt = WorkflowTime.AtHour(now.Hour, now.Minute); 144 | config.OnExecuted = (result) => count++; 145 | }); 146 | 147 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 148 | 149 | // Act 150 | scheduler.StartAsync(tokenSource.Token); 151 | 152 | // Assert 153 | Thread.Sleep(1000); // Must sleep for a second 154 | 155 | tokenSource.Cancel(); 156 | 157 | Assert.Equal(1, count); // Count should be two because both WorkflowStep's increment 158 | } 159 | 160 | [Fact] 161 | public void AtTime_Day_Success() 162 | { 163 | // Arrange 164 | int count = 0; 165 | 166 | DateTime now = DateTime.Now; 167 | 168 | var scheduler = new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 169 | { 170 | config.ExecuteAt = WorkflowTime.AtDay(now.Day, now.Hour, now.Minute); 171 | config.OnExecuted = (result) => count++; 172 | }); 173 | 174 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 175 | 176 | // Act 177 | scheduler.StartAsync(tokenSource.Token); 178 | 179 | // Assert 180 | Thread.Sleep(1000); // Must sleep for a second 181 | 182 | tokenSource.Cancel(); 183 | 184 | Assert.Equal(1, count); // Count should be two because both WorkflowStep's increment 185 | } 186 | 187 | [Fact] 188 | public void AtTime_No_Fire_Success() 189 | { 190 | // Arrange 191 | int count = 0; 192 | 193 | var scheduler = new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 194 | { 195 | config.ExecuteAt = WorkflowTime.AtHour(1, DateTime.Now.Minute - 1); 196 | config.OnExecuted = (result) => count++; 197 | }); 198 | 199 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 200 | 201 | // Act 202 | scheduler.StartAsync(tokenSource.Token); 203 | 204 | // Assert 205 | Thread.Sleep(200); 206 | 207 | tokenSource.Cancel(); 208 | 209 | Assert.Equal(0, count); 210 | } 211 | 212 | [Fact] 213 | public void AddWorkflowScheduler_Extensions_Success() 214 | { 215 | // Arrange 216 | // Act 217 | var workflowScheduler = new ServiceCollection() 218 | .AddWorkflowScheduler(() => new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 219 | { 220 | config.ExecuteAt = WorkflowTime.AtHour(1); 221 | })) 222 | .BuildServiceProvider() 223 | .GetRequiredService>(); 224 | 225 | // Assert 226 | Assert.NotNull(workflowScheduler); 227 | } 228 | 229 | [Fact] 230 | public void Dispose_Success() 231 | { 232 | // Arrange 233 | int count = 0; 234 | 235 | var scheduler = new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 236 | { 237 | config.ExecuteAt = WorkflowTime.AtFrequency(TimeSpan.FromMilliseconds(50)); 238 | config.OnExecuted = (result) => count++; 239 | }); 240 | 241 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 242 | 243 | scheduler.StartAsync(tokenSource.Token); 244 | 245 | // Act 246 | scheduler.Dispose(); 247 | 248 | Thread.Sleep(100); 249 | 250 | // Assert 251 | Assert.Equal(0, count); 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/WorkflowTime_Tests.cs: -------------------------------------------------------------------------------- 1 | using NetWorkflow.Exceptions; 2 | using NetWorkflow.Scheduler; 3 | 4 | namespace NetWorkflow.Tests 5 | { 6 | public class WorkflowTime_Tests 7 | { 8 | [Fact] 9 | public void Day_Range_Min_Success() 10 | { 11 | // Arrange 12 | bool hit = false; 13 | 14 | // Act 15 | try 16 | { 17 | var time = WorkflowTime.AtDay(0); 18 | 19 | hit = true; 20 | } 21 | catch (Exception ex) 22 | { 23 | Assert.IsType(ex); 24 | } 25 | 26 | // Assert 27 | Assert.False(hit); 28 | } 29 | 30 | [Fact] 31 | public void Day_Range_Max_Success() 32 | { 33 | // Arrange 34 | bool hit = false; 35 | 36 | // Act 37 | try 38 | { 39 | var time = WorkflowTime.AtDay(32); 40 | 41 | hit = true; 42 | } 43 | catch (Exception ex) 44 | { 45 | Assert.IsType(ex); 46 | } 47 | 48 | // Assert 49 | Assert.False(hit); 50 | } 51 | 52 | [Fact] 53 | public void Hour_Range_Min_Success() 54 | { 55 | // Arrange 56 | bool hit = false; 57 | 58 | // Act 59 | try 60 | { 61 | var time = WorkflowTime.AtHour(-1); 62 | 63 | hit = true; 64 | } 65 | catch (Exception ex) 66 | { 67 | Assert.IsType(ex); 68 | } 69 | 70 | // Assert 71 | Assert.False(hit); 72 | } 73 | 74 | [Fact] 75 | public void Hour_Range_Max_Success() 76 | { 77 | // Arrange 78 | bool hit = false; 79 | 80 | // Act 81 | try 82 | { 83 | var time = WorkflowTime.AtDay(60); 84 | 85 | hit = true; 86 | } 87 | catch (Exception ex) 88 | { 89 | Assert.IsType(ex); 90 | } 91 | 92 | // Assert 93 | Assert.False(hit); 94 | } 95 | 96 | [Fact] 97 | public void Minute_Range_Min_Success() 98 | { 99 | // Arrange 100 | bool hit = false; 101 | 102 | // Act 103 | try 104 | { 105 | var time = WorkflowTime.AtMinute(-1); 106 | 107 | hit = true; 108 | } 109 | catch (Exception ex) 110 | { 111 | Assert.IsType(ex); 112 | } 113 | 114 | // Assert 115 | Assert.False(hit); 116 | } 117 | 118 | [Fact] 119 | public void Minute_Range_Max_Success() 120 | { 121 | // Arrange 122 | bool hit = false; 123 | 124 | // Act 125 | try 126 | { 127 | var time = WorkflowTime.AtMinute(60); 128 | 129 | hit = true; 130 | } 131 | catch (Exception ex) 132 | { 133 | Assert.IsType(ex); 134 | } 135 | 136 | // Assert 137 | Assert.False(hit); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /NetWorkflow.Tests/Workflow_Tests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NetWorkflow.Extensions; 3 | using NetWorkflow.Tests.Examples; 4 | 5 | namespace NetWorkflow.Tests 6 | { 7 | public class Workflow_Tests 8 | { 9 | [Fact] 10 | public void HelloWorld_Success() 11 | { 12 | // Arrange 13 | var workflow = new HelloWorldWorkflow(); 14 | 15 | // Act 16 | var result = workflow.Run(); 17 | 18 | // Assert 19 | Assert.NotNull(result); 20 | Assert.True(result.IsCompleted); 21 | Assert.False(result.IsCanceled); 22 | Assert.False(result.IsFaulted); 23 | Assert.True(result); 24 | } 25 | 26 | [Fact] 27 | public void HelloWorld_Ran_Twice_Success() 28 | { 29 | // Arrange 30 | var workflow = new HelloWorldWorkflow(); 31 | 32 | // Act 33 | workflow.Run(); 34 | 35 | var result = workflow.Run(); 36 | 37 | // Assert 38 | Assert.NotNull(result); 39 | Assert.True(result.IsCompleted); 40 | Assert.False(result.IsCanceled); 41 | Assert.False(result.IsFaulted); 42 | Assert.True(result); 43 | } 44 | 45 | [Fact] 46 | public void Parallel_Success() 47 | { 48 | // Arrange 49 | var workflow = new ParallelWorkflow(false); 50 | 51 | // Act 52 | var result = workflow.Run(); 53 | 54 | // Assert 55 | Assert.NotNull(result); 56 | Assert.True(result.IsCompleted); 57 | Assert.False(result.IsCanceled); 58 | Assert.False(result.IsFaulted); 59 | Assert.True(result); 60 | } 61 | 62 | [Fact] 63 | public void Parallel_Cancel_Success() 64 | { 65 | // Arrange 66 | var tokenSource = new CancellationTokenSource(); 67 | 68 | var workflow = new ParallelWorkflow(tokenSource); 69 | 70 | // Act 71 | var result = workflow.Run(tokenSource.Token); 72 | 73 | // Assert 74 | Assert.False(result.IsCompleted); 75 | Assert.False(result.IsFaulted); 76 | Assert.True(result.IsCanceled); 77 | } 78 | 79 | [Fact] 80 | public void Parallel_Throw_Within_Task_Success() 81 | { 82 | // Arrange 83 | var workflow = new ParallelWorkflow(true); 84 | 85 | // Act 86 | var result = workflow.Run(); 87 | 88 | // Assert 89 | Assert.NotNull(result); 90 | Assert.False(result.IsCompleted); 91 | Assert.False(result.IsCanceled); 92 | Assert.True(result.IsFaulted); 93 | } 94 | 95 | [Fact] 96 | public void Conditional_Success() 97 | { 98 | // Arrange 99 | var workflow = new ConditionalWorkflow(); 100 | 101 | // Act 102 | var result = workflow.Run(); 103 | 104 | // Assert 105 | Assert.NotNull(result); 106 | Assert.True(result.IsCompleted); 107 | Assert.False(result.IsCanceled); 108 | Assert.False(result.IsFaulted); 109 | Assert.Equal(1, result.Output); 110 | } 111 | 112 | [Fact] 113 | public void Conditional_Else_Success() 114 | { 115 | // Arrange 116 | var workflow = new ConditionalWorkflow("Unknown"); 117 | 118 | // Act 119 | var result = workflow.Run(); 120 | 121 | // Assert 122 | Assert.NotNull(result); 123 | Assert.False(result.IsCompleted); 124 | Assert.False(result.IsCanceled); 125 | Assert.True(result.IsFaulted); 126 | Assert.Equal(0, result.Output); 127 | } 128 | 129 | 130 | [Fact] 131 | public void ConditionalParallel_Success() 132 | { 133 | // Arrange 134 | var workflow = new ConditionalParallelWorkflow(); 135 | 136 | // Act 137 | var result = workflow.Run(); 138 | 139 | // Assert 140 | Assert.NotNull(result); 141 | Assert.True(result.IsCompleted); 142 | Assert.False(result.IsCanceled); 143 | Assert.False(result.IsFaulted); 144 | Assert.Equal(1, result.Output); // This test should return a favorable result 145 | } 146 | 147 | [Fact] 148 | public void ConditionalStop_Success() 149 | { 150 | // Arrange 151 | var workflow = new ConditionalStopWorkflow(); 152 | 153 | // Act 154 | var result = workflow.Run(); 155 | 156 | // Assert 157 | Assert.Null(result.Output); // Should be null if it passes 158 | Assert.True(result.IsCanceled); 159 | Assert.False(result.IsCompleted); 160 | } 161 | 162 | [Fact] 163 | public void ConditionalThrow_Success() 164 | { 165 | // Arrange 166 | var workflow = new ConditionalThrowWorkflow(); 167 | 168 | // Act 169 | var result = workflow.Run(); 170 | 171 | // Assert 172 | Assert.Null(result.Output); // Should be null if it passes 173 | Assert.False(result.IsCanceled); 174 | Assert.False(result.IsCompleted); 175 | Assert.True(result.IsFaulted); 176 | Assert.IsType(result.Exception); 177 | } 178 | 179 | [Fact] 180 | public void ConditionalThrow_WithOptions_Success() 181 | { 182 | // Arrange 183 | var workflow = new ConditionalThrowWorkflow(new WorkflowOptions() 184 | { 185 | Rethrow = true 186 | }); 187 | 188 | bool hit = false; 189 | 190 | // Act 191 | try 192 | { 193 | _ = workflow.Run(); 194 | 195 | hit = true; 196 | } 197 | catch (Exception ex) 198 | { 199 | Assert.IsType(ex); 200 | } 201 | 202 | // Assert 203 | Assert.False(hit); 204 | } 205 | 206 | [Fact] 207 | public void Catch_Exception_InStep_Success() 208 | { 209 | // Arrange 210 | var workflow = new ConditionalThrowWorkflow(true); 211 | 212 | // Act 213 | var result = workflow.Run(); 214 | 215 | // Assert 216 | Assert.False(result.IsCompleted); 217 | Assert.False(result.IsCanceled); 218 | Assert.IsType(result.Exception); 219 | } 220 | 221 | [Fact] 222 | public void AddWorkflow_Extensions_Success() 223 | { 224 | // Arrange 225 | var workflow = new ServiceCollection() 226 | .AddWorkflow(() => new HelloWorldWorkflow()) 227 | .BuildServiceProvider() 228 | .GetRequiredService(); 229 | 230 | // Act 231 | var result = workflow.Run(); 232 | 233 | // Assert 234 | Assert.NotNull(result); 235 | Assert.True(result.IsCompleted); 236 | Assert.False(result.IsCanceled); 237 | Assert.False(result.IsFaulted); 238 | Assert.True(result); 239 | } 240 | 241 | [Fact] 242 | public void Retry_WorkflowStep_Success() 243 | { 244 | // Arrange 245 | var workflow = new RetryWorkflow(); 246 | 247 | // Act 248 | var result = workflow.Run(); 249 | 250 | // Assert 251 | Assert.Equal(2, RetryWorkflow.FirstStep.RanCount); 252 | Assert.False(result.IsCompleted); 253 | Assert.False(result.IsCanceled); 254 | Assert.IsType(result.Exception); 255 | } 256 | 257 | [Fact] 258 | public void Dispose_Success() 259 | { 260 | // Arrange 261 | var workflow = new ConditionalThrowWorkflow(); 262 | 263 | bool hit = false; 264 | 265 | // Act 266 | try 267 | { 268 | workflow.Dispose(); 269 | 270 | _= workflow.Run(); 271 | 272 | hit = true; 273 | } 274 | catch (Exception ex) 275 | { 276 | Assert.IsType(ex); 277 | } 278 | 279 | // Assert 280 | Assert.False(hit); 281 | } 282 | } 283 | } -------------------------------------------------------------------------------- /NetWorkflow.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32602.215 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetWorkflow", "NetWorkflow\NetWorkflow.csproj", "{4F560600-BEEB-4E15-86F6-DDF975D09AFA}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetWorkflow.Tests", "NetWorkflow.Tests\NetWorkflow.Tests.csproj", "{2D8F7306-C9C4-449D-8B91-2E7AFA3D5052}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetWorkflow.Extensions", "NetWorkflow.Extensions\NetWorkflow.Extensions.csproj", "{28352C87-A16B-4F76-82AD-7F57A5FADBD8}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {4F560600-BEEB-4E15-86F6-DDF975D09AFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {4F560600-BEEB-4E15-86F6-DDF975D09AFA}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {4F560600-BEEB-4E15-86F6-DDF975D09AFA}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {4F560600-BEEB-4E15-86F6-DDF975D09AFA}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {2D8F7306-C9C4-449D-8B91-2E7AFA3D5052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {2D8F7306-C9C4-449D-8B91-2E7AFA3D5052}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {2D8F7306-C9C4-449D-8B91-2E7AFA3D5052}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {2D8F7306-C9C4-449D-8B91-2E7AFA3D5052}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {28352C87-A16B-4F76-82AD-7F57A5FADBD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {28352C87-A16B-4F76-82AD-7F57A5FADBD8}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {28352C87-A16B-4F76-82AD-7F57A5FADBD8}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {28352C87-A16B-4F76-82AD-7F57A5FADBD8}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {2A445561-DF40-4579-B676-870B28A1A965} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /NetWorkflow/Exceptions/WorkflowInvalidValueException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetWorkflow.Exceptions 4 | { 5 | /// 6 | /// Exception that is thrown when an invalid value is set 7 | /// 8 | public class WorkflowInvalidValueException : Exception 9 | { 10 | public WorkflowInvalidValueException(string message) : base(message) 11 | { 12 | } 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /NetWorkflow/Exceptions/WorkflowMaxRetryException.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | 4 | namespace NetWorkflow 5 | { 6 | /// 7 | /// Exception that is thrown when the maximum ammount of retries has been met within a Workflow 8 | /// 9 | public class WorkflowMaxRetryException : Exception 10 | { 11 | public WorkflowMaxRetryException() : base("Max retry limit has been met within the Workflow") 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /NetWorkflow/Exceptions/WorkflowNoConditionMetException.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | 4 | namespace NetWorkflow 5 | { 6 | /// 7 | /// Exception that is thrown when the maximum ammount of retries has been met within a Workflow 8 | /// 9 | public class WorkflowNoConditionMetException : Exception 10 | { 11 | public WorkflowNoConditionMetException() : base("No condition was met in the Workflow.") 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /NetWorkflow/Exceptions/WorkflowStoppedException.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | 4 | namespace NetWorkflow 5 | { 6 | /// 7 | /// Exception that is thrown when the Workflow has stopped 8 | /// 9 | public class WorkflowStoppedException : Exception 10 | { 11 | public WorkflowStoppedException() : base("The Workflow has stopped.") 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /NetWorkflow/Executors/WorkflowConditionalExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Threading; 6 | 7 | namespace NetWorkflow 8 | { 9 | internal class WorkflowExecutorConditional : IWorkflowExecutor 10 | { 11 | private readonly List _wrappers = new List(); 12 | 13 | private ExecutorWrapper _active => _wrappers.Last(); 14 | 15 | private bool _disposedValue; 16 | 17 | public WorkflowExecutorConditional(Expression> expression) 18 | { 19 | _wrappers.Add(new ExecutorWrapper(expression)); 20 | } 21 | 22 | public void Append(Expression> expression) 23 | { 24 | _wrappers.Add(new ExecutorWrapper(expression)); 25 | } 26 | 27 | public void Append(IWorkflowExecutor executor) 28 | { 29 | _active.Executor = executor; 30 | } 31 | 32 | public void Stop() 33 | { 34 | _active.ShouldStop = true; 35 | } 36 | 37 | public void OnExceptionDo(Expression> func) 38 | { 39 | _active.OnException = func; 40 | } 41 | 42 | public void SetRetry(TimeSpan delay, int maxRetries, Action onRetry) 43 | { 44 | var wrapper = _active; 45 | 46 | wrapper.Retry = true; 47 | wrapper.Delay = delay; 48 | wrapper.RetryCount = maxRetries; 49 | wrapper.OnRetry = onRetry; 50 | } 51 | 52 | public object Run(TIn args, CancellationToken token = default) 53 | { 54 | foreach (var wrapper in _wrappers) 55 | { 56 | var condition = wrapper.Expression.Compile() as Func; 57 | 58 | if (condition.Invoke(args)) 59 | { 60 | if (wrapper.ShouldStop || token.IsCancellationRequested) 61 | throw new WorkflowStoppedException(); 62 | 63 | if (wrapper.OnException != null) 64 | throw wrapper.OnException.Compile()(); 65 | 66 | if (wrapper.Retry) 67 | { 68 | if (wrapper.RetryCount == 0) 69 | throw new WorkflowMaxRetryException(); 70 | 71 | wrapper.RetryCount--; 72 | 73 | if (wrapper.OnRetry == null) 74 | throw new InvalidOperationException("Internal error with null OnRetry callback"); 75 | 76 | Thread.Sleep(wrapper.Delay); 77 | 78 | wrapper.OnRetry(); 79 | } 80 | 81 | return wrapper.Executor?.Run(args, token); 82 | } 83 | } 84 | 85 | throw new WorkflowNoConditionMetException(); 86 | } 87 | 88 | protected virtual void Dispose(bool disposing) 89 | { 90 | if (!_disposedValue) 91 | { 92 | if (disposing) 93 | { 94 | foreach (var wrapper in _wrappers) 95 | (wrapper.Executor as IDisposable)?.Dispose(); 96 | 97 | _wrappers.Clear(); 98 | } 99 | 100 | _disposedValue = true; 101 | } 102 | } 103 | 104 | public void Dispose() 105 | { 106 | Dispose(disposing: true); 107 | 108 | GC.SuppressFinalize(this); 109 | } 110 | 111 | private sealed class ExecutorWrapper 112 | { 113 | public LambdaExpression Expression { get; } 114 | public IWorkflowExecutor Executor { get; set; } 115 | public Expression> OnException { get; set; } 116 | public TimeSpan Delay { get; set; } 117 | public bool Retry { get; set; } 118 | public int RetryCount { get; set; } 119 | public Action OnRetry { get; set; } 120 | public bool ShouldStop { get; set; } 121 | 122 | public ExecutorWrapper(Expression> expression) 123 | { 124 | Expression = expression ?? throw new ArgumentNullException(nameof(expression)); 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /NetWorkflow/Executors/WorkflowMoveNextExecutor.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | using System.Threading; 4 | 5 | namespace NetWorkflow 6 | { 7 | internal class WorkflowMoveNextExecutor : IWorkflowExecutor 8 | { 9 | private bool _disposedValue; 10 | 11 | public TArgs Run(TArgs args, CancellationToken token = default) => args; 12 | 13 | protected virtual void Dispose(bool disposing) 14 | { 15 | if (!_disposedValue) 16 | { 17 | _disposedValue = true; 18 | } 19 | } 20 | 21 | public void Dispose() 22 | { 23 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 24 | Dispose(disposing: true); 25 | 26 | GC.SuppressFinalize(this); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /NetWorkflow/Executors/WorkflowParallelExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace NetWorkflow 10 | { 11 | internal class WorkflowParallelExecutor : IWorkflowExecutor> 12 | { 13 | private readonly LambdaExpression _expression; 14 | 15 | private bool _disposedValue; 16 | 17 | public WorkflowParallelExecutor(LambdaExpression expression) 18 | { 19 | _expression = expression ?? throw new ArgumentNullException(nameof(expression)); 20 | } 21 | 22 | public IEnumerable Run(TIn args, CancellationToken token = default) 23 | { 24 | if (_expression.Parameters.Count > 0) 25 | throw new InvalidOperationException("Parameter count within lambda cannot be greater than 0"); 26 | 27 | var body = _expression.Compile().DynamicInvoke(); 28 | 29 | if (body == null) 30 | throw new InvalidOperationException("IWorkflowStep cannot be null"); 31 | 32 | if (token.IsCancellationRequested) 33 | throw new WorkflowStoppedException(); 34 | 35 | if (body is IEnumerable asyncSteps) 36 | { 37 | var tasks = asyncSteps.Select(x => 38 | { 39 | var executor = x.GetType().GetMethod(nameof(IWorkflowStepAsync.RunAsync)); 40 | 41 | if (executor == null) 42 | throw new InvalidOperationException("Internal error"); 43 | 44 | if (executor.GetParameters().Length > 1) 45 | return (Task)executor.Invoke(x, new object[] { args, token }); 46 | else 47 | return (Task)executor.Invoke(x, new object[] { token }); 48 | 49 | }).ToArray(); 50 | 51 | if (token.IsCancellationRequested) 52 | throw new WorkflowStoppedException(); 53 | 54 | Task.WaitAll(tasks, token); 55 | 56 | return tasks.Select(x => x.Result); 57 | } 58 | 59 | throw new InvalidOperationException("Internal error"); 60 | } 61 | 62 | protected virtual void Dispose(bool disposing) 63 | { 64 | if (!_disposedValue) 65 | { 66 | if (disposing) 67 | { 68 | // Dispose managed resources 69 | (_expression as IDisposable)?.Dispose(); 70 | } 71 | 72 | // Clean up unmanaged resources 73 | // (none in this case) 74 | 75 | _disposedValue = true; 76 | } 77 | } 78 | 79 | public void Dispose() 80 | { 81 | Dispose(disposing: true); 82 | 83 | GC.SuppressFinalize(this); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /NetWorkflow/Executors/WorkflowStepAsyncExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace NetWorkflow 8 | { 9 | internal class WorkflowStepAsyncExecutor : IWorkflowExecutor 10 | { 11 | private readonly LambdaExpression _expression; 12 | 13 | private bool _disposedValue; 14 | 15 | public WorkflowStepAsyncExecutor(LambdaExpression expression) 16 | { 17 | _expression = expression ?? throw new ArgumentNullException(nameof(expression)); 18 | } 19 | 20 | public TOut Run(TIn args, CancellationToken token = default) 21 | { 22 | if (_expression.Parameters.Count > 0) 23 | throw new InvalidOperationException("Parameter count within lambda cannot be greater than 0"); 24 | 25 | var body = _expression.Compile().DynamicInvoke(); 26 | 27 | if (body == null) 28 | throw new InvalidOperationException("IWorkflowStep cannot be null"); 29 | 30 | if (token.IsCancellationRequested) 31 | throw new WorkflowStoppedException(); 32 | 33 | if (!(body is IWorkflowStepAsync stepAsync)) 34 | throw new InvalidOperationException("Internal error"); 35 | 36 | var executingMethod = stepAsync.GetType().GetMethod(nameof(IWorkflowStepAsync.RunAsync)); 37 | 38 | if (executingMethod == null) 39 | throw new InvalidOperationException("WorkflowStep executing method not found"); 40 | 41 | var task = (Task)executingMethod.Invoke(stepAsync, new object[] { args, token }); 42 | 43 | task.Wait(token); 44 | 45 | return task.GetAwaiter().GetResult(); 46 | } 47 | 48 | protected virtual void Dispose(bool disposing) 49 | { 50 | if (!_disposedValue) 51 | { 52 | if (disposing) 53 | { 54 | // Dispose managed resources 55 | (_expression as IDisposable)?.Dispose(); 56 | } 57 | 58 | // Clean up unmanaged resources 59 | // (none in this case) 60 | 61 | _disposedValue = true; 62 | } 63 | } 64 | 65 | public void Dispose() 66 | { 67 | Dispose(disposing: true); 68 | 69 | GC.SuppressFinalize(this); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /NetWorkflow/Executors/WorkflowStepExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Threading; 4 | 5 | namespace NetWorkflow 6 | { 7 | internal class WorkflowStepExecutor : IWorkflowExecutor, IDisposable 8 | { 9 | private readonly LambdaExpression _expression; 10 | 11 | private bool _disposedValue; 12 | 13 | public WorkflowStepExecutor(LambdaExpression expression) 14 | { 15 | _expression = expression ?? throw new ArgumentNullException(nameof(expression)); 16 | } 17 | 18 | public TOut Run(TIn args, CancellationToken token = default) 19 | { 20 | if (_expression.Parameters.Count > 0) 21 | throw new InvalidOperationException("Parameter count within lambda cannot be greater than 0"); 22 | 23 | var body = _expression.Compile().DynamicInvoke(); 24 | 25 | if (body == null) 26 | throw new InvalidOperationException("IWorkflowStep cannot be null"); 27 | 28 | if (token.IsCancellationRequested) 29 | throw new WorkflowStoppedException(); 30 | 31 | if (!(body is IWorkflowStep step)) 32 | throw new InvalidOperationException("Internal error"); 33 | 34 | var executingMethod = step.GetType().GetMethod(nameof(IWorkflowStep.Run)); 35 | 36 | if (executingMethod == null) 37 | throw new InvalidOperationException("Workflow executing method not found"); 38 | 39 | var parameters = executingMethod.GetParameters(); 40 | 41 | if (parameters.Length == 1) 42 | return (TOut)executingMethod.Invoke(step, new object[] { token }); 43 | else 44 | return (TOut)executingMethod.Invoke(step, new object[] { args, token }); 45 | } 46 | 47 | protected virtual void Dispose(bool disposing) 48 | { 49 | if (!_disposedValue) 50 | { 51 | if (disposing) 52 | { 53 | // Dispose managed resources 54 | (_expression as IDisposable)?.Dispose(); 55 | } 56 | 57 | // Clean up unmanaged resources 58 | // (none in this case) 59 | 60 | _disposedValue = true; 61 | } 62 | } 63 | 64 | public void Dispose() 65 | { 66 | Dispose(disposing: true); 67 | 68 | GC.SuppressFinalize(this); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /NetWorkflow/Interfaces/IWorkflow.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace NetWorkflow 6 | { 7 | /// 8 | /// Defines the required members and methods of the Workflow. 9 | /// 10 | /// The result of the Workflow. 11 | public interface IWorkflow 12 | { 13 | /// 14 | /// Abstract method that injects a IWorkflowBuilder to build the steps of the Workflow. 15 | /// 16 | /// The IWorkflowBuilder to build the Workflow's steps. 17 | public abstract IWorkflowBuilder Build(IWorkflowBuilder builder); 18 | 19 | /// 20 | /// Builds and runs the Workflow and provides a WorkflowResult. 21 | /// 22 | /// The CancellationToken to cancel the Workflow. 23 | /// A WorkflowResult with TOut data. 24 | public WorkflowResult Run(CancellationToken token = default); 25 | 26 | /// 27 | /// Builds and runs the Workflow asynchronously and provides a WorkflowResult. 28 | /// 29 | /// The CancellationToken to cancel the Workflow. 30 | /// A a task with a WorkflowResult with TOut data. 31 | public Task> RunAsync(CancellationToken token = default); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /NetWorkflow/Interfaces/IWorkflowBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace NetWorkflow 5 | { 6 | /// 7 | /// Interface that defines the base methods on a WorkflowBuilder. 8 | /// 9 | public interface IWorkflowBuilder 10 | { 11 | /// 12 | /// Defines what WorkflowStep to execute first within a Workflow. 13 | /// 14 | /// The WorkflowStep's output type. 15 | /// A function that returns a WorkflowStep. 16 | /// An instance of a WorkflowBuilder. 17 | public IWorkflowBuilderNext StartWith(Expression>> func); 18 | } 19 | 20 | /// 21 | /// Extended generic IWorkflowBuilder interface that defines the base methods on a WorkflowBuilder. 22 | /// 23 | /// The end result of the Workflow. 24 | public interface IWorkflowBuilder 25 | { 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /NetWorkflow/Interfaces/IWorkflowBuilderConditional.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace NetWorkflow 5 | { 6 | /// 7 | /// Interface that defines all the useable methods extending from a conditional WorkflowBuilder type. 8 | /// 9 | /// The type of the incoming parameter. 10 | public interface IWorkflowBuilderConditional : IWorkflowBuilder 11 | { 12 | /// 13 | /// Defines the WorkflowStep to execute if the condition is true. 14 | /// 15 | /// The return type of the WorkflowStep. 16 | /// A function that returns a WorkflowStep. 17 | /// An instance of a WorkflowBuilder. 18 | public IWorkflowBuilderConditionalNext Do(Expression>> func); 19 | 20 | /// 21 | /// Designates the Workflow to stop execution if the condition is true. 22 | /// 23 | /// An instance of a WorkflowBuilder. 24 | public IWorkflowBuilderConditionalNext Stop(); 25 | 26 | /// 27 | /// Designates the Workflow to throw an exception if the condition is true. 28 | /// 29 | /// A function that returns an exception. 30 | /// An instance of a WorkflowBuilder. 31 | public IWorkflowBuilderConditionalNext Throw(Expression> func); 32 | 33 | /// 34 | /// Defines the Workflow to retry the previous WorkflowStep. 35 | /// Optional max retries count can be passed in with the default value being 1. 36 | /// 37 | /// The amount of time to delay before retrying. 38 | /// Max number of retries before breaking. 39 | /// An instance of a WorkflowBuilder. 40 | public IWorkflowBuilderConditionalNext Retry(TimeSpan delay, int maxRetries = 1); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /NetWorkflow/Interfaces/IWorkflowBuilderConditionalFinal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace NetWorkflow 5 | { 6 | /// 7 | /// Interface that defines all the useable methods extending from a conditional WorkflowBuilder type. 8 | /// 9 | /// The type of the incoming parameter. 10 | public interface IWorkflowBuilderConditionalFinal : IWorkflowBuilder 11 | { 12 | /// 13 | /// Defines the WorkflowStep to execute if the condition is true. 14 | /// 15 | /// The return type of the WorkflowStep. 16 | /// A function that returns a WorkflowStep. 17 | /// An instance of a WorkflowBuilder. 18 | public IWorkflowBuilderConditionalFinalAggregate Do(Expression>> func); 19 | 20 | /// 21 | /// Designates the Workflow to stop execution if the condition is true 22 | /// 23 | /// An instance of a WorkflowBuilder. 24 | public IWorkflowBuilderConditionalFinalAggregate Stop(); 25 | 26 | /// 27 | /// Designates the Workflow to throw an exception if the condition is true. 28 | /// 29 | /// A function that returns an exception. 30 | /// An instance of a WorkflowBuilder. 31 | public IWorkflowBuilderConditionalFinalAggregate Throw(Expression> func); 32 | 33 | /// 34 | /// Defines the Workflow to retry the previous WorkflowStep. 35 | /// Optional max retries count can be passed in with the default value being 1. 36 | /// 37 | /// The amount of time to delay before retrying. 38 | /// Max number of retries before breaking. 39 | /// An instance of a WorkflowBuilder. 40 | public IWorkflowBuilderConditionalFinalAggregate Retry(TimeSpan delay , int maxRetries = 1); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /NetWorkflow/Interfaces/IWorkflowBuilderConditionalFinalAggregate.cs: -------------------------------------------------------------------------------- 1 | namespace NetWorkflow 2 | { 3 | /// 4 | /// Interace that defines the aggregation after else is used within a conditional WorkflowBuilder. 5 | /// 6 | public interface IWorkflowBuilderConditionalFinalAggregate 7 | { 8 | /// 9 | /// Ends the conditional statement within the Workflow and consolidates the result to use in the next WorkflowStep. 10 | /// 11 | /// An instance of a WorkflowBuilder. 12 | /// 13 | /// For compile time validation because each conditional WorkflowStep can return a different result 14 | /// the type is boxed into an object 15 | /// 16 | public IWorkflowBuilderNext EndIf(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /NetWorkflow/Interfaces/IWorkflowBuilderConditionalNext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace NetWorkflow 5 | { 6 | /// 7 | /// Interface that defines the following methods after the first conditional within a Workflow. 8 | /// 9 | /// The type of the incoming parameter. 10 | public interface IWorkflowBuilderConditionalNext 11 | { 12 | /// 13 | /// Defines a condition to execute in the Workflow if the condition is true. 14 | /// 15 | /// A function that requires the end result of the previous WorkflowStep and returns a boolean result. 16 | /// An instance of a WorkflowBuilder. 17 | public IWorkflowBuilderConditional ElseIf(Expression> func); 18 | 19 | /// 20 | /// Defines a catch all scenario in the Workflow to execute if previous conditions weren't met. 21 | /// 22 | /// An instance of a WorkflowBuilder. 23 | public IWorkflowBuilderConditionalFinal Else(); 24 | 25 | /// 26 | /// Ends the conditional statement within the Workflow and consolidates the result to use in the next WorkflowStep. 27 | /// 28 | /// An instance of a WorkflowBuilder. 29 | /// 30 | /// For compile time validation because each conditional WorkflowStep can return a different result the type is boxed into an object. 31 | /// 32 | public IWorkflowBuilderNext EndIf(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /NetWorkflow/Interfaces/IWorkflowBuilderNext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace NetWorkflow 6 | { 7 | /// 8 | /// Interface that defines the base WorkflowSteps on a Workflow after the initial WorkflowStep has been determined. 9 | /// 10 | /// The type of the incoming parameter. 11 | public interface IWorkflowBuilderNext : IWorkflowBuilder 12 | { 13 | /// 14 | /// Defines what WorkflowStep to execute next within the Workflow. 15 | /// 16 | /// The WorkflowStep's output type. 17 | /// A function that returns a WorkflowStep. 18 | /// An instance of a WorkflowBuilder. 19 | public IWorkflowBuilderNext Then(Expression>> func); 20 | 21 | /// 22 | /// Defines what asynchronous WorkflowStep to execute next within the Workflow. 23 | /// 24 | /// The WorkflowStep's output type. 25 | /// A function that returns a asynchronous WorkflowStep. 26 | /// An instance of a WorkflowBuilder. 27 | public IWorkflowBuilderNext ThenAsync(Expression>> func); 28 | 29 | /// 30 | /// Defines what asynchronous WorkflowSteps to execute next within the Workflow. Each WorkflowStep will execute on their own thread. 31 | /// 32 | /// The WorkflowStep's output type. 33 | /// A function that returns an enumeration of WorkflowSteps. 34 | /// An instance of a WorkflowBuilder. 35 | /// 36 | /// For compile time validation, each WorkflowStep must return the same type. The follwing WorkflowStep requires input type of TNext[]. 37 | /// The TNext[] will be ordered in the same as the AsyncSteps passed into the func parameter. 38 | /// 39 | public IWorkflowBuilderNext> Parallel(Expression>>> func); 40 | 41 | /// 42 | /// Defines the first conditional to execute following the last WorkflowStep. 43 | /// 44 | /// A function that requires the result of the previous Workflow and returns a boolean result. 45 | /// An instance of a WorkflowBuilder. 46 | public IWorkflowBuilderConditional If(Expression> func); 47 | } 48 | 49 | public interface IWorkflowBuilderNext : IWorkflowBuilderNext 50 | { 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /NetWorkflow/Interfaces/IWorkflowExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace NetWorkflow 5 | { 6 | internal interface IWorkflowExecutor : IDisposable { } 7 | 8 | internal interface IWorkflowExecutor : IWorkflowExecutor 9 | { 10 | TOut Run(TIn args, CancellationToken token = default); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /NetWorkflow/Interfaces/IWorkflowStep.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace NetWorkflow 5 | { 6 | /// 7 | /// Marker interface for the WorkflowStep. 8 | /// 9 | public interface IWorkflowStep { } 10 | 11 | /// 12 | /// Interface that defines a WorkflowStep that requires no input and defines an output of type TOut. 13 | /// 14 | /// The type of the WorkflowStep's output. 15 | public interface IWorkflowStep : IWorkflowStep 16 | { 17 | /// 18 | /// Runs the WorkflowStep and returns a result of type TOut. 19 | /// 20 | /// A optional CancellationToken that is passed throughout the Workflow. 21 | /// A result of type TOut. 22 | public TOut Run(CancellationToken token = default); 23 | } 24 | 25 | /// 26 | /// Interface that defines a WorkflowStep that requires an input of type TIn and defines an output of type TOut. 27 | /// 28 | /// The type of the previous output. 29 | /// The type of the WorkflowStep's output. 30 | public interface IWorkflowStep : IWorkflowStep 31 | { 32 | /// 33 | /// Runs the WorkflowStep and returns a result of type TOut. 34 | /// 35 | /// The required input of the WorkflowStep. 36 | /// A optional CancellationToken that is passed throughout the Workflow. 37 | /// A result of type TOut. 38 | public TOut Run(TIn args, CancellationToken token = default); 39 | } 40 | 41 | /// 42 | /// Marker interface that defines an async WorkflowStep. 43 | /// 44 | public interface IWorkflowStepAsync : IWorkflowStep { } 45 | 46 | /// 47 | /// Interface that defines an async WorkflowStep that requires no input and defines an output of type TOut. 48 | /// 49 | /// The type of the WorkflowStep's output. 50 | public interface IWorkflowStepAsync : IWorkflowStepAsync 51 | { 52 | /// 53 | /// Runs the WorkflowStep and returns a result of type TOut. 54 | /// 55 | /// A optional CancellationToken that is passed throughout the Workflow. 56 | /// A task with a result of type TOut. 57 | public Task RunAsync(CancellationToken token = default); 58 | } 59 | 60 | /// 61 | /// Interface that defines an async WorkflowStep that requires an input of type TIn and defines an output of type TOut. 62 | /// 63 | /// The type of the previous output. 64 | /// The type of the WorkflowStep's output. 65 | public interface IWorkflowStepAsync : IWorkflowStepAsync 66 | { 67 | /// 68 | /// Runs the WorkflowStep and returns a result of type TOut. 69 | /// 70 | /// The required input of the async WorkflowStep. 71 | /// A optional CancellationToken that is passed throughout the Workflow. 72 | /// A task with a result of type TOut. 73 | public Task RunAsync(TIn args, CancellationToken token = default); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /NetWorkflow/NetWorkflow.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Travis Arndt 5 | netstandard2.1;net6.0;net7.0; 6 | disable 7 | NetWorkflow 8 | Fluent .NET Workflow Library that allows a user to explicitly define their workflow steps in a single location with compile time validation. 9 | https://github.com/Tmarndt1/NetWorkflow 10 | README.md 11 | https://github.com/Tmarndt1/NetWorkflow 12 | Fluent;NetWorkflow;Net;Workflow; 13 | LICENSE 14 | 3.0.0 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | True 23 | \ 24 | 25 | 26 | 27 | 28 | True 29 | \ 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /NetWorkflow/Scheduler/WorkflowScheduler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace NetWorkflow.Scheduler 6 | { 7 | /// 8 | /// The WorkflowScheduler is a generic workflow scheduler that is responsible for scheduling and executing workflows. 9 | /// 10 | /// The Workflow to executed. 11 | /// The output type of the Workflow. 12 | public class WorkflowScheduler : IDisposable 13 | where TWorkflow : IWorkflow 14 | { 15 | // Declare the event. 16 | private Func _workflowFactory; 17 | 18 | private WorkflowSchedulerConfiguration _configuration = new WorkflowSchedulerConfiguration(); 19 | 20 | private int _count = 0; 21 | 22 | private bool _disposedValue; 23 | 24 | /// 25 | /// Designates the WorkflowScheduler to use the function to create the new Workflow. 26 | /// 27 | /// A function that returns a Workflow. 28 | /// The same instance of the WorkflowScheduler. 29 | public WorkflowScheduler(Func workflowFactory, Action> configuration) 30 | { 31 | if (workflowFactory == null) throw new ArgumentNullException(nameof(workflowFactory), "The WorkflowScheduler requires a workflow factory."); 32 | 33 | if (configuration == null) throw new ArgumentNullException(nameof(configuration), "The WorkflowScheduler requires a configuration to define when to execute thew workflow."); 34 | 35 | _workflowFactory = workflowFactory; 36 | 37 | configuration.Invoke(_configuration); 38 | } 39 | 40 | /// 41 | /// Starts the WorkflowScheduler and returns the Task/Thread the WorkflowScheduler is running on. 42 | /// 43 | /// The CancellationToken to cancel the request. 44 | /// A long running Task until canceled. 45 | public Task StartAsync(CancellationToken token = default) 46 | { 47 | if (_workflowFactory == null) 48 | { 49 | throw new InvalidOperationException($"A {nameof(WorkflowScheduler)} requires a Workflow Factory function."); 50 | } 51 | 52 | // A user should specify a WorkflowScheduler to execute a specific frequency or time. 53 | if (_configuration.ExecuteAt == null) 54 | { 55 | throw new InvalidOperationException($"A {nameof(WorkflowSchedulerConfiguration.ExecuteAt)} has not been set."); 56 | } 57 | 58 | return Task.Run(() => 59 | { 60 | if (_configuration.ExecuteAt is WorkflowFrequency workflowFrequency) 61 | { 62 | ExecuteAsync(workflowFrequency, token).Wait(); 63 | } 64 | else if (_configuration.ExecuteAt is WorkflowDateTime workflowDateTime) 65 | { 66 | ExecuteAsync(workflowDateTime, token).Wait(); 67 | } 68 | else 69 | { 70 | throw new InvalidOperationException("Invalid workflow execution configuration."); 71 | } 72 | }, token); 73 | } 74 | 75 | private async Task ExecuteAsync(WorkflowFrequency workflowFrequency, CancellationToken token) 76 | { 77 | while (!token.IsCancellationRequested) 78 | { 79 | await Task.Delay(workflowFrequency.Frequency, token); 80 | 81 | WorkflowResult output = _workflowFactory.Invoke().Run(token); 82 | 83 | _configuration?.OnExecuted?.Invoke(output); 84 | 85 | if (++_count == workflowFrequency.ExecutionCount) break; 86 | } 87 | } 88 | 89 | private async Task ExecuteAsync(WorkflowDateTime workflowDateTime, CancellationToken token) 90 | { 91 | while (!token.IsCancellationRequested && !_disposedValue) 92 | { 93 | if (workflowDateTime.IsNow()) 94 | { 95 | WorkflowResult output = _workflowFactory.Invoke().Run(token); 96 | 97 | _configuration?.OnExecuted?.Invoke(output); 98 | 99 | if (++_count == workflowDateTime.ExecutionCount) break; 100 | } 101 | 102 | await Task.Delay(60000, token); // Delay 1 minute 103 | } 104 | } 105 | 106 | protected virtual void Dispose(bool disposing) 107 | { 108 | if (!_disposedValue) 109 | { 110 | if (disposing) 111 | { 112 | _workflowFactory = null; 113 | _configuration = null; 114 | } 115 | 116 | _disposedValue = true; 117 | } 118 | } 119 | 120 | ~WorkflowScheduler() 121 | { 122 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 123 | Dispose(disposing: false); 124 | } 125 | 126 | public void Dispose() 127 | { 128 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 129 | Dispose(disposing: true); 130 | 131 | GC.SuppressFinalize(this); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /NetWorkflow/Scheduler/WorkflowSchedulerConfiguration.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | 4 | namespace NetWorkflow.Scheduler 5 | { 6 | /// 7 | /// The options to use within a WorkflowScheduler. 8 | /// 9 | public class WorkflowSchedulerConfiguration 10 | { 11 | private readonly string _changeExceptionMessage = $"Cannot change the {nameof(WorkflowSchedulerConfiguration)} after initial definition."; 12 | 13 | private bool _executeAtSet = false; 14 | 15 | private WorkflowTime _executeAt; 16 | 17 | /// 18 | /// The specific time when a Workflow should be executed. 19 | /// 20 | /// 21 | /// A specific day/hour/minute, hour/minute, or minute mark can be determined to run the Workflow. 22 | /// 23 | public WorkflowTime ExecuteAt 24 | { 25 | get => _executeAt; 26 | set 27 | { 28 | if (_executeAtSet) throw new InvalidOperationException(_changeExceptionMessage); 29 | 30 | _executeAtSet = true; 31 | 32 | _executeAt = value; 33 | } 34 | } 35 | 36 | private bool _onExecutedSet = false; 37 | 38 | private Action> _onExecuted; 39 | 40 | /// 41 | /// Provides a hook into retrieving the result of an executed Workflow. 42 | /// Will be called once a Workflow has been completed, canceled or faulted. 43 | /// 44 | public Action> OnExecuted 45 | { 46 | get => _onExecuted; 47 | set 48 | { 49 | if (_onExecutedSet) throw new InvalidOperationException(_changeExceptionMessage); 50 | 51 | _onExecutedSet= true; 52 | 53 | _onExecuted = value; 54 | } 55 | } 56 | 57 | internal WorkflowSchedulerConfiguration() { } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /NetWorkflow/Scheduler/WorkflowTime.cs: -------------------------------------------------------------------------------- 1 | using NetWorkflow.Exceptions; 2 | using System; 3 | 4 | namespace NetWorkflow.Scheduler 5 | { 6 | /// 7 | /// WorkflowTime represents when a WorkflowScheduler should execute a Workflow 8 | /// 9 | /// 10 | /// Days span from 1 to 31. 11 | /// Hours are in military time so they span from 0 to 23. 12 | /// Minutes span from 0 to 59. 13 | /// 14 | public abstract class WorkflowTime 15 | { 16 | /// 17 | /// Designates the WorkflowScheduler to execute the Workflow at the given frequency 18 | /// 19 | /// The frequency to execute the Workflow 20 | /// An instance of WorkflowFrequency 21 | public static WorkflowFrequency AtFrequency(TimeSpan frequency) 22 | { 23 | return new WorkflowFrequency(frequency); 24 | } 25 | 26 | /// 27 | /// Designates a WorkflowScheduler should execute a Workflow at the given day, hour, and minute. 28 | /// 29 | /// The day of the month the Workflow should be executed. 30 | /// The hour of the day the Workflow should be executed. 31 | /// The minute of the hour the Workflow should be executed. 32 | /// A new instance of WorkflowTime. 33 | public static WorkflowDateTime AtDay(int day, int hour, int minute) 34 | { 35 | return new WorkflowDateTime(day, hour, minute); 36 | } 37 | 38 | /// 39 | /// Designates a WorkflowScheduler should execute a Workflow at the given day, hour, and minute 0. 40 | /// 41 | /// The day of the month the Workflow should be executed. 42 | /// The hour of the day the Workflow should be executed. 43 | /// A new instance of WorkflowTime. 44 | /// 45 | /// Will execute at the beginning of the provided day and hour. 46 | /// 47 | public static WorkflowDateTime AtDay(int day, int hour) 48 | { 49 | return new WorkflowDateTime(day, hour, 0); 50 | } 51 | 52 | /// 53 | /// Designates a WorkflowScheduler should execute a Workflow on the given day, hour 0, and minute 0. 54 | /// 55 | /// The day of the month the Workflow should be executed. 56 | /// A new instance of WorkflowTime. 57 | /// 58 | /// Will execute at midnight on the given day. 59 | /// 60 | public static WorkflowDateTime AtDay(int day) 61 | { 62 | return new WorkflowDateTime(day, 0, 0); 63 | } 64 | 65 | /// 66 | /// Designates a WorkflowScheduler should execute a Workflow at the given 67 | /// hour and at the given minute. 68 | /// 69 | /// The hour of the day the Workflow should be executed. 70 | /// The minute of the hour the Workflow should be executed. 71 | /// A new instance of WorkflowTime. 72 | public static WorkflowDateTime AtHour(int hour, int minute) 73 | { 74 | return new WorkflowDateTime(hour, minute); 75 | } 76 | 77 | /// 78 | /// Designates a WorkflowScheduler should execute a Workflow at the given hour and minute 0. 79 | /// 80 | /// The hour of the day the Workflow should be executed. 81 | /// A new instance of WorkflowTime. 82 | public static WorkflowDateTime AtHour(int hour) 83 | { 84 | return new WorkflowDateTime(hour, 0); 85 | } 86 | 87 | /// 88 | /// Designates a WorkflowScheduler should execute a Workflow at the given minute. 89 | /// 90 | /// The minute of the hour the Workflow should be executed. 91 | /// A new instance of WorkflowTime. 92 | public static WorkflowDateTime AtMinute(int minute) 93 | { 94 | return new WorkflowDateTime(minute); 95 | } 96 | 97 | /// 98 | /// Designates the Workflow to execute until the count is met. 99 | /// 100 | /// The max amount of times the Workflow should execute. 101 | /// The same instance of the WorkflowTime. 102 | public WorkflowTime Until(int count) 103 | { 104 | ExecutionCount = count; 105 | return this; 106 | } 107 | 108 | /// 109 | /// Designates the Workflow to execute until the count is met. 110 | /// 111 | internal int ExecutionCount { get; private set; } = -1; 112 | } 113 | 114 | public class WorkflowDateTime : WorkflowTime 115 | { 116 | /// 117 | /// The day of the month a Workflow should be executed. 118 | /// 119 | public int Day { get; private set; } = -1; 120 | 121 | /// 122 | /// The hour of the day a Workflow should be executed. 123 | /// 124 | public int Hour { get; private set; } = -1; 125 | 126 | /// 127 | /// The minute of the hour a Workflow should be executed. 128 | /// 129 | public int Minute { get; private set; } 130 | 131 | internal WorkflowDateTime(int day, int hour, int minute) 132 | { 133 | Day = ValidateDay(day); 134 | Hour = ValidateHour(hour); 135 | Minute = ValidateMinute(minute); 136 | } 137 | 138 | internal WorkflowDateTime(int hour, int minute) 139 | { 140 | Hour = ValidateHour(hour); 141 | Minute = ValidateMinute(minute); 142 | } 143 | 144 | internal WorkflowDateTime(int minute) 145 | { 146 | Minute = ValidateMinute(minute); 147 | } 148 | 149 | private static int ValidateDay(int day) 150 | { 151 | if (day < 1 || day > 31) 152 | { 153 | throw new WorkflowInvalidValueException("Day must be between 1 and 31."); 154 | } 155 | return day; 156 | } 157 | 158 | private static int ValidateHour(int hour) 159 | { 160 | if (hour < 0 || hour > 23) 161 | { 162 | throw new WorkflowInvalidValueException("Hour must be between 0 and 23."); 163 | } 164 | return hour; 165 | } 166 | 167 | private static int ValidateMinute(int minute) 168 | { 169 | if (minute < 0 || minute > 59) 170 | { 171 | throw new WorkflowInvalidValueException("Minute must be between 0 and 59."); 172 | } 173 | return minute; 174 | } 175 | 176 | internal bool IsNow() 177 | { 178 | DateTime now = DateTime.Now; 179 | 180 | if (Day != -1) 181 | { 182 | return now.Day == Day && now.Hour == Hour && now.Minute == Minute; 183 | } 184 | else if (Hour != -1) 185 | { 186 | return now.Hour == Hour && now.Minute == Minute; 187 | } 188 | else if (Minute != -1) 189 | { 190 | return now.Minute == Minute; 191 | } 192 | 193 | return false; 194 | } 195 | } 196 | 197 | public class WorkflowFrequency : WorkflowTime 198 | { 199 | /// 200 | /// Determines the frequency of how often the WorkflowScheduler executes a Workflow 201 | /// 202 | public TimeSpan Frequency { get; } 203 | 204 | internal WorkflowFrequency(TimeSpan frequency) 205 | { 206 | Frequency = frequency; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /NetWorkflow/Workflow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace NetWorkflow 6 | { 7 | /// 8 | /// Defines a Workflow and runs the various WorkflowSteps in sequence that are established within the Build method. 9 | /// 10 | public abstract class Workflow : IWorkflow, IDisposable 11 | { 12 | private readonly WorkflowBuilder _next; 13 | 14 | private WorkflowOptions _options; 15 | 16 | private bool _disposedValue; 17 | 18 | /// 19 | /// Workflow constructor. 20 | /// 21 | protected Workflow() 22 | { 23 | _next = new WorkflowBuilder(); 24 | } 25 | 26 | /// 27 | /// Overloaded Workflow constructor that requires WorkflowOptions for enhanced usablility. 28 | /// 29 | /// The WorkflowOptions to pass within a Workflow to provide tailored functionality. 30 | protected Workflow(WorkflowOptions options) : this() 31 | { 32 | _options = options; 33 | } 34 | 35 | /// 36 | /// Abstract method that injects a IWorkflowBuilder to build the steps of the Workflow. 37 | /// The Workflow is lazily built when the Run method is invoked. 38 | /// 39 | /// The IWorkflowBuilder to build the Workflow's steps. 40 | public abstract IWorkflowBuilder Build(IWorkflowBuilder builder); 41 | 42 | /// 43 | /// Builds and runs the Workflow and returns a final result if each step has executed successfully. 44 | /// 45 | /// The CancellationToken to cancel the workflow. 46 | /// A generic WorkflowResult. 47 | public WorkflowResult Run(CancellationToken token = default) 48 | { 49 | if (_disposedValue) 50 | { 51 | throw new ObjectDisposedException(typeof(WorkflowResult).Name); 52 | } 53 | 54 | // Builds the Workflow 55 | Build(_next); 56 | 57 | DateTime timestamp = DateTime.Now; 58 | 59 | try 60 | { 61 | TOut result = (TOut)_next.Run(default, token); 62 | 63 | return WorkflowResult.Success(result, DateTime.Now - timestamp); 64 | } 65 | catch (OperationCanceledException) 66 | { 67 | if (_options?.Rethrow == true) throw; 68 | 69 | return WorkflowResult.Cancelled(DateTime.Now - timestamp); 70 | } 71 | catch (WorkflowStoppedException) 72 | { 73 | if (_options?.Rethrow == true) throw; 74 | 75 | return WorkflowResult.Cancelled(DateTime.Now - timestamp); 76 | } 77 | catch (Exception ex) 78 | { 79 | if (_options?.Rethrow == true) throw; 80 | 81 | return WorkflowResult.Faulted(ex.InnerException ?? ex, DateTime.Now - timestamp); 82 | } 83 | } 84 | /// 85 | /// Builds and runs the Workflow asynchronously and returns a final result if each step has executed successfully 86 | /// 87 | /// The CancellationToken to cancel the workflow. 88 | /// A Task with a generic WorkflowResult. 89 | public Task> RunAsync(CancellationToken token = default) 90 | { 91 | return Task.Run(() => Run(token), token); 92 | } 93 | 94 | protected virtual void Dispose(bool disposing) 95 | { 96 | if (!_disposedValue) 97 | { 98 | if (disposing) 99 | { 100 | _next.Dispose(); 101 | } 102 | 103 | _options = null; 104 | 105 | _disposedValue = true; 106 | } 107 | } 108 | 109 | public void Dispose() 110 | { 111 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 112 | Dispose(disposing: true); 113 | 114 | GC.SuppressFinalize(this); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /NetWorkflow/WorkflowBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Threading; 5 | 6 | namespace NetWorkflow 7 | { 8 | internal class WorkflowBuilder : IWorkflowBuilder, IDisposable 9 | { 10 | protected WorkflowBuilder _next; 11 | 12 | private bool _disposedValue; 13 | 14 | public object Result { get; protected set; } 15 | 16 | public IWorkflowBuilderNext StartWith(Expression>> func) 17 | { 18 | _next = new WorkflowBuilder(new WorkflowStepExecutor(func)); 19 | 20 | return (IWorkflowBuilderNext)_next; 21 | } 22 | 23 | public virtual object Run(object args, CancellationToken token = default) => _next?.Run(args, token); 24 | 25 | protected virtual void Dispose(bool disposing) 26 | { 27 | if (!_disposedValue) 28 | { 29 | if (disposing) 30 | { 31 | _next?.Dispose(); 32 | 33 | Result = null; 34 | } 35 | 36 | _disposedValue = true; 37 | } 38 | } 39 | 40 | ~WorkflowBuilder() 41 | { 42 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 43 | Dispose(disposing: false); 44 | } 45 | 46 | public void Dispose() 47 | { 48 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 49 | Dispose(disposing: true); 50 | 51 | GC.SuppressFinalize(this); 52 | } 53 | } 54 | 55 | internal class WorkflowBuilder : WorkflowBuilder, IWorkflowBuilderNext 56 | { 57 | internal readonly IWorkflowExecutor _executor; 58 | 59 | private bool _disposedValue; 60 | 61 | internal WorkflowBuilder(IWorkflowExecutor executor) 62 | { 63 | _executor = executor; 64 | } 65 | 66 | public IWorkflowBuilderNext> Parallel(Expression>>> func) 67 | { 68 | _next = new WorkflowBuilder>(new WorkflowParallelExecutor(func)); 69 | 70 | return (IWorkflowBuilderNext>)_next; 71 | } 72 | 73 | public IWorkflowBuilderNext Then(Expression>> func) 74 | { 75 | _next = new WorkflowBuilder(new WorkflowStepExecutor(func)); 76 | 77 | return (IWorkflowBuilderNext)_next; 78 | } 79 | 80 | public IWorkflowBuilderNext ThenAsync(Expression>> func) 81 | { 82 | _next = new WorkflowBuilder(new WorkflowStepAsyncExecutor(func)); 83 | 84 | return (IWorkflowBuilderNext)_next; 85 | } 86 | 87 | public IWorkflowBuilderConditional If(Expression> func) 88 | { 89 | _next = new WorkflowBuilderConditional(new WorkflowExecutorConditional(func), this); 90 | 91 | return (IWorkflowBuilderConditional)_next; 92 | } 93 | 94 | public override object Run(object args, CancellationToken token = default) 95 | { 96 | Result = _executor.Run((TIn)args, token); 97 | 98 | if (_next == null) return Result; 99 | 100 | return _next?.Run(Result, token); 101 | } 102 | 103 | protected override void Dispose(bool disposing) 104 | { 105 | if (!_disposedValue) 106 | { 107 | if (disposing) 108 | { 109 | _executor?.Dispose(); 110 | } 111 | 112 | _disposedValue = true; 113 | 114 | base.Dispose(disposing); 115 | } 116 | } 117 | } 118 | 119 | internal class WorkflowBuilderConditional : WorkflowBuilder, 120 | IWorkflowBuilderConditional, 121 | IWorkflowBuilderConditionalNext, 122 | IWorkflowBuilderConditionalFinal, 123 | IWorkflowBuilderConditionalFinalAggregate 124 | { 125 | private readonly WorkflowExecutorConditional _executor; 126 | 127 | private readonly WorkflowBuilder _lastBuilder; 128 | 129 | private CancellationToken _token; 130 | 131 | private bool _disposedValue; 132 | 133 | public WorkflowBuilderConditional(WorkflowExecutorConditional executor, WorkflowBuilder lastBuilder) 134 | { 135 | _executor = executor; 136 | 137 | _lastBuilder = lastBuilder; 138 | } 139 | 140 | public IWorkflowBuilderConditionalNext Do(Expression>> func) 141 | { 142 | _executor.Append(new WorkflowStepExecutor(func)); 143 | 144 | return this; 145 | } 146 | 147 | public IWorkflowBuilderConditional ElseIf(Expression> func) 148 | { 149 | _executor.Append(func); 150 | 151 | return this; 152 | } 153 | 154 | public IWorkflowBuilderConditionalFinal Else() 155 | { 156 | _executor.Append(args => true); 157 | 158 | return this; 159 | } 160 | 161 | public IWorkflowBuilderNext EndIf() 162 | { 163 | _next = new WorkflowBuilder(new WorkflowMoveNextExecutor()); 164 | 165 | return (IWorkflowBuilderNext)_next; 166 | } 167 | 168 | public IWorkflowBuilderConditionalNext Stop() 169 | { 170 | _executor.Stop(); 171 | 172 | return this; 173 | } 174 | 175 | public IWorkflowBuilderConditionalNext Throw(Expression> func) 176 | { 177 | _executor.OnExceptionDo(func); 178 | 179 | return this; 180 | } 181 | 182 | public IWorkflowBuilderConditionalNext Retry(TimeSpan delay, int maxRetries = 1) 183 | { 184 | _executor.SetRetry(delay, maxRetries, () => _lastBuilder.Run(_lastBuilder.Result, _token)); 185 | 186 | return this; 187 | } 188 | 189 | public override object Run(object args, CancellationToken token = default) 190 | { 191 | _token = token; 192 | 193 | Result = _executor.Run((TIn)args, token); 194 | 195 | if (_next == null) return Result; 196 | 197 | return _next?.Run(Result, token); 198 | } 199 | 200 | IWorkflowBuilderConditionalFinalAggregate IWorkflowBuilderConditionalFinal.Do(Expression>> func) 201 | { 202 | Do(func); 203 | 204 | return this; 205 | } 206 | 207 | IWorkflowBuilderConditionalFinalAggregate IWorkflowBuilderConditionalFinal.Stop() 208 | { 209 | Stop(); 210 | 211 | return this; 212 | } 213 | 214 | IWorkflowBuilderConditionalFinalAggregate IWorkflowBuilderConditionalFinal.Throw(Expression> func) 215 | { 216 | Throw(func); 217 | 218 | return this; 219 | } 220 | 221 | IWorkflowBuilderConditionalFinalAggregate IWorkflowBuilderConditionalFinal.Retry(TimeSpan delay, int maxRetries) 222 | { 223 | Retry(delay, maxRetries); 224 | 225 | return this; 226 | } 227 | 228 | protected override void Dispose(bool disposing) 229 | { 230 | if (!_disposedValue) 231 | { 232 | if (disposing) 233 | { 234 | _executor?.Dispose(); 235 | } 236 | 237 | _disposedValue = true; 238 | 239 | base.Dispose(disposing); 240 | } 241 | } 242 | } 243 | } 244 | 245 | -------------------------------------------------------------------------------- /NetWorkflow/WorkflowOptions.cs: -------------------------------------------------------------------------------- 1 | namespace NetWorkflow 2 | { 3 | /// 4 | /// WorkflowOptions to pass within a Workflow to provide tailored functionality 5 | /// 6 | public class WorkflowOptions 7 | { 8 | /// 9 | /// Enables re-throwing exceptions within the Workflow when they are caught 10 | /// 11 | public bool Rethrow { get; set; } = false; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /NetWorkflow/WorkflowResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetWorkflow 4 | { 5 | /// 6 | /// Represents the result of a workflow, providing information about its execution. 7 | /// 8 | /// The type of the Workflow's output. 9 | public sealed class WorkflowResult 10 | { 11 | /// 12 | /// A user friendly string message capturing the end result of the Workflow. 13 | /// 14 | public string Message { get; private set; } = "The Workflow completed successfully."; 15 | 16 | /// 17 | /// Boolean value on whether the Workflow has successfully completed all its steps. 18 | /// 19 | public bool IsCompleted { get; private set; } = false; 20 | 21 | /// 22 | /// Boolean value on whether the Workflow has catched an exception within the steps. 23 | /// 24 | public bool IsFaulted => Exception != null; 25 | 26 | /// 27 | /// Boolean value on whether the Worfklow has been canceled. 28 | /// 29 | public bool IsCanceled { get; private set; } = false; 30 | 31 | /// 32 | /// The catched exception if thrown within the steps 33 | /// 34 | public Exception Exception { get; private set; } 35 | 36 | /// 37 | /// The duration from when the Workflow was initially ran until the WorkflowResult is returned. 38 | /// 39 | public TimeSpan Duration { get; private set; } 40 | 41 | /// 42 | /// The Workflow's output. 43 | /// 44 | public TOut Output { get; private set; } 45 | 46 | internal static WorkflowResult Success(TOut output, TimeSpan duration) 47 | { 48 | return new WorkflowResult() 49 | { 50 | Message = "The Workflow has completed successfully.", 51 | Output = output, 52 | Duration = duration, 53 | IsCompleted = true 54 | }; 55 | } 56 | 57 | internal static WorkflowResult Faulted(Exception ex, TimeSpan duration) 58 | { 59 | return new WorkflowResult() 60 | { 61 | Message = "The Workflow was stopped because an exception was thrown.", 62 | Exception = ex, 63 | Duration = duration 64 | }; 65 | } 66 | 67 | internal static WorkflowResult Cancelled(TimeSpan duration) 68 | { 69 | return new WorkflowResult() 70 | { 71 | Message = "The Workflow was canceled.", 72 | IsCanceled = true, 73 | Duration = duration 74 | }; 75 | } 76 | 77 | public static implicit operator TOut(WorkflowResult result) => result.Output; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetWorkflow 2 | 3 | NetWorkflow is a light weight C# .NET Workflow library that leverages the fluent syntax and allows a user to explicitly define their WorkflowSteps in a single location with compile time validation between WorkflowStep inputs and outputs. 4 | 5 | main: ![Build Status](https://github.com/Tmarndt1/NetWorkflow/workflows/.NET/badge.svg?branch=main) 6 | 7 | ## Give a Star! :star: 8 | 9 | If you like or are using this project please give it a star. Thanks! 10 | 11 | ## HelloWorld Workflow Example 12 | 13 | ```csharp 14 | 15 | using NetWorkflow; 16 | 17 | public class HelloWorldWorkflow : Workflow 18 | { 19 | private const string _helloWorld = "HelloWorld"; 20 | 21 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 22 | builder 23 | .StartWith(() => new HelloWorld()) 24 | .Then(() => new GoodbyeWorld()); 25 | 26 | private class HelloWorld : IWorkflowStep 27 | { 28 | public string Run(CancellationToken token = default) 29 | { 30 | return _helloWorld; 31 | } 32 | } 33 | 34 | private class GoodbyeWorld : IWorkflowStep 35 | { 36 | public bool Run(string args, CancellationToken token = default) 37 | { 38 | return args == _helloWorld; 39 | } 40 | } 41 | } 42 | 43 | ``` 44 | 45 | ## Conditional Workflow Example 46 | 47 | ```csharp 48 | 49 | using NetWorkflow; 50 | 51 | public class ConditionalWorkflow : Workflow 52 | { 53 | private readonly string _message; 54 | 55 | public ConditionalWorkflow(string message) 56 | { 57 | _message = message; 58 | } 59 | 60 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 61 | builder 62 | .StartWith(() => new FirstStep(_message)) 63 | .If(x => x == "Success") 64 | .Do(() => new ConditionalStep()) 65 | .ElseIf(x => x == "Failed") 66 | .Do(() => new ConditionalStep()) 67 | .Else() 68 | .Retry(TimeSpan.FromSeconds(10), 2) // Max retry 2 times 69 | .EndIf() 70 | .Then(() => new FinalStep()); 71 | 72 | 73 | // Example WorkflowStep 74 | private class FirstStep : IWorkflowStep 75 | { 76 | private readonly string _message; 77 | 78 | internal FirstStep(string message) 79 | { 80 | _message = message; 81 | } 82 | 83 | public string Run(CancellationToken token = default) 84 | { 85 | return _message; 86 | } 87 | } 88 | 89 | // Other WorkflowSteps are omitted to simplify example! 90 | } 91 | 92 | // Implicit cast to an int 93 | int result = new ConditionalWorkflow().Run(); 94 | 95 | ``` 96 | 97 | ## Parallel Workflow Example 98 | 99 | ```csharp 100 | 101 | using NetWorkflow; 102 | 103 | public class ParallelWorkflow : Workflow 104 | { 105 | public override IWorkflowBuilder Build(IWorkflowBuilder builder) => 106 | builder 107 | .StartWith(() => new Step1()) 108 | .Parallel(() => new IWorkflowStepAsync[] 109 | { 110 | new AsyncStep1(), 111 | new AsyncStep2() 112 | }) 113 | .ThenAsync(() => new AsyncStep3()) 114 | .ThenAsync(() => AsyncStep4()); 115 | 116 | 117 | // Example Async WorkflowStep 118 | private class AsyncStep1 : IWorkflowStepAsync 119 | { 120 | public Task RunAsync(Guid args, CancellationToken token = default) 121 | { 122 | return Task.Delay(500, token).ContinueWith(t => new string[] 123 | { 124 | "Hello", 125 | "World" 126 | }, token); 127 | } 128 | } 129 | 130 | // Other WorkflowSteps are omitted to simplify example! 131 | } 132 | 133 | // Implicit cast to a string array 134 | string[] results = new ParallelWorkflow() 135 | .Run(new CancellationTokenSource().Token); 136 | 137 | ``` 138 | 139 | ## WorkflowScheduler AtFrequency Example 140 | 141 | ```csharp 142 | 143 | using NetWorkflow.Scheduler; 144 | 145 | var scheduler = new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 146 | { 147 | // Will execute every 10 seconds until the Workflow has been executed 5 times. 148 | config.ExecuteAt = WorkflowTime.AtFrequency(TimeSpan.FromSeconds(10)).Until(5); 149 | config.OnExecuted = (WorkflowResult result) => 150 | { 151 | Console.WriteLine($"Workflow result: {result.Output}"); 152 | } 153 | }); 154 | 155 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 156 | 157 | _ = scheduler.StartAsync(tokenSource.Token); 158 | 159 | ``` 160 | 161 | ## WorkflowScheduler AtDateTime Example 162 | 163 | ```csharp 164 | 165 | using NetWorkflow.Scheduler; 166 | 167 | var scheduler = new WorkflowScheduler(() => new HelloWorldWorkflow(), config => 168 | { 169 | config.ExecuteAt = WorkflowTime.AtMinute(DateTime.Now.Minute); 170 | config.OnExecuted = (WorkfowResult result) => 171 | { 172 | Console.WriteLine($"Workflow result: {result.Output}"); 173 | } 174 | }); 175 | 176 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 177 | 178 | _ = scheduler.StartAsync(tokenSource.Token); 179 | 180 | ``` 181 | 182 | ## Authors 183 | 184 | - **Travis Arndt** 185 | 186 | ## License 187 | 188 | This project is licensed under the MIT License - [LICENSE.md](LICENSE) 189 | --------------------------------------------------------------------------------