Loading...
1. Introduction

The BPM.NET for Visual Studio 2010 is a Visual Studio extension to Windows Workflow 4.0+. The purpose of the tool is to deliver the capability of Business Process Management on the .net platform by leveraging the power of windows workflow. This product can be used to work with BPM.NET Designer tool to cover the life cycle of business process authoring. The BPM.NET for Visual Studio 2010 is fully built on top of Windows Workflow 4 and compatible with all the features provided by Standard Windows Workflow and Visual Studio such as Source Control, Debugging, Tracking Participants, WCF and Workflow Sql Instance Store, etc.

1.1. Software Requirements

Visual Studio 2010

2. BPM.NET for Visual Studio 2010 Installation


3. Launch BPM.NET Designer

Start Visual Studio 2010 (Run as Administrator if on win7/win8). Check the installation by clicking menu Tools ‐> Extension Manager:

Click Help ‐> About Visual Studio from the menu:

4. Licensing


The Activiate BPM.NET product link in the Help menu can be used to check the installed license or enter the license key. If a proper license has been installed, it will display a message saying ‘The product has been activated already’ by clicking the menu link. The default license included in the installer is a trial version, however, it has the same functionalities as the full version. If you require a commercial license for source codes, product samples, technical support or customization etc, please see Contact page.

4.1. Workflow Toolbox
4.1.1. Business Process


The Business Process section in toolbox contains all the tools for business process authoring. The features and usage of these tools are described in other parts of the document.

5. Hello World Sample
5.1. Create a Workflow Project

Create a normal Workflow Console Application with name BusinessProcessApp1.

5.2. Create a Process

Drag a Process from Business Process section of the toolbox to the content of Workflow1.

A Process can be created as a root activity or it can be added to other Microsoft standard activities such as the Sequence:

Variables can be created and will be visible to all the child activities of the process.

5.3. Add a Swimlane

Create a normal Workflow Console Application with name BusinessProcessApp1.

A Swimlane can only be added to a Process activity. Variables can be created and will be visible to all the child activities of the lane.

5.4. Add a Start, State and End activity


All activities except for the Process and the Swimlane in the Business Process section can be added to a Swimlane.

5.5. Create Transitions

Create transitions as shown below:

Although the designer supports the ability to set a condition on each transition, all the activities except for Split can have one and only one unconditional transition, otherwise an error message will be reported. Split can have multiple unconditional or conditional transitions but there has to be at least one unconditional transition from it.

5.6. Execute the Process

Double click State and drag drop a WriteLine from the Primitives of the standard toolbox and enter ‘Hello World, BPM.NET’.

Press F5 to execute the Process:

6. Business Process Primitives
6.1. Start

The Start is the first activity that gets executed by the Process it is placed in. Double click a Start activity within the Process and the designer displays the content of the Start activity. There has to be one and only one Start activity for a Process.

6.2. End

The End activity has to be used to finish the execution of a Process otherwise the designer will show an error.

6.3. Process

The Process is the root container of the Swimlane.

6.4. Swimlane

The Swimlane is the container of all other activities in Business Process and it has to be hosted within a Process. Because of the implementation of BPM.NET, Entry and Exit of the Swimlane gets executed every time a child element in the Swimlane is fired.

6.5. State

The State is the major activity to carry out the business actions of a process. Other standard .net workflow activities such as Messaging activities or Flowcharts can be placed in the Body part of the State.

6.6. Task

