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 IEnumerableThus, 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:
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());
}
}
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:
UpdateSchemeIfObsolete
method:var schemeCreationParameters = new Disctionary<string,object>();
WorkflowInit.Runtime.UpdateSchemeIfObsolete(processId,schemeCreationParameters);
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.