Beispiel #1
0
        /// <summary>
        /// Initializes a <see cref="WorkflowInstance" />. See Remarks for full details of this process.
        /// </summary>
        /// <param name="instance">The workflow instance.</param>
        /// <param name="workflow">The <see cref="Workflow" /> that this is an instance of.</param>
        /// <param name="context">The dictionary of context values that was supplied when this instance was created.</param>
        /// <returns>A <see cref="Task" /> that completes when the instance is initialised.</returns>
        /// <remarks>
        /// <para>
        /// Initialization includes the following steps:
        /// - Setting the instance's <see cref="WorkflowInstance.Context" /> property to the supplied dictionary.
        /// - Setting the state of this instance to the workflow's initial state.
        /// - Validating the entry conditions of the initial state.
        /// - Executing the entry actions of the initial state.
        /// </para>
        /// <para>
        /// It is possible for an action executed as part of initialization to cause an
        /// <see cref="IWorkflowTrigger" /> to be created. If an action does do this, it should
        /// add it to the current <see cref="IWorkflowMessageQueue" />. As a result of this it
        /// is vitally important that the code creating and Initializing a workflow instance
        /// takes a shared lease at the earliest possible moment.
        /// </para>
        /// </remarks>
        private async Task InitializeInstanceAsync(WorkflowInstance instance, Workflow workflow, IDictionary <string, string> context = null)
        {
            instance.WorkflowId = workflow.Id;
            instance.Context    = context;

            WorkflowState workflowState = workflow.GetInitialState();

            bool entryAllowed = await this.CheckConditionsAsync(workflowState.EntryConditions, instance, null).ConfigureAwait(false);

            if (!entryAllowed)
            {
                this.logger.LogDebug(
                    "Initialization not permitted to state {WorkflowStateId} [{WorkflowStateDisplayName}] in instance {InstanceId}",
                    workflowState.Id,
                    workflowState.DisplayName,
                    instance.Id);
                instance.Status = WorkflowStatus.Faulted;

                return;
            }

            instance.SetState(workflowState);
            instance.Status = WorkflowStatus.Waiting;

            this.logger.LogDebug(
                "Executing entry actions on state {WorkflowStateId} [{WorkflowStateDisplayName}]",
                workflowState.Id,
                workflowState.DisplayName);

            await this.ExecuteAsync(workflowState.EntryActions, instance, null).ConfigureAwait(false);
        }
Beispiel #2
0
        /// <summary>
        /// Determines whether an <see cref="WorkflowState" /> is able to accept a trigger,
        /// and processes it if so.
        /// </summary>
        /// <param name="workflow">The workflow that is in operation.</param>
        /// <param name="state">The state that will process the trigger.</param>
        /// <param name="instance">The <see cref="WorkflowInstance" /> that is in this state.</param>
        /// <param name="trigger">The <see cref="IWorkflowTrigger" /> to process.</param>
        /// <returns>
        /// A <see cref="Task" /> that will complete when the trigger has been
        /// processed, or it is determined that the trigger can't be processed
        /// for the given <see cref="WorkflowInstance" /> and <see cref="WorkflowState" />.
        /// </returns>
        /// <remarks>
        /// <para>
        /// This method consists of three parts:
        /// <list type="bullet">
        ///     <item>
        ///         <description>
        ///             Determining if the supplied trigger can be accepted by the
        ///             <see cref="WorkflowInstance" /> in its current state.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             Executing the actions required to move to the new state.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             Updating the <see cref="WorkflowInstance" /> with the new state and
        ///             associated interests.
        ///         </description>
        ///     </item>
        /// </list>
        /// </para>
        /// <para>
        /// To determine the transition to use, the following steps are taken:
        /// <list type="bullet">
        ///     <item>
        ///         <description>
        ///             Evaluate the exit conditions of the current state (from
        ///             <see cref="WorkflowState.ExitConditions" />. If any conditions
        ///             evaluate to false, processing ends.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             Iterate the <see cref="WorkflowState.Transitions" /> collection
        ///             and select the first <see cref="WorkflowTransition" /> whose
        ///             <see cref="WorkflowTransition.Conditions" /> all evaluate to true.
        ///             If no transitions match, then processing ends.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             Retrieve the target state from the transition, and evaluate its
        ///             entry conditions (from <see cref="WorkflowState.EntryConditions" />.
        ///             If any conditions evaluate to false, processing ends.
        ///         </description>
        ///     </item>
        /// </list>
        /// </para>
        /// <para>
        /// Once it has been determined that the trigger can be processed, actions
        /// from the current state, transition and target state are executed in order:
        /// <list type="bullet">
        ///     <item>
        ///         <description>
        ///             The current state's <see cref="WorkflowState.ExitActions" />.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             The transition's <see cref="WorkflowTransition.Actions" />
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             The target state's <see cref="WorkflowState.ExitActions" />.
        ///         </description>
        ///     </item>
        /// </list>
        /// </para>
        /// <para>
        /// Once all actions have been processed, the <see cref="WorkflowInstance" />s status
        /// is set back to <see cref="WorkflowStatus.Waiting" />, if the new current state
        /// contains any transitions. If the new current state doesn't contain any transitions,
        /// there is no way of leaving this state and the workflow status is set to
        /// <see cref="WorkflowStatus.Complete" />. Additionally, the <see cref="WorkflowInstance.IsDirty" />
        /// property is set to true to ensure that the instance is saved by the <see cref="IWorkflowEngine" />.
        /// </para>
        /// </remarks>
        private async Task <WorkflowTransition> AcceptTriggerAsync(
            Workflow workflow,
            WorkflowState state,
            WorkflowInstance instance,
            IWorkflowTrigger trigger)
        {
            bool exitAllowed = await this.CheckConditionsAsync(state.ExitConditions, instance, trigger).ConfigureAwait(false);

            if (!exitAllowed)
            {
                this.logger.LogDebug(
                    "Exit not permitted from state {StateId} [{StateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                    state.Id,
                    state.DisplayName,
                    instance.Id,
                    trigger.Id);
                instance.Status = WorkflowStatus.Waiting;
                return(null);
            }

            WorkflowTransition transition = await this.FindTransitionAsync(state.Transitions, instance, trigger).ConfigureAwait(false);

            if (transition == null)
            {
                this.logger.LogDebug(
                    "No transition found from state {StateId} [{StateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                    state.Id,
                    state.DisplayName,
                    instance.Id,
                    trigger.Id);
                instance.Status = WorkflowStatus.Waiting;
                return(transition);
            }

            WorkflowState targetState = workflow.GetState(transition.TargetStateId);

            this.logger.LogDebug(
                "Transition {TransitionId}  [{TransitionDisplayName}] found from state {StateId} [{StateDisplayName}] to state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                transition.Id,
                transition.DisplayName,
                state.Id,
                state.DisplayName,
                targetState.Id,
                targetState.DisplayName,
                instance.Id,
                trigger.Id);

            bool entryAllowed = await this.CheckConditionsAsync(targetState.EntryConditions, instance, trigger)
                                .ConfigureAwait(false);

            if (!entryAllowed)
            {
                this.logger.LogDebug(
                    "Entry not permitted into state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                    targetState.Id,
                    targetState.DisplayName,
                    instance.Id,
                    trigger.Id);
                instance.Status = WorkflowStatus.Waiting;
                return(transition);
            }

            this.logger.LogDebug(
                "Executing exit actions on transition {TransitionId} from state {StateId} [{StateDisplayName}] to state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                transition.Id,
                state.Id,
                state.DisplayName,
                targetState.Id,
                targetState.DisplayName,
                instance.Id,
                trigger.Id);

            await this.ExecuteAsync(state.ExitActions, instance, trigger).ConfigureAwait(false);

            this.logger.LogDebug(
                "Executing transition actions on transition {TransitionId} from state {StateId} [{StateDisplayName}] to state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                transition.Id,
                state.Id,
                state.DisplayName,
                targetState.Id,
                targetState.DisplayName,
                instance.Id,
                trigger.Id);
            await this.ExecuteAsync(transition.Actions, instance, trigger).ConfigureAwait(false);

            instance.Status = targetState.Transitions.Count == 0 ? WorkflowStatus.Complete : WorkflowStatus.Waiting;
            instance.SetState(targetState);

            this.logger.LogDebug(
                "Executing entry actions on transition {TransitionId} from state {StateId} [{StateDisplayName}] to state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                transition.Id,
                state.Id,
                state.DisplayName,
                targetState.Id,
                targetState.DisplayName,
                instance.Id,
                trigger.Id);
            await this.ExecuteAsync(targetState.EntryActions, instance, trigger).ConfigureAwait(false);

            // Then update the instance status, set the new state
            instance.IsDirty = true;
            return(transition);
        }