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 theCreateInstance
is called is conveyed to the method. Only simple types such as String, Bool, Guid, Int, etc. andIEnumerable<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.