Task activity is used when there is a human interaction in the process. It introduces the concept of users and groups into the process designer. Although human interactions can be in the body part of State activity with Receive activity etc, the Task activity is a more recommended way because it provides lots of features out of the box with best performance. The In argument defines the data of the human task awaiting human interactions. It is an extension (BusinessProcessExtension) of PersistenceParticipant of WF so it follows the best practise. The data is persisted into the configged InstanceStore in the runtime in XML format. The Out argument defines the data that is submitted back to the process. The object type of the In and Out argument of the Task activity has to be serializable.
To submit the task back to the process, BPM.NET provides a customized WorkflowHostingEndpoint called BusinessProcessControlEndpoint. The endpoint also provides other actions such as assign a task to a user, assign a task to groups and change task due date etc.
The BPM.NET comes with a memory instance stores that helps the developers to quickly build and test the bpm system. When running the BPM as WCF WF service, BPM.NET also provides the behavior of the memory instance store. In addtion to that, BPM.NET encapsulates the BusinessProcessExtension into a SqlBusinessProcessInstanceStoreBehavior. This helps the developers building the BPM system without spending any time in the integration. BPM.NET exposes the Task data with predefined repository classes implementing an interface of ITaskRepository. This abstracts the different instance stores provided by BPM.NET. Developers can simply build the process without worrying about reading the database or writing any serialization code

6.7. Decision

The Decision provides a decision gateway to a Process so that conditions can be evaluated on each transition path from the Decision. At the end of the Decision execution, only one transition path will be taken by the process for further execution. As part of the design, there has to be one and only one unconditional (default) transition path from a Decision otherwise an error is reported.

6.8. Split and Join

The Split and Join have to be used together to achieve parallel tasks within a process. The split routes have to be joined at a certain point in the process by using Join activity so that the multi parallel routes merge to back into one. Each route in the split has to be complete before the process can move any further at Join activity. Conditions can also be applied to the transition paths from a Split activity. There has to be at least one unconditional (default) transition path from a Split.

6.9. Trigger

When an external event gets fired on a State, the Trigger activity can be used to respond to the external event. The Delay, Receive Message and, Receive and Reply Message are the typical activities that can be used as a Trigger. There’s no limit to the type of the activity in a Trigger, but an event type activity is more likely to be used in a real world scenario. When the Trigger gets fired, the body of its State is cancelled immediately so the process takes the route from the Trigger.

An example the Transition of a Trigger that can go to its parent State:

Double click the Trigger icon in the process designer to put the event activity on the Trigger. The Trigger has three different icons: (null), Message or Timer. This can be set by the TriggerType in the Property panel. Setting the type to (null) which is the default type will let the engine bypass the validation of the trigger so there will be no validation errors. However, setting the type to (null) also means the trigger is not going to be executed in the runtime. This is more for the business user quickly modeling process.

When setting the type to Message or Time, errors will be displayed in the process if there’s no event in any Triggers.

6.10. Tracking Point

The Tracking Point is very useful to the business to capture the data in the business process and the captured data is used to build process oriented report using WF tracking feature. To set up the tracking points just simply click the Content button in the Tracking Point activity.

The dialog of the Tracking Point content has access to all the variables in the scope of the workflow. The Name column is the Key to the data when the tracking data get emitted at runtime.

6.11. Tracking Point <T>

The generic type of the Tracking Point has the same function of the normal Tracking Point which is to capture the data in the business process for reporting purpose. The generic type also give the business a clear way of defining the data to be captured. The genertic type simplifies the Tracking Point definition and also force the process to use the same data structure accross multiple Tracking Points if the same type is selected. This is beneficial when building standard statistic report of the process or even creating ad hoc report in real time.
select the type to be used for the generic Tracking Point


Unlike the normal Tracking Point, the Name and Type of the generic Tracking Point is not editable because they are auto populated with the Type selected for the generic Tracking Point. The Value column has access to all the variables in the scope of the workflow.

6.12. Transition

Double click to transition to edit the Condition or change the condition of the selected Transition in the Property panel.

The start point of a transition will turn to a diamond shape when setting any conditions on a transition. (It is a circle shape by default)

6.13. Colors for Swimlanes and States

When selecting a swimlane and state, a Color option is avaiable in the property panel. The predefined colors in the property panel can be used for better process modeling.

7. API Usage Sample

The following code snippets demonstrate how to author a business process workflow using C# code.

