Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

84
Vistas
How to encapsulate .NET Stateless state machine

I have a project where there is a mostly linear workflow. I'm attempting to use the .NET Stateless library to act as workflow engine/state machine. The number of examples out there is limited, but I've put together the following code:

private StateMachine<WorkflowStateType, WorkflowStateTrigger> stateMachine;
private StateMachine<WorkflowStateType, WorkflowStateTrigger>.TriggerWithParameters<Guid, DateTime> registrationTrigger;
private Patient patient;

public Patient RegisterPatient(DateTime dateOfBirth)
{
    configureStateMachine(WorkflowState.Unregistered);
    stateMachine.Fire<DateTime>(registrationTrigger, dateOfBirth);
    logger.Info("State changed to: " + stateMachine.State);
    return patient;
}

private void configureStateMachine(WorkflowState state)
{
    stateMachine = new StateMachine<WorkflowState, WorkflowTrigger>(state);

    registrationTrigger = stateMachine.SetTriggerParameters<DateTime>(WorkflowTrigger.Register);

    stateMachine.Configure(WorkflowState.Unregistered)
        .Permit(WorkflowTrigger.Register, WorkflowStateType.Registered);

    stateMachine.Configure(WorkflowState.Registered)
        .Permit(WorkflowTrigger.ScheduleSampling, WorkflowState.SamplingScheduled)
        .OnEntryFrom(registrationTrigger, (dateOfBirth) => registerPatient(dateOfBirth));
}

private void registerPatient(DateTime dateOfBirth)
{
    //Registration code
}

As you can see, I'm using the Stateless Fire() overload that allows me to pass in a trigger. This is so I can have the state machine process business logic, in this case, code to register a new patient.

This all works, but now I'd like to move all the state machine code into another class to encapsulate it and I'm having trouble doing this. The challenges I've had in doing this are:

  • instantiating a StateMachine object requires you to specify state and State is a readonly property that can only be set at instantiation.
  • my registrationTrigger has to be instantiated during state machine configuration and also has to be available by the calling class.

How can I overcome these items and encapsulate the state machine code?

about 4 years ago · Santiago Trujillo
2 Respuestas
Responde la pregunta

0

There is an article by Scott Hanselman with an example and introduction to a library. Also there few examples available on their GitHub including Bug implementation example mentioned in Scott's article that encapsulates the state machine.

Below is an example of how the state can be extracted from behavior:

public class PatientRegistrationState
{
    private StateMachine<WorkflowState, WorkflowTrigger> stateMachine;
    private StateMachine<WorkflowState, WorkflowStateTrigger>.TriggerWithParameters<DateTime> registrationTrigger;

    public PatientRegistrationState(State initialState = default(State)) {
        stateMachine = new StateMachine<WorkflowState, WorkflowTrigger>(initialState);

        stateMachine.Configure(WorkflowState.Unregistered)
            .Permit(WorkflowTrigger.Register, WorkflowStateType.Registered);

        stateMachine.Configure(WorkflowState.Registered)
            .Permit(WorkflowTrigger.ScheduleSampling, WorkflowState.SamplingScheduled)
            .OnEntryFrom(registrationTrigger, (date) => OnPatientRegistered(date));
    }

    public WorkflowState State => stateMachine.State;
    public Action<DateTime> OnPatientRegistered {get; set;} = (date) => { };

    // For state changes that do not require parameters.
    public void ChangeTo(WorkflowTrigger trigger)
    {
        stateMachine.Fire<DateTime>(trigger);
    }

    // For state changes that require parameters.
    public void ChangeToRegistered(DateTime dateOfBirth)
    {
        stateMachine.Fire<DateTime>(registrationTrigger, dateOfBirth);        
    }

    // Change to other states that require parameters...
}

public class PatientRegistration
{
    private PatientRegistrationState registrationState;
    private Patient patient;

    public PatientRegistration()
    {
        registrationState = PatientRegistrationState(WorkflowState.Unregistered)
        {
            OnPatientRegistered = RegisterPatient;
        }
    }

    public Patient RegisterPatient(DateTime dateOfBirth)
    {
        registrationState.ChangeToRegistered(dateOfBirth);
        logger.Info("State changed to: " + registrationState.State);
        return patient;
    }

    private void RegisterPatient(DateTime dateOfBirth)
    {
        // Registration code
    }
}
about 4 years ago · Santiago Trujillo Denunciar

0

This is how I achieved it in my project.

Separated workflow logic to separate class. I had couple of workflows based on one of the flags present in the request object; below is one of the workflow classes:

public class NationalWorkflow : BaseWorkflow
{
    public NationalWorkflow(SwiftRequest request) : this(request, Objects.RBDb)
    { }

    public NationalWorkflow(SwiftRequest request, RBDbContext dbContext)
    {
        this.request = request;
        this.dbContext = dbContext;
        this.ConfigureWorkflow();
    }

    protected override void ConfigureWorkflow()
    {
        workflow = new StateMachine<SwiftRequestStatus, SwiftRequestTriggers>(
           () => request.SwiftRequestStatus, state => request.SwiftRequestStatus = state);

        workflow.OnTransitioned(Transitioned);

        workflow.Configure(SwiftRequestStatus.New)
            .OnEntry(NotifyRequestCreation)
            .Permit(SwiftRequestTriggers.ProcessRequest, SwiftRequestStatus.InProgress);

        workflow.Configure(SwiftRequestStatus.InProgress)
            .OnEntry(ValidateRequestEligibility)
            .Permit(SwiftRequestTriggers.AutoApprove, SwiftRequestStatus.Approved)
            .Permit(SwiftRequestTriggers.AdvancedServicesReview, SwiftRequestStatus.PendingAdvancedServices);

.....................
}

Which is triggered from the controller/any other layer:

private static void UpdateRequest(SwiftRequestDTO dtoRequest)
    {
            var workflow = WorkflowFactory.Get(request);
            workflow.UpdateRequest();
    }

As mentioned above, I had different workflow rules based on conditions in the request object and hence used a factory pattern WorkflowFactory.Get(request); you may create an instance of your workflow/inject it as desired

And inside the workflow class (BaseWorkflow class in my case), I have exposed the actions:

    public void UpdateRequest()
    {
        using (var trans = this.dbContext.Database.BeginTransaction())
        {
            this.actionComments = "Updating the request";
            this.TryFire(SwiftRequestTriggers.Update);

            SaveChanges();
            trans.Commit();
        }
    }

  protected void TryFire(SwiftRequestTriggers trigger)
    {
        if (!workflow.CanFire(trigger))
        {
            throw new Exception("Cannot fire " + trigger.ToString() + " from state- " + workflow.State);
        }
        workflow.Fire(trigger);
    }
about 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda