Skip to main content

Scheme generation

We have already mentioned several times that in case Workflow Engine does not find an active scheme in the WorkflowProcessInstance table (object), scheme generation is started. Let's discuss what a scheme generator is and what opportunities for enhancing Workflow Engine's operation it provides. In fact, the IWorkflowGenerator interface is defined by a single method.

public interface IWorkflowGenerator<out TSchemeMedium> where TSchemeMedium : class
{
TSchemeMedium Generate(string schemeCode, Guid schemeId, IDictionary<string, object> parameters);
}

The TSchemeMedium type specifies the type of scheme storing. By default, this is the XElement type; in other words, the scheme is stored in XML. The Generate method accepts three parameters:

  • schemeCode - scheme name (code);
  • schemeId - scheme id, with which it will be stored in the WorkflowProcessScheme table (object);
  • parameters - a dictionary of parameters that may be used to generate schemes. The parameter dictionary with which the CreateInstance is called is conveyed to the method. Only simple types such as String, Bool, Guid, Int, etc. and IEnumerable<T>, where T is a simple type, may so far be used in the dictionary values.

Thus, parameters which we convey to the SchemeCreationParameters dictionary will be conveyed to the scheme generator and used for scheme generation.

var createInstanceParams = new CreateInstanceParams("SchemeCode", processId)
{
SchemeCreationParameters = new Dictionary<string, object>()
{
{"GeneratorParameter1", parameterValue}
}
};

Each Persistence provider implements the IWorkflowGenerator interface. The following code explains how it functions out-of-the-box:

public XElement Generate(string schemeCode, Guid schemeId, IDictionary<string, object> parameters)
{
if (parameters.Count > 0)
throw new InvalidOperationException("Parameters not supported");

return GetScheme(schemeCode);
}

public XElement GetScheme(string code)
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
WorkflowScheme scheme = WorkflowScheme.SelectByKey(connection, code);
if (scheme == null || string.IsNullOrEmpty(scheme.Scheme))
throw SchemeNotFoundException.Create(code, SchemeLocation.WorkflowProcessScheme);

return XElement.Parse(scheme.Scheme);
}
}

Thus, by default, the generator does not use parameters, and simply returns the scheme from the WorkflowScheme table (object).

The following use cases may be implemented with the help of a scheme generator:

  • Adjusting of the basic scheme, depending on generation parameters. It comprises addition of new Activities, Transitions, Actions, etc.
  • Generation of a scheme according to the business data. For example, you have an object where all the successive process stages are specified, and a process scheme is generated on their basis.
  • Template-based generation.
  • Transformation of a scheme created in custom designer into the Workflow Engine scheme.

The following code explains how to add standardized Actions to each Activity of an existing scheme. This example is taken from a real project, though it was tailored a bit.

public class Generator : IWorkflowGenerator<XElement>
{
public XElement Generate(string schemeCode, Guid schemeId, IDictionary<string, object> parameters)
{
var processDefinition = WorkflowInit.Runtime.Builder.GetProcessSchemeForDesigner(schemeCode);

if (processDefinition == null)
{
throw SchemeNotFoundException.Create(schemeCode, SchemeLocation.WorkflowProcessScheme);
}

foreach (var activity in processDefinition.Activities.Where(a => !string.IsNullOrWhiteSpace(a.State)))
{
if (activity.Implementation.All(c => c.ActionName != "SetDocumentState"))
{
activity.AddAction(
ActionDefinitionReference.Create("SetDocumentState", "0", string.Empty));
}

if (activity.IsForSetState)
{
if (activity.Implementation.All(c => c.ActionName != "UpdateHistory"))
{
activity.AddAction(
ActionDefinitionReference.Create("UpdateHistory", "99", string.Empty));
}
if (activity.PreExecutionImplementation.All(c => c.ActionName != "WriteHistory"))
{
activity.AddPreExecutionAction(
ActionDefinitionReference.Create("WriteHistory", "99", string.Empty));
}
}
}

return XElement.Parse(processDefinition.Serialize());
}
}

Generation parameters and scheme updates

In case generation parameters are conveyed to the generator during scheme generation, a scheme will be identified by the scheme name (code) + parameters combination. Thus, two schemes with the same name but different parameters are considered different schemes. In case a process scheme is generated with the use of parameters, generation parameters should be conveyed again during process scheme update. They may be conveyed in two ways:

  • Explicitly when calling the UpdateSchemeIfObsolete method:

    var schemeCreationParameters = new Dictionary<string, object>();
    WorkflowInit.Runtime.UpdateSchemeIfObsolete(processId, schemeCreationParameters);
  • By returning them in the OnNeedDeterminingParameters event handler:

    runtime.OnNeedDeterminingParameters += (sender, args) =>
    {
    Guid processId = args.ProcessId;
    args.DeterminingParameters = new Dictionary<string, object>() {...};
    };

In case scheme generation parameters which the scheme was generated with depend on the process (for example, they represent fields of an object that the process uses), IsObsolete should not necessarily be set. Other processes will probably use this scheme, and it won't be obsolete for them. In this case, it is more reasonable to set the IsDeterminingParametersChanged attribute to the entry in the WorkflowProcessInstance table. In case a scheme with the conveyed parameters already exists, the update or rebind of the scheme for a certain process takes place.