From db5760b3362e3a165135caa422175391267c8634 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=90=BD=E7=AC=94?=
<46271592+Skymly@users.noreply.github.com>
Date: Mon, 22 Jun 2026 23:22:28 +0800
Subject: [PATCH] Add State v2 guard and DI integration demos to State sample
Extends the State sample to demonstrate v2 features:
- Guard delegates on [Transition] with CanCancelDraft/CanCancelSubmitted
static guard methods on the holder class
- DI integration via generated RegisterDi + AddTransitionTable extension
- GetAllowedTriggers output showing terminal state (Paid)
- Cancelled state added to enum for richer edge coverage
- DesignPatternsSampleKind changed from Core to DependencyInjection to
pull in the DI extension and enable DesignPatterns_EnableDiIntegration
---
.../DesignPatterns.Samples.State.csproj | 4 +-
DesignPatterns.Samples.State/OrderMachine.cs | 21 ++++++-
DesignPatterns.Samples.State/Program.cs | 55 ++++++++++++++++---
3 files changed, 69 insertions(+), 11 deletions(-)
diff --git a/DesignPatterns.Samples.State/DesignPatterns.Samples.State.csproj b/DesignPatterns.Samples.State/DesignPatterns.Samples.State.csproj
index ea758c0..08be9f5 100644
--- a/DesignPatterns.Samples.State/DesignPatterns.Samples.State.csproj
+++ b/DesignPatterns.Samples.State/DesignPatterns.Samples.State.csproj
@@ -6,8 +6,8 @@
enable
enable
DesignPatterns.Samples.State
- Core
- Console sample for state transition tables (manual builder and [StateMachine] generator).
+ DependencyInjection
+ Console sample for state transition tables: manual builder, [StateMachine] generator, guard delegates, and DI integration.
diff --git a/DesignPatterns.Samples.State/OrderMachine.cs b/DesignPatterns.Samples.State/OrderMachine.cs
index 7cd6529..d288052 100644
--- a/DesignPatterns.Samples.State/OrderMachine.cs
+++ b/DesignPatterns.Samples.State/OrderMachine.cs
@@ -1,21 +1,38 @@
using DesignPatterns.Behavioral;
-namespace State.Sample;
+namespace DesignPatterns.Samples.State;
public enum OrderStatus
{
Draft,
Submitted,
Paid,
+ Cancelled,
}
public enum OrderTrigger
{
Submit,
Pay,
+ Cancel,
}
[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft)]
[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)]
+[Transition(OrderStatus.Draft, OrderTrigger.Cancel, OrderStatus.Cancelled, Guard = nameof(CanCancelDraft))]
[Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)]
-public static partial class OrderMachine;
+[Transition(OrderStatus.Submitted, OrderTrigger.Cancel, OrderStatus.Cancelled, Guard = nameof(CanCancelSubmitted))]
+public static partial class OrderMachine
+{
+ ///
+ /// Guard: draft orders can only be cancelled when no items have been submitted yet.
+ /// Demonstrates a guard that always passes in the sample (no domain state).
+ ///
+ public static bool CanCancelDraft(OrderStatus state, OrderTrigger trigger) => true;
+
+ ///
+ /// Guard: submitted orders can only be cancelled before payment is processed.
+ /// Demonstrates a guard that always passes in the sample (no domain state).
+ ///
+ public static bool CanCancelSubmitted(OrderStatus state, OrderTrigger trigger) => true;
+}
diff --git a/DesignPatterns.Samples.State/Program.cs b/DesignPatterns.Samples.State/Program.cs
index 084e951..853dad3 100644
--- a/DesignPatterns.Samples.State/Program.cs
+++ b/DesignPatterns.Samples.State/Program.cs
@@ -1,26 +1,67 @@
using DesignPatterns.Behavioral;
-using State.Sample;
+using DesignPatterns.Extensions.DependencyInjection;
+using DesignPatterns.Samples.State;
+using Microsoft.Extensions.DependencyInjection;
-Console.WriteLine("=== Manual TransitionTableBuilder ===");
+Console.WriteLine("=== Manual TransitionTableBuilder (with guard) ===");
var manualTable = new TransitionTableBuilder()
.WithInitial(OrderStatus.Draft)
.Add(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)
+ .Add(OrderStatus.Draft, OrderTrigger.Cancel, OrderStatus.Cancelled,
+ guard: (state, trigger) => true) // guard always passes in sample
.Add(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)
+ .Add(OrderStatus.Submitted, OrderTrigger.Cancel, OrderStatus.Cancelled,
+ guard: (state, trigger) => true)
.Build();
RunTransition(manualTable, OrderStatus.Draft, OrderTrigger.Submit);
RunTransition(manualTable, OrderStatus.Submitted, OrderTrigger.Pay);
+RunTransition(manualTable, OrderStatus.Draft, OrderTrigger.Cancel);
AssertInvalid(manualTable, OrderStatus.Paid, OrderTrigger.Pay);
Console.WriteLine();
-Console.WriteLine("=== Generated [StateMachine] table ===");
+Console.WriteLine("=== Generated [StateMachine] table (with guard) ===");
Console.WriteLine($"Initial state: {OrderMachine.InitialState}");
RunGenerated(OrderStatus.Draft, OrderTrigger.Submit);
RunGenerated(OrderStatus.Submitted, OrderTrigger.Pay);
+RunGenerated(OrderStatus.Draft, OrderTrigger.Cancel);
+RunGenerated(OrderStatus.Submitted, OrderTrigger.Cancel);
AssertInvalidGenerated(OrderStatus.Paid, OrderTrigger.Pay);
+Console.WriteLine();
+Console.WriteLine("=== DI integration (RegisterDi + AddTransitionTable) ===");
+
+// Generated RegisterDi: registers ITransitionTable as singleton
+var services = new ServiceCollection();
+OrderStatusTransitionTable.RegisterDi(services);
+
+// Also demonstrate manual AddTransitionTable extension (TryAdd idempotent — won't overwrite)
+services.AddTransitionTable(OrderStatusTransitionTable.Instance);
+
+var provider = services.BuildServiceProvider();
+var diTable = provider.GetRequiredService>();
+
+Console.WriteLine($"Resolved from DI: {diTable.GetType().Name}");
+Console.WriteLine($"DI table initial state: {diTable.InitialState}");
+
+RunTransition(diTable, OrderStatus.Draft, OrderTrigger.Submit);
+RunTransition(diTable, OrderStatus.Submitted, OrderTrigger.Pay);
+AssertInvalid(diTable, OrderStatus.Cancelled, OrderTrigger.Submit);
+
+Console.WriteLine();
+Console.WriteLine("=== GetAllowedTriggers ===");
+
+var allowed = diTable.GetAllowedTriggers(OrderStatus.Draft);
+Console.WriteLine($"Allowed triggers from Draft: {string.Join(", ", allowed)}");
+
+var submittedAllowed = diTable.GetAllowedTriggers(OrderStatus.Submitted);
+Console.WriteLine($"Allowed triggers from Submitted: {string.Join(", ", submittedAllowed)}");
+
+var paidAllowed = diTable.GetAllowedTriggers(OrderStatus.Paid);
+Console.WriteLine($"Allowed triggers from Paid (terminal): {(paidAllowed.Count == 0 ? "(none — terminal state)" : string.Join(", ", paidAllowed))}");
+
static void RunTransition(
ITransitionTable table,
OrderStatus current,
@@ -28,7 +69,7 @@ static void RunTransition(
{
if (table.TryTransition(current, trigger, out var next))
{
- Console.WriteLine($"{current} + {trigger} -> {next}");
+ Console.WriteLine($" {current} + {trigger} -> {next}");
}
}
@@ -39,7 +80,7 @@ static void AssertInvalid(
{
if (!table.TryTransition(current, trigger, out _))
{
- Console.WriteLine($"{current} + {trigger} is invalid (expected).");
+ Console.WriteLine($" {current} + {trigger} is invalid (expected).");
}
}
@@ -47,7 +88,7 @@ static void RunGenerated(OrderStatus current, OrderTrigger trigger)
{
if (OrderMachine.TryTransition(current, trigger, out var next))
{
- Console.WriteLine($"{current} + {trigger} -> {next}");
+ Console.WriteLine($" {current} + {trigger} -> {next}");
}
}
@@ -55,6 +96,6 @@ static void AssertInvalidGenerated(OrderStatus current, OrderTrigger trigger)
{
if (!OrderMachine.TryTransition(current, trigger, out _))
{
- Console.WriteLine($"{current} + {trigger} is invalid (expected).");
+ Console.WriteLine($" {current} + {trigger} is invalid (expected).");
}
}