7.1. Simple Process
Start start = new Start();
End end = new End();
start.Transitions.Add(new Transition
{
    Target = end,
});
Swimlane swimlane = new Swimlane
{
    Activities = { start, end },
};
Process process = new Process
{
    Swimlanes = { swimlane }
};
WorkflowInvoker.Invoke(process);
7.2. Using Swimlane Variables
Variable<string> var = new Variable<string>("str", "str");
Start start = new Start();
End end = new End
{
    Exit = new System.Activities.Statements.WriteLine
    {
        Text = var
    }
};
State state1 = new State
{
    Body = new Assign<string>
    {
        To = var,
        Value = "Hello World."
    },
};
start.Transitions.Add(new Transition
{
    Target = state1
});
state1.Transitions.Add(new Transition
{
    Target = end
});
Swimlane lane1 = new Swimlane
{
    Variables = { var },
    Activities = { start, state1, end },
};
Process process = new Process
{
    Swimlanes = { lane1 },
};
WorkflowInvoker.Invoke(process);
7.3. Using Process Variables
Variable<string> var = new Variable<string>("str", "str");
Start start = new Start();
End end = new End
{
    Exit = new System.Activities.Statements.WriteLine
    {
        Text = var
    }
};
State state1 = new State
{
    Body = new Assign<string>
    {
        To = var,
        Value = "Hello World."
    },
};
start.Transitions.Add(new Transition
{
    Target = state1
});
state1.Transitions.Add(new Transition
{
    Target = end
});
Swimlane lane1 = new Swimlane
{
    Activities = { start, state1, end },
};
Process process = new Process
{
    Variables = { var },
    Swimlanes = { lane1 },
};
WorkflowInvoker.Invoke(process);
7.4. Trigger Sample
Start start = new Start();
End end = new End();
State state1 = new State();
start.Transitions.Add(new Transition
{
    Target = state1
});
state1.Triggers.Add(new Trigger
{
    TriggerType = TriggerType.Timer,
    Event = new System.Activities.Statements.Delay
    {
        Duration = new InArgument<TimeSpan>(ProcessHelper.GetTime(time))
    },
    Transition = new Transition
    {
        Target = end
    }
});
Swimlane swimlane = new Swimlane
{
    Activities = { start, state1, end },
};
Process process = new Process
{
    Swimlanes = { swimlane }
};
WorkflowInvoker.Invoke(process);
7.5. TrackingPoint Sample
Start start = new Start();
TrackingPoint tp = new TrackingPoint { DisplayName = "tp1" };
tp.TrackingData.Add("testObj", new InArgument<string>("123"));
End end = new End();
start.Transitions.Add(new Transition
{
    Target = tp
});
tp.Transitions.Add(new Transition
{
    Target = end
});
Swimlane swimlane = new Swimlane
{
    Activities = { start, tp, end },
};
Process process = new Process
{
    Swimlanes = { swimlane }
};
ConsoleTrackingParticipant customTrackingParticipant = new ConsoleTrackingParticipant()
{
    TrackingProfile = new TrackingProfile()
    {
        Name = "ConsoleTrackingParticipant",
        Queries =
        {
            new BusinessProcessQuery()
            {
                ActivityName = "*"
            }
        }
    }
};
...
public class ConsoleTrackingParticipant : TrackingParticipant
{
    protected override void Track(TrackingRecord record, TimeSpan timeout)
    {
        BusinessProcessRecord customTrackingRecord = record as BusinessProcessRecord;
        if (customTrackingRecord != null)
        {
            foreach (var data in customTrackingRecord.Data)
            {
                Console.WriteLine(string.Format("{0}:{1}", data.Key, data.Value));
            }
        }
    }
}
7.6. TrackingPoint<T> Sample
public class TrackingData
{
    public string Data { get; set; }
}
...
Start start = new Start();
TrackingPoint<TrackingData> tp = new TrackingPoint<TrackingData> { DisplayName = "tp1" };
tp.TrackingData.Single(p => p.Name == "Data").Expression = new VisualBasicValue<string>("\"TrackingData\"");
End end = new End();
start.Transitions.Add(new Transition
{
    Target = tp
});
tp.Transitions.Add(new Transition
{
    Target = end
});
Swimlane swimlane = new Swimlane
{
    Activities = { start, tp, end },
};
Process process = new Process
{
    Swimlanes = { swimlane }
};
ConsoleTrackingParticipant customTrackingParticipant = new ConsoleTrackingParticipant()
{
    TrackingProfile = new TrackingProfile()
    {
        Name = "ConsoleTrackingParticipant",
        Queries =
        {
            new BusinessProcessQuery()
            {
                ActivityName = "*"
            }
        }
    }
};
...
public class ConsoleTrackingParticipant : TrackingParticipant
{
    protected override void Track(TrackingRecord record, TimeSpan timeout)
    {
        BusinessProcessRecord<TrackingData> customTrackingRecord = record as BusinessProcessRecord<TrackingData>;
        if (customTrackingRecord != null)
        {
            Console.WriteLine(customTrackingRecord.TrackingPointDisplayName);
            Console.WriteLine(customTrackingRecord.TrackingPointProcessName);
            Console.WriteLine(customTrackingRecord.TrackingPointTimestamp);
            Console.WriteLine(customTrackingRecord.TrackingPointWorkflowInstanceId);
            Console.WriteLine(customTrackingRecord.TrackingPointModel.Data);
        }
    }
}
7.7. Task<T1, T2> Samples
Variable<TaskDataOutput> var = new Variable<TaskDataOutput>();
string INPUT = "Test Data Input";
Start start = new Start();
End end = new End
{
    Exit = new System.Activities.Statements.WriteLine
    {
        Text = new InArgument(Finished)
    }
};
Task<TaskDataInput, TaskDataOutput> task1 = new Task<TaskDataInput, TaskDataOutput>
{
    TaskDataIn = new InArgument<TaskDataInput>((env) => new TaskDataInput { Data = INPUT }),
    TaskDataOut = new OutArgument<TaskDataOutput>(var),
    Exit = new System.Activities.Statements.WriteLine
    {
        Text = new InArgument<string>((env) => var.Get(env).Data)
    }
};
            
