Build Performance Evaluation Workflow with WorkflowEngine in .NET
A workflow is a set of activities and events to be performed to complete a task or a process.
There are so many use cases where you might need to build a workflow.
Few examples of these use cases include:
- Tasks Management System
- Document Signing Process
- HR Appraisal System
- HR Leave Management System
- Software Deployments Management
And many more.
A workflow that include several activities and involve different people from different roles to perform actions on each of the workflow activity.
If you want to build such a feature from scratch, you can go ahead and do so, but good luck in completing it without spending tons of hours and hefty efforts into preparing all the needed requirements, splitting them into tasks and writing code to achieve them, let’s not forget about the testing part as well.
It is a very long and complicated process.
In this article we are looking further in one of these use cases.
Employee Performance Evaluation Use Case
The use case we are looking at here is a very common process in the world of careers and companies.
The below diagram represents a simple flow for how a performance evaluation process will happen from start till the end.
Usually employees will have to undergo performance appraisal throughout the year by the company, this is a very important process to be done for the following reasons:
- Review the work for each employee
- Identify potential career growth opportunities
- Set goals and objects that are requested by each employee
- Understand if there are any problems or issues that are hindering the employee
And this list goes on, there are so many benefits from conducting a performance appraisal.
Nevertheless, many companies do this process manually:
The HR will pass an evaluation paper document for each employee and ask them to fill it
Then the employee will have to sit with their direct manager to discuss and agree on the employee’s evaluation values
And then the paper document will be sent back to the HR for final review and confirmation.
See how this approach is tedious?
The possibilities of failures and the hassle to recreate the document in case there was an issue or the HR didn’t approve the evaluation, and many other reasons.
Introducing WorkflowEngine by OptimaJet
Here comes the power of automation and setting up a digitized Workflow solutions.
And what is better than WorkflowEngine to fulfill this requirement and provide a smart and robust solution for this widely common problem.
A trusted and reliable provider that already invested countless of hours into building a ready solution to integrate with.
WorkflowEngine is your ultimate .NET workflow library that would provide you a complete solution to design and build a workflow process and integrate it right into your .NET applications.
WorkflowEngine is a flagship product of OptimaJet.
WorkflowEngine Features
WorkflowEngine comes bundled you with a lot of amazing features, including:
- HTML 5 Visual Designer
- Core Components: Activities, Transitions, Actions, Actors, Commands
- Version Control
- Parallel Workflows
- XML Import/Export
- Localization
- Timers
WorkflowEngine Components
The main components of the workflow engine are:
- WorkflowEngine Designer
- WorkflowEngine Runtime
- DB Provider
- Workflow Tables
- IWorkflowRuleProvider
- IWorkflowActionProvider
This tutorial will show you how to integrate with WorkflowEngine your .NET application. you build a project that integrates with WorkflowEngine and the result will be displayed in Web Pages using ASP.NET Core Razor Pages Technology.
So here is the final diagram for the employee evaluation workflow
So let’s start building our application with workflow integration
In this tutorial we will learn how to integrate with WorkflowEngine which includes:
Creating the needed workflow persistence tables in Microsoft SQL Server
Preparing the .NET 8 solution in Visual Studio 2022
The Solution will include a core project for the runtime workflow engine and the persistence library
We will be utilizing .NET 8 in Visual Studio 2022
DB Persistence Setup
Whether you want to connect WorkflowEngine to an existing database or you want to have it on a separate database, you are covered from both sides.
WorkflowEngine requires some tables to be existing to be able to persist and maintain the workflow scheme and keep full track of all the workflow activities and processes.
Which implies that it doesn’t matter if you want to keep your workflow integration separately or you want to include it within your existing database.
So head over to WorkflowEngine’s download path and download the “WorkflowEngine 12.0.0 .NET Core “ compressed folder.
The latest version of WorkflowEngine was recently added, in 04-December-2023
Once the download is complete, extract it and locate the folder “OptimaJet.Workflow.DbPersistence”
Then explore the SQL folder and open the file CreatePersistenceObjects.sql
Run the SQL script.
You will notice the following tables added:
This is the persistence layer that WorkflowEngine will communicate to manage all the workflow related processes.
Workflow Runtime
Now let’s go create a .NET solution to see how we can integrate with OptimaJet’s WorkflowEngine
I will use VS 2022 latest update, feel free to choose the IDE of your preference.
Open VS 2022 and create a new empty solution
Then add a project, choose class library
This will host the Workflow Runtime, the core module that handles all the heavy lifting of the WorfklowEngine
Let’s make sure to add the related Nuget Packages of the WorkflowEngine in this project:
Now create a new class file with name WorkflowInit
It will contain the Workflow Runtime Initialization code, which is basically the bare minimum setup needed to configure the WorkflowEngine Runtime:
using OptimaJet.Workflow.Core.Builder;
using OptimaJet.Workflow.Core.Runtime;
using OptimaJet.Workflow.DbPersistence;
using System.Xml.Linq;
namespace Workflow.Core
{
public static class WorkflowInit
{
private static readonly Lazy LazyRuntime = new Lazy(InitWorkflowRuntime); public static WorkflowRuntime Runtime
{
get { return LazyRuntime.Value; }
} public static string ConnectionString { get; set; } private static WorkflowRuntime InitWorkflowRuntime()
{
// TODO Uncomment for .NET Framework if you don't set ConnectionString externally.
//ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString; if (string.IsNullOrEmpty(ConnectionString))
{
throw new Exception("Please init ConnectionString before calling the Runtime!");
}
// TODO If you have a license key, you have to register it here
//WorkflowRuntime.RegisterLicense("your license key text"); // TODO If you are using database different from SQL Server you have to use different persistence provider here.
var dbProvider = new MSSQLProvider(ConnectionString); var builder = new WorkflowBuilder(
dbProvider,
new OptimaJet.Workflow.Core.Parser.XmlWorkflowParser(),
dbProvider
).WithDefaultCache(); var runtime = new WorkflowRuntime()
.WithBuilder(builder)
.WithPersistenceProvider(dbProvider)
.EnableCodeActions()
.SwitchAutoUpdateSchemeBeforeGetAvailableCommandsOn()
.AsSingleServer(); var plugin = new OptimaJet.Workflow.Plugins.BasicPlugin();
// Settings for SendEmail actions
// plugin.Setting_Mailserver = "smtp.yourserver.com";
// plugin.Setting_MailserverPort = 25;
// plugin.Setting_MailserverFrom = "from@yourserver.com";
// plugin.Setting_MailserverLogin = "login@yourserver.com";
// plugin.Setting_MailserverPassword = "pass";
// plugin.Setting_MailserverSsl = true;
runtime.WithPlugin(plugin); // events subscription
runtime.ProcessActivityChanged += (sender, args) => { };
runtime.ProcessStatusChanged += (sender, args) => { };
// TODO If you have planned to use Code Actions functionality that required references to external assemblies
// you have to register them here
//runtime.RegisterAssemblyForCodeActions(Assembly.GetAssembly(typeof(SomeTypeFromMyAssembly))); // starts the WorkflowRuntime
// TODO If you have planned use Timers the best way to start WorkflowRuntime is somewhere outside
// of this function in Global.asax for example
runtime.Start(); return runtime;
}
}
}
You can learn more about the different configurations and features that are supported by the Workflow Runtime from the official docs page.
This is great so far, we have basically covered one major component of the WorkflowEngine.
Let’s get into the new step, which is preparing the project to host and display Workflow designer UI component.
It is the component that will help us build the Performance Evaluation or appraisal workflow.
Workflow Designer
Project Setup
From the solution, let’s create a new project with type ASP.NET Core MVC Web Application
Add the same dependencies to this project:
Also you will need to add a project reference to the previous project, I named it Workflow.Core
Now we have all the needed dependencies ready.
We will start by preparing our Workflow Designer’s API endpoint.
This endpoint will handle the communication between the Designer component on the UI and the Workflow Runtime.
Designer API
Add a new Controller with name DesignerController:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Specialized;
using System.Text;
using Workflow.Core;
using OptimaJet.Workflow;
namespace Workflow.Designer.Controllers;public class DesignerController : Controller
{
public IActionResult Index()
{
return View();
}
public async Task Api()
{
Stream? filestream = null;
var parameters = new NameValueCollection(); //Defining the request method
var isPost = Request.Method.Equals("POST", StringComparison.OrdinalIgnoreCase); //Parse the parameters in the query string
foreach (var q in Request.Query)
{
parameters.Add(q.Key, q.Value.First());
} if (isPost)
{
//Parsing the parameters passed in the form
var keys = parameters.AllKeys; foreach (var key in Request.Form.Keys)
{
if (!keys.Contains(key))
{
parameters.Add(key, Request.Form[key]);
}
} //If a file is passed
if (Request.Form.Files.Count > 0)
{
//Save file
filestream = Request.Form.Files[0].OpenReadStream();
}
} //Calling the Designer Api and store answer
var (result, hasError) = await WorkflowInit.Runtime.DesignerAPIAsync(parameters, filestream); //If it returns a file, send the response in a special way
if (parameters["operation"]?.ToLower() == "downloadscheme" && !hasError)
return File(Encoding.UTF8.GetBytes(result), "text/xml"); if (parameters["operation"]?.ToLower() == "downloadschemebpmn" && !hasError)
return File(Encoding.UTF8.GetBytes(result), "text/xml"); //response
return Content(result);
}
}
Also we need to make sure the connection string is correctly loaded and set to the database that hosts the workflow persistence tables, as we have previously added in this tutorial.
Since I am using SQL Server Express database for this tutorial, here is how my appsettings.json looks like:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost\\SQLEXPRESS;Database=HumanResourcesDb;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Your connection string might look different based on the type of database you are using.
Now let’s jump to prepare the UI part to load our Workflow Design Component.
Designer UI
From the Solution explorer, add a new folder with name ‘Designer’ under th
And inside it add an index.cshtml file
This file should include the razor code to prepare the web page:
@{
ViewBag.Title = "Designer";
Layout = "~/Views/Shared/_Layout.cshtml";
}
Now navigate to Home folder and open index.cshtml and edit it to look as the below code:
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p><a href="/Designer/Index">Open designer here</a></p>
</div>
Head to WorkflowEngine.NET site to download the needed UI resources for the Workflow Designer
You will need to download the below items:
- templates folder
- workflowdesigner.min.css
- workflowdesigner.min.js
Add them under wwwroot folder of your Workflow.Designer Project, as per the below image:
Next, we will need to prepare the building blocks that setup the Workflow Designer and display it properly on the page while making sure all its functions are attached.
The index.cshtml under Designer Folder should look as the below:
@{
ViewBag.Title = "Designer";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<link href="~/css/workflowdesigner.min.css" rel="stylesheet" type="text/css" />
<script src="~/js/workflowdesigner.min.js" type="text/javascript"></script>
<script src="~/lib/jquery/dist/jquery.min.js" type="text/javascript"></script>
<form action="" id="uploadform" method="post" enctype="multipart/form-data" onsubmit="tmp()" style="padding-bottom: 8px;">
<input type="file" name="uploadFile" id="uploadFile" style="display:none" onchange="javascript: UploadScheme(this);">
</form><div id="wfdesigner" style="min-height:600px; max-width: 1200px;"></div>
<script>
var QueryString = function () {
// This function is anonymous, is executed immediately and
// the return value is assigned to QueryString!
var query_string = {};
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
// If first entry with this name
if (typeof query_string[pair[0]] === "undefined") {
query_string[pair[0]] = pair[1];
// If second entry with this name
} else if (typeof query_string[pair[0]] === "string") {
var arr = [query_string[pair[0]], pair[1]];
query_string[pair[0]] = arr;
// If third or later entry with this name
} else {
query_string[pair[0]].push(pair[1]);
}
}
return query_string;
}();
//Load settings
var schemecode = QueryString.code ? QueryString.code : 'SimpleWF';
var processid = QueryString.processid;
var graphwidth = 1200;
var graphminheight = 600;
var graphheight = graphminheight;
var wfdesigner = undefined; //Recreate designer object
function wfdesignerRedraw() {
var data;
if (wfdesigner != undefined) {
wfdesigner.destroy();
}
wfdesigner = new WorkflowDesigner({
name: 'simpledesigner',
apiurl: '/Designer/API',
renderTo: 'wfdesigner',
templatefolder: '/templates/',
graphwidth: graphwidth,
graphheight: graphheight
});
if (data == undefined) {
var isreadonly = false;
if (processid != undefined && processid != '')
isreadonly = true;
var p = { schemecode: schemecode, processid: processid, readonly: isreadonly };
if (wfdesigner.exists(p))
wfdesigner.load(p);
else
wfdesigner.create(schemecode);
}
else {
wfdesigner.data = data;
wfdesigner.render();
}
}
wfdesignerRedraw();
//Adjusts the size of the designer window
$(window).resize(function () {
if (window.wfResizeTimer) {
clearTimeout(window.wfResizeTimer);
window.wfResizeTimer = undefined;
}
window.wfResizeTimer = setTimeout(function () {
var w = $(window).width();
var h = $(window).height();
if (w > 300)
graphwidth = w - 40;
if (h > 300)
graphheight = h - 250;
if (graphheight < graphminheight)
graphheight = graphminheight;
wfdesigner.resize(graphwidth, graphheight);
}, 150);
});
$(window).resize(); function DownloadScheme() {
wfdesigner.downloadscheme();
}
function DownloadSchemeBPMN() {
wfdesigner.downloadschemeBPMN();
}
var selectSchemeType;
function SelectScheme(type) {
if (type)
selectSchemeType = type;
var file = $('#uploadFile');
file.trigger('click');
}
function UploadScheme(form) {
if (form.value == "")
return;
if (selectSchemeType == "bpmn") {
wfdesigner.uploadschemeBPMN($('#uploadform')[0], function () {
wfdesigner.autoarrangement();
alert('The file is uploaded!');
});
}
else {
wfdesigner.uploadscheme($('#uploadform')[0], function () {
alert('The file is uploaded!');
});
}
}
function OnSave() {
wfdesigner.schemecode = schemecode;
var err = wfdesigner.validate();
if (err != undefined && err.length > 0) {
alert(err);
}
else {
wfdesigner.save(function () {
alert('The scheme is saved!');
});
}
} function OnNew() {
wfdesigner.create();
}</script>
Now let’s add some css to style the page and the buttons:
body {
margin-bottom: 60px;
}
a {
outline: none;
text-decoration: none;
}.ui {
padding: 8px 15px;
}.ui.primary.button,
.ui.primary.button:focus {
background: #f2f2f2;
border: 1px solid #f2f2f2;
border-radius: 2px;
font-weight: normal;
color: #2c2c2c;
}.ui.primary.button:hover {
background: #c9c9c9;
}.ui.primary.button:active {
background: #b5b5b5;
}.ui.secondary.button,
.ui.secondary.button:focus {
background: #FFFFFF;
border: 1px solid #f2f2f2;
border-radius: 2px;
font-weight: normal;
color: #4d4d4d;
}.ui.secondary.button:hover {
background: #dbdbdb;
color: #2c2c2c;
}.ui.secondary.button:active {
background: #dbdbdb;
color: #2c2c2c;
}
Now your Designer is ready to be displayed on browser.
One last thing before we test it, we need to make sure the connection string is correctly loaded and set to the database that hosts the workflow persistence tables, as we have previously added in this tutorial.
Since I am using SQL Server Express database for this tutorial, here is how my appsettings.json looks like:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost\\SQLEXPRESS;Database=HumanResourcesDb;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Your connection string might look different based on the type of database you are using.
Now open program.cs file, and the below line after builder.build method call
WorkflowInit.ConnectionString = app.Configuration.GetConnectionString("DefaultConnection");
Testing the Workflow Designer
Make sure to set the Workflow.Designer Project as startup project, then press F5 to run it on browser.
Click on Open Design button on the page, you should then see the Workflow Designer nicely loading its comprehensive components:
Designing the Performance Evaluation Workflow
Now let’s see how can we design our Performance Evaluation Workflow with this designer tool.
Note that any design you make can be saved (persisted) in the Workflow tables as a scheme in XML format, so that it can be pulled again at any time and edited here.
Also the power of WorkflowEngine lies in that it allows the system owner or admin to change the workflow at anytime and it will be reflected on the system without having to deploy or change code.
So let’s start designing our workflow
Activities
Drag and drop an initial activity.
Double-click on it to show the details panel, Change the name and state as per the below then press Save.
Now let’s add some basic activities and a final activity, edit them same as we have done in the previous activity.
These should look as the below on the designer:
For now we have only defined activities, these can actually do nothing without connections.
These connections are known as Transitions in WorkflowEngine.
Transitions
A transition can come in 3 different forms:
Direct, Reverse or unspecified
A transition can happen upon an action, which is called a trigger.
Triggers
A trigger can either be auto, command or timer
We can create some commands to help us define how we will trigger the transitions from some activities.
Click on the command icon from the designer’s top menu, second button:
This will open the commands dialog.
Press Create and let’s add 2 commands:
Submit and Amend
We will use these to trigger transitions for some activities.
There is a complete documentation about all the different elements of the Workflow Designer
Check the official docs here.
Now let’s complete our designer sample to make it look as the below:
Now let’s see how the scheme xml for this workflow would look like, from the Menu choose File then download scheme
Here is our complete workflow xml:
<Process Name="SimpleWF" CanBeInlined="false" Tags="" LogEnabled="false">
<Designer />
<Commands>
<Command Name="Submit" />
<Command Name="Amend" />
</Commands>
<Activities>
<Activity Name="System Generates Self-Evaluation Form" State="Form Created" IsInitial="true" IsFinal="false" IsForSetState="true" IsAutoSchemeUpdate="true">
<Designer X="420" Y="10" Hidden="false" />
</Activity>
<Activity Name="Employee Submits Evaluation Form" State="Form Submitted" IsInitial="false" IsFinal="false" IsForSetState="true" IsAutoSchemeUpdate="true">
<Designer X="730" Y="140" Hidden="false" />
</Activity>
<Activity Name="Manager Takes Decision on Evaluation Form" State="Form In Review" IsInitial="false" IsFinal="false" IsForSetState="true" IsAutoSchemeUpdate="true">
<Designer X="1070" Y="310" Hidden="false" />
</Activity>
<Activity Name="HR Takes Decision on Evaluation Form" State="In Review" IsInitial="false" IsFinal="false" IsForSetState="true" IsAutoSchemeUpdate="true">
<Designer X="550" Y="300" Hidden="false" />
</Activity>
<Activity Name="System Completes Evaluation Form" State="Form Complete" IsInitial="false" IsFinal="true" IsForSetState="true" IsAutoSchemeUpdate="true">
<Designer X="550" Y="440" Hidden="false" />
</Activity>
<Activity Name="Notify Employee" State="Form Created" IsInitial="false" IsFinal="false" IsForSetState="true" IsAutoSchemeUpdate="true">
<Designer X="470" Y="140" Hidden="false" />
</Activity>
<Activity Name="Notify Manager" State="In Review" IsInitial="false" IsFinal="false" IsForSetState="true" IsAutoSchemeUpdate="true">
<Designer X="1130" Y="140" Hidden="false" />
</Activity>
</Activities>
<Transitions>
<Transition Name="Employee Submits Evaluation Form_Notify Manager_1" To="Notify Manager" From="Employee Submits Evaluation Form" Classifier="Direct" AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And" DisableParentStateControl="false">
<Triggers>
<Trigger Type="Command" NameRef="Submit" />
</Triggers>
<Conditions>
<Condition Type="Always" />
</Conditions>
<Designer X="1042" Y="167" Hidden="false" />
</Transition>
<Transition Name="System Generates Self-Evaluation Form_Notify Employee_1" To="Notify Employee" From="System Generates Self-Evaluation Form" Classifier="Direct" AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And" DisableParentStateControl="false">
<Triggers>
<Trigger Type="Auto" />
</Triggers>
<Conditions>
<Condition Type="Always" />
</Conditions>
<Designer X="541.5" Y="100" Hidden="false" />
</Transition>
<Transition Name="Notify Employee_Employee Submits Evaluation Form_1" To="Employee Submits Evaluation Form" From="Notify Employee" Classifier="Direct" AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And" DisableParentStateControl="false">
<Triggers>
<Trigger Type="Auto" />
</Triggers>
<Conditions>
<Condition Type="Always" />
</Conditions>
<Designer Hidden="false" />
</Transition>
<Transition Name="Notify Manager_Manager Takes Decision on Evaluation Form_1" To="Manager Takes Decision on Evaluation Form" From="Notify Manager" Classifier="Direct" AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And" DisableParentStateControl="false">
<Triggers>
<Trigger Type="Auto" />
</Triggers>
<Conditions>
<Condition Type="Always" />
</Conditions>
<Designer Hidden="false" />
</Transition>
<Transition Name="Manager Takes Decision on Evaluation Form_HR Takes Decision on Evaluation Form_1" To="HR Takes Decision on Evaluation Form" From="Manager Takes Decision on Evaluation Form" Classifier="Direct" AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And" DisableParentStateControl="false">
<Triggers>
<Trigger Type="Command" NameRef="Submit" />
</Triggers>
<Conditions>
<Condition Type="Always" />
</Conditions>
<Designer Hidden="false" />
</Transition>
<Transition Name="Manager Takes Decision on Evaluation Form_Employee Submits Evaluation Form_1" To="Employee Submits Evaluation Form" From="Manager Takes Decision on Evaluation Form" Classifier="Reverse" AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And" DisableParentStateControl="false">
<Triggers>
<Trigger Type="Command" NameRef="Amend" />
</Triggers>
<Conditions>
<Condition Type="Always" />
</Conditions>
<Designer Hidden="false" />
</Transition>
<Transition Name="HR Takes Decision on Evaluation Form_System Completes Evaluation Form_1" To="System Completes Evaluation Form" From="HR Takes Decision on Evaluation Form" Classifier="Direct" AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And" DisableParentStateControl="false">
<Triggers>
<Trigger Type="Command" NameRef="Submit" />
</Triggers>
<Conditions>
<Condition Type="Always" />
</Conditions>
<Designer X="671.1933822631836" Y="393" Hidden="false" />
</Transition>
<Transition Name="HR Takes Decision on Evaluation Form_Employee Submits Evaluation Form_1" To="Employee Submits Evaluation Form" From="HR Takes Decision on Evaluation Form" Classifier="Reverse" AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And" DisableParentStateControl="false">
<Triggers>
<Trigger Type="Command" NameRef="Amend" />
</Triggers>
<Conditions>
<Condition Type="Always" />
</Conditions>
<Designer Hidden="false" />
</Transition>
</Transitions>
</Process>
Next Steps:
After doing all those previous actions to prepare and run a workflow designer on our localhost, next would come implementing the above into actual .NET project where you can integrate with Workflow to handle all the processes that are related to multiple step operations to complete an action, same as have seen in the Performance Appraisal example in this tutorial.
WorkflowEngine has great sample codes, built with ASP.NET Core.
You can download the sample code from the GitHub account, and follow all the steps mentioned in the readme to test the implementation and have a bigger picture on this great product.
The project in the link shows how to build vacation requests workflow in ASP.NET Core using WorkflowEngine
Here is how it looks like:
And it has other functions that directly communicate with the Workflow Runtime:
- Creating a vacation request
- Displaying all the vacation requests workflows
- Seeing all inbox and outbox workflow notifications
- Ability to switch between the different employees to see how they can interact with the workflow activities.
And many others.
If you have technical questions, please let me know or reach out directly to OptimaJet’s tech support support@optimajet.com
For commercial use, you can send email to sales@optimajet.com
Summary
In this tutorial we have learned how can WorkflowEngine help you build a workflow process for Employee performance appraisal using .NET and SQL Server.
WorkflowEngine is a comprehensive product that provides you great flexibility to integrate a workflow process into your .NET applications.
You can find lots of samples to help you integrate your .NET application with Workflow Engine
Get started with WorkflowEngine from here
Also there are video tutorials in OptimaJet’s Youtube Channel that explain the different processes and parts of the WorkflowEngine.
And a video by Jeff Fritz where he explains in details about WorkflowEngine.
Collaborations
I am always open to discuss any protentional opportunities and collaborations.
Check this page to learn more about how we can benefit each other.
Bonus
The workflow topic is huge and requires a good amount of focus and a relaxed environment.
You can enjoy reading and learning more about WorkflowEngine with these brilliant classical masterpieces: