Skip to content

Commit 09cb9a8

Browse files
authored
Merge pull request #1 from Jakab-Laszlo/feature/state-machine
State machine commons +semver: minor
2 parents d387887 + 5b24e55 commit 09cb9a8

File tree

7 files changed

+170
-48
lines changed

7 files changed

+170
-48
lines changed

src/AutSoft.Core/AutSoft.Common.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
10+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
11+
<PackageReference Include="Microsoft.EntityFrameworkCore" />
12+
<PackageReference Include="Stateless" />
1113
</ItemGroup>
1214

1315
</Project>
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
using AutSoft.Common.Exceptions;
2+
using AutSoft.Common.Time;
3+
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Infrastructure;
6+
using Microsoft.Extensions.DependencyInjection;
7+
8+
using Stateless;
9+
10+
using System.Linq.Expressions;
11+
12+
namespace AutSoft.Common.StateMachines;
13+
14+
/// <summary>
15+
/// Abstract base class of state machines, which add functionalities for Stateless StateMachine class
16+
/// </summary>
17+
/// <typeparam name="TState">Type of state machine's states</typeparam>
18+
/// <typeparam name="TTrigger">Type of state machine's triggers</typeparam>
19+
/// <typeparam name="TEntity">The type of entity whose state is described by the state machine</typeparam>
20+
public abstract class EntityStateMachineBase<TState, TTrigger, TEntity> : StateMachine<TState, TTrigger>
21+
where TState : struct, Enum
22+
where TTrigger : Enum
23+
where TEntity : class
24+
{
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="EntityStateMachineBase{TState, TTrigger, TEntity}"/> class.
27+
/// </summary>
28+
/// <param name="entity">The entity whose state is described by the state machine</param>
29+
/// <param name="statePropertySelector">The state property of the entity described by the state machine</param>
30+
/// <param name="stateModifiedDatePropertySelector">The property of entity, which contains the time of the last change of state</param>
31+
/// <param name="timeProvider">An instance of <see cref="ITimeProvider"/></param>
32+
/// <param name="dbContext">An instance of <see cref="DbContext"/></param>
33+
/// <param name="exceptionMessage">Message of exception</param>
34+
/// <exception cref="ArgumentNullException">This exception is thrown if the added properties aren't available</exception>
35+
/// <exception cref="BusinessException">This exception is thrown if a state transition does not permitted</exception>
36+
protected EntityStateMachineBase(
37+
TEntity entity,
38+
Expression<Func<TEntity, TState>> statePropertySelector,
39+
Expression<Func<TEntity, DateTimeOffset>>? stateModifiedDatePropertySelector = null,
40+
ITimeProvider? timeProvider = null,
41+
DbContext? dbContext = null,
42+
string? exceptionMessage = null)
43+
: base(
44+
stateAccessor: () => (TState)statePropertySelector.GetPropertyAccess().GetValue(entity)!,
45+
stateMutator: state => statePropertySelector.GetPropertyAccess().SetValue(entity, state))
46+
{
47+
if (stateModifiedDatePropertySelector != null && timeProvider == null)
48+
throw new ArgumentNullException(nameof(timeProvider), $"If {nameof(stateModifiedDatePropertySelector)} is specified, the {nameof(timeProvider)} parameter cannot be null");
49+
50+
if (stateModifiedDatePropertySelector != null && dbContext == null)
51+
throw new ArgumentNullException(nameof(timeProvider), $"If {nameof(stateModifiedDatePropertySelector)} is specified, the {nameof(dbContext)} parameter cannot be null");
52+
53+
OnTransitionedAsync(async _ =>
54+
{
55+
stateModifiedDatePropertySelector?.GetPropertyAccess().SetValue(entity, timeProvider?.Now);
56+
57+
if (dbContext != null)
58+
await dbContext.SaveChangesAsync();
59+
});
60+
61+
OnUnhandledTrigger((s, t) =>
62+
{
63+
var id = entity.GetType().GetProperty("Id")?.GetValue(entity);
64+
throw new BusinessException(
65+
exceptionMessage ?? $"The desired state transition is not allowed! (entity: {entity.GetType().Name} id: {id} state: {s} trigger: {t})", "Incorrect operation!");
66+
});
67+
}
68+
69+
/// <summary>
70+
/// Gives that an trigger can be fired with specified arguments or not
71+
/// </summary>
72+
/// <param name="trigger">The trigger object</param>
73+
/// <param name="arguments">The specified arguments</param>
74+
/// <returns>The trigger can be fired with specified arguments or not</returns>
75+
public bool CanFire(TTrigger trigger, params object[] arguments)
76+
{
77+
if (arguments == null || arguments.Length == 0)
78+
return base.CanFire(trigger);
79+
80+
return GetPermittedTriggers(arguments).Any(x => Equals(x, trigger));
81+
}
82+
83+
/// <summary>
84+
/// Base abstract class of state machine factories
85+
/// </summary>
86+
/// <typeparam name="TStateMachine">Type of state machine to be created</typeparam>
87+
public abstract class FactoryBase<TStateMachine>
88+
where TStateMachine : EntityStateMachineBase<TState, TTrigger, TEntity>
89+
{
90+
private readonly IServiceProvider _serviceProvider;
91+
92+
/// <summary>
93+
/// Initializes a new instance of the <see cref="FactoryBase{TStateMachine}"/> class
94+
/// </summary>
95+
/// <param name="serviceProvider">Instance of an IServiceProvider</param>
96+
protected FactoryBase(IServiceProvider serviceProvider)
97+
{
98+
_serviceProvider = serviceProvider;
99+
}
100+
101+
/// <summary>
102+
/// Create a state machine with specified type
103+
/// </summary>
104+
/// <param name="entity">The entity whose state is described by the state machine</param>
105+
/// <returns>The created state machine</returns>
106+
public TStateMachine CreateStateMachine(TEntity entity)
107+
{
108+
return ActivatorUtilities.CreateInstance<TStateMachine>(_serviceProvider, entity);
109+
}
110+
}
111+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Stateless;
2+
using Stateless.Reflection;
3+
4+
namespace AutSoft.Common.StateMachines;
5+
6+
/// <summary>
7+
/// Extension methods for state configuration with Stateless package
8+
/// </summary>
9+
public static class StateConfigurationExtensions
10+
{
11+
/// <summary>
12+
/// Permit manually changes in state machine
13+
/// </summary>
14+
/// <typeparam name="TState">Type of state machine's states</typeparam>
15+
/// <typeparam name="TTrigger">Type of state machine's triggers</typeparam>
16+
/// <param name="config">Configuration instance of a state of the state machine</param>
17+
/// <param name="trigger">The trigger, which triggers the state transition</param>
18+
/// <param name="permittedDestinationStates">The permitted target states</param>
19+
/// <returns>The configured configuration instance</returns>
20+
public static StateMachine<TState, TTrigger>.StateConfiguration PermitManualChange<TState, TTrigger>(
21+
this StateMachine<TState, TTrigger>.StateConfiguration config,
22+
TTrigger trigger,
23+
TState[] permittedDestinationStates)
24+
{
25+
var dynamicStateInfos = new DynamicStateInfos();
26+
dynamicStateInfos.AddRange(permittedDestinationStates.Select(s => new DynamicStateInfo(s.ToString(), string.Empty)));
27+
28+
return config.PermitDynamicIf(
29+
new StateMachine<TState, TTrigger>.TriggerWithParameters<TState>(trigger),
30+
newState => newState,
31+
newState => permittedDestinationStates.Contains(newState),
32+
possibleDestinationStates: dynamicStateInfos);
33+
}
34+
}

src/AutSoft.DbScaffolding.Identity/AutSoft.DbScaffolding.Identity.csproj

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,9 @@
55
</PropertyGroup>
66

77
<ItemGroup>
8-
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.8" />
9-
<PackageReference Include="EntityFrameworkCore.Scaffolding.Handlebars" Version="6.0.3">
10-
<PrivateAssets>all</PrivateAssets>
11-
</PackageReference>
12-
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
13-
<PrivateAssets>all</PrivateAssets>
14-
</PackageReference>
15-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.8" />
8+
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
9+
<PackageReference Include="EntityFrameworkCore.Scaffolding.Handlebars" PrivateAssets="All" />
10+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="All" />
1611
</ItemGroup>
1712

1813
<ItemGroup>

src/AutSoft.DbScaffolding/AutSoft.DbScaffolding.csproj

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,9 @@
55
</PropertyGroup>
66

77
<ItemGroup>
8-
<PackageReference Include="EntityFrameworkCore.Scaffolding.Handlebars" Version="6.0.3">
9-
<PrivateAssets>all</PrivateAssets>
10-
</PackageReference>
11-
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
12-
<PrivateAssets>all</PrivateAssets>
13-
</PackageReference>
14-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.8" />
15-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" Version="6.0.8" />
8+
<PackageReference Include="EntityFrameworkCore.Scaffolding.Handlebars" PrivateAssets="All" />
9+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="All" />
10+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" />
1611
</ItemGroup>
1712

1813
<ItemGroup>

src/AutSoft.Linq/AutSoft.Linq.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="AutoMapper" Version="11.0.1" />
12-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.8" />
11+
<PackageReference Include="AutoMapper" />
1312
</ItemGroup>
1413

1514
<ItemGroup>

src/Directory.Packages.props

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,18 @@
11
<Project>
2+
3+
<PropertyGroup>
4+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
5+
</PropertyGroup>
6+
27
<ItemGroup>
3-
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.1.1" PrivateAssets="All" />
4-
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0">
5-
<PrivateAssets>all</PrivateAssets>
6-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
7-
</PackageReference>
8-
<PackageReference Include="Roslynator.Analyzers" Version="4.1.1">
9-
<PrivateAssets>all</PrivateAssets>
10-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
11-
</PackageReference>
12-
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.43.0.51858">
13-
<PrivateAssets>all</PrivateAssets>
14-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
15-
</PackageReference>
16-
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
17-
<PrivateAssets>all</PrivateAssets>
18-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19-
</PackageReference>
8+
<PackageVersion Include="AutoMapper" Version="11.0.1" />
9+
<PackageVersion Include="EntityFrameworkCore.Scaffolding.Handlebars" Version="6.0.3" />
10+
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.8" />
11+
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="6.0.8" />
12+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8" />
13+
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" Version="6.0.8" />
14+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
15+
<PackageVersion Include="Stateless" Version="5.12.0" />
2016
</ItemGroup>
21-
<ItemGroup>
22-
<PackageVersion Include="Microsoft.NET.Test.Sdk">
23-
<Version>17.3.0</Version>
24-
</PackageVersion>
25-
<PackageVersion Include="xunit">
26-
<Version>2.4.2</Version>
27-
</PackageVersion>
28-
<PackageVersion Include="xunit.runner.visualstudio">
29-
<Version>2.4.5</Version>
30-
</PackageVersion>
31-
</ItemGroup>
32-
</Project>
17+
18+
</Project>

0 commit comments

Comments
 (0)