start.Transitions.Add(new Transition
{
    Target = task1
});
task1.Transitions.Add(new Transition
{
    Target = end
});
Swimlane swimlane = new Swimlane
{
    Activities = { start, task1, end },
};
Process process = new Process
{
    Swimlanes = { swimlane },
    Variables = { var }
};
string message = "there we go";
List<TaskDataOutput> outputs = new List<TaskDataOutput>
{
    new TaskDataOutput { Data = message },
};
List<Tuple<HumanTask, TaskDataInput>> inputs = new List<Tuple<HumanTask, TaskDataInput>>();
ProcessHelper.RunInApplication<TaskDataInput, TaskDataOutput>(process, inputs, outputs);
...
public class TaskDataInput
{
    public string Data { get; set; }
}
public class TaskDataOutput
{
    public string Data { get; set; }
}
...
static string[] RunInApplication<T1, T2>(Process process, IList<Tuple<HumanTask, T1>> inputTasks, IList<T2> outputObjects)
{
    MemoryWorkflowInstanceStore store = new MemoryWorkflowInstanceStore();
    using (System.IO.StringWriter writer = new System.IO.StringWriter())
    {
        Console.SetOut(writer);
        AutoResetEvent applicationUnloaded = new AutoResetEvent(false);
        WorkflowApplication application = new WorkflowApplication(process);
        Guid id = application.Id;
        application.InstanceStore = store;
        application.Extensions.Add(new BusinessProcessExtension());
        application.PersistableIdle = (e) => { return PersistableIdleAction.Unload; };
        application.Unloaded = e => applicationUnloaded.Set();
        application.Run();
        applicationUnloaded.WaitOne();
        ITaskRepository repository = TaskRepositoryFactory.Create(TaskRepositoryType.Memory);
        for (int i = 0; i < outputObjects.Count; i++)
        {
            IList<HumanTask> humanTasks = repository.GetAllTasksFor(id);
            inputTasks.Add(Tuple.Create(
                humanTasks[0],
                PersistanceHelper.Deserialize<T1>(humanTasks[0].Content.Value)));
            application = new WorkflowApplication(process);
            application.InstanceStore = store;
            application.Extensions.Add(new BusinessProcessExtension());
            application.PersistableIdle = (e) => { return PersistableIdleAction.Unload; };
            application.Unloaded = e => applicationUnloaded.Set();
            application.Load(id);
            application.ResumeBookmark(humanTasks[0].Id.ToString(),
                new TaskOutcome
                {
                    Action = TaskAction.Submit,
                    Content = PersistanceHelper.Serialize<T2>(outputObjects[i])
                });
            applicationUnloaded.Reset();
            applicationUnloaded.WaitOne();
        }
        return writer.ToString().Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
    }
}
                    
7.8. Task Repository Samples
public class Service1 : IService1
{
    ITaskRepository repository = TaskRepositoryFactory.Create(TaskRepositoryType.Memory);
    public IList<InstanceHumanTasks>GetAllTasks(DateTime? dueDate)
    {
        return repository.GetAllTasks(dueDate);
    }
    public IList<humantask>GetAllTasksFor(Guid instanceId)
    {
        return repository.GetAllTasksFor(instanceId);
    }
    public HumanTask GetTaskFor(Guid instanceId, Guid taskId)
    {
        return repository.GetTaskFor(instanceId, taskId);
    }
    public IList<instancehumantasks>GetGroupsTasks(IList<string>assignedGroups, DateTime? dueDate)
    {
        return repository.GetGroupsTasks(assignedGroups, dueDate);
    }
    public IList<instancehumantasks>GetUserTasks(string assignedUser, DateTime? dueDate)
    {
        return repository.GetUserTasks(assignedUser, dueDate);
    }
    public int GetAllTasksCount(DateTime? dueDate)
    {
        return repository.GetAllTasksCount(dueDate);
    }
    public int GetGroupsTasksCount(IList<string>assignedGroups, DateTime? dueDate)
    {
        return repository.GetGroupsTasksCount(assignedGroups, dueDate);
    }
    public int GetUserTasksCount(string assignedUser, DateTime? dueDate)
    {
        return repository.GetUserTasksCount(assignedUser, dueDate);
    }
}
                
7.9. MemoryWorkflowInstanceStoreBehavior
<extensions>
    <behaviorExtensions>
        <add name="memoryWorkflowInstanceStoreBehavior" type="BPM.NET.Activities.ServiceModel.MemoryWorkflowInstanceStoreElement, BPM.NET.Activities.ServiceModel" />
    </behaviorExtensions>
</extensions>
<serviceBehaviors>
    <behavior>
        <memoryWorkflowInstanceStoreBehavior />
    </behavior>
</serviceBehaviors>
7.10. SqlBusinessProcessInstanceStoreBehavior
<extensions>
    <behaviorExtensions>
        <add name="sqlBusinessProcessInstanceStoreBehavior" type="BPM.NET.Activities.ServiceModel.SqlBusinessProcessInstanceStoreElement, BPM.NET.Activities.ServiceModel" />
    </behaviorExtensions>
</extensions>
<serviceBehaviors>
    <behavior>
        <sqlBusinessProcessInstanceStoreBehavior connectionString="Data Source=.;Initial Catalog=InstanceStore;integrated security=true" />
    </behavior>
</serviceBehaviors>
7.11. BusinessProcessControlEndpoint
<extensions>
    <endpointExtensions>
        <add name="businessProcessControlEndpoint" type="BPM.NET.Activities.ServiceModel.BusinessProcessControlEndpointCollection, BPM.NET.Activities.ServiceModel" />
    </endpointExtensions>
</extensions>
<services>
    <service name="Workflow1">
        <endpoint kind="businessProcessControlEndpoint" binding="basicHttpBinding" address="bpmCtrl"/>
    </service>
</services>