Using Elsa Workflow with ABP Framework
Elsa Core is an open-source workflows library that can be used in any kind of .NET Core application. Using such a workflow library can be useful to implement business rules visually or programmatically.
This article shows how we can use this workflow library within our ABP-based application. We will start with a couple of examples and then we will integrate the Elsa Dashboard (you can see it in the above gif) into our application to be able to design our workflows visually.
Source Code
You can find the source of the example solution used in this article here.
Create the Project
In this article, I will create a new startup template with EF Core as a database provider and MVC/Razor-Pages for the UI framework.
If you already have a project with MVC/Razor-Pages or Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project (you can skip this section).
- We will create a new solution named
ElsaDemo
(or whatever you want). We will create a new startup template with EF Core as a database provider and MVC/Razor-Pages for the UI framework by using the ABP CLI:
abp new ElsaDemo
Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE).
We can run the
ElsaDemo.DbMigrator
project to apply migration into our database and seed initial data.After the database and initial data created, we can run the
ElsaDemo.Web
to see our UI working properly.
Default admin username is admin and password is 1q2w3E*
Let's Create The First Workflow (Console Activity)
We can start with creating our first workflow. Let's get started with creating a basic hello-world workflow by using console activity. In this example, we will programmatically define a workflow definition that displays the text "Hello World from Elsa!" to the console using Elsa's Workflow Builder API and run this workflow when the application initialized.
Install Packages
We need to add two packages: Elsa
and Elsa.Activities.Console
into our ElsaDemo.Web
project. We can add these two packages with the following command:
dotnet add package Elsa
dotnet add package Elsa.Activities.Console
- After the packages installed, we can define our first workflow. To do this, create a folder named Workflows and in this folder create a class named
HelloWorldConsole
.
using Elsa.Activities.Console;
using Elsa.Builders;
namespace ElsaDemo.Web.Workflows
{
public class HelloWorldConsole : IWorkflow
{
public void Build(IWorkflowBuilder builder) => builder.WriteLine("Hello World from Elsa!");
}
}
In here we've basically implemented the
IWorkflow
interface which only has one method named Build. In this method, we can define our workflow's execution steps (activities).As you can see in the example above, we've used an activity named WriteLine, which writes a line of text to the console. Elsa Core has many pre-defined activities like that. E.g HttpEndpoint and WriteHttpResponse (we will see them both in the next section).
"An activity is an atomic building block that represents a single executable step on the workflow." - Elsa Core Activity Definition
- After defining our workflow, we need to define service registrations which required for the Elsa Core library to work properly. To do that, open your
ElsaDemoWebModule
class and update yourElsaDemoWebModule
with the following lines. Most of the codes are abbreviated for simplicity.
using ElsaDemo.Web.Workflows;
using Elsa.Services;
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
//...
ConfigureElsa(context);
}
private void ConfigureElsa(ServiceConfigurationContext context)
{
context.Services.AddElsa(options =>
{
options
.AddConsoleActivities()
.AddWorkflow<HelloWorldConsole>();
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
//...
var workflowRunner = context.ServiceProvider.GetRequiredService<IBuildsAndStartsWorkflow>();
workflowRunner.BuildAndStartWorkflowAsync<HelloWorldConsole>();
}
Here we basically, configured Elsa's services in our
ConfigureServices
method and after that in ourOnApplicationInitialization
method we started theHelloWorldConsole
workflow.If we run the application and examine the console outputs, we should see the message that we defined in our workflow.
Creating A Workflow By Using Http Activities
In this example, we will create a workflow that uses Http Activities. It will basically listen the specified route for incoming HTTP Request and writes back a simple response.
Add Elsa.Activities.Http Package
- To be able to use HTTP Activities we need to add
Elsa
(we've already added in the previous section) andElsa.Activities.Http
packages into our web application.
dotnet add package Elsa.Activities.Http
- After the package installed, we can create our workflow. Let's started with creating a class named
HelloWorldHttp
under Workflows folder.
using System.Net;
using Elsa.Activities.Http;
using Elsa.Builders;
namespace ElsaDemo.Web.Workflows
{
public class HelloWorldHttp : IWorkflow
{
public void Build(IWorkflowBuilder builder)
{
builder
.HttpEndpoint("/hello-world")
.WriteHttpResponse(HttpStatusCode.OK, "<h1>Hello World!</h1>", "text/html");
}
}
}
The above workflow has two activities. The first activity
HttpEndpoint
represents an HTTP endpoint, which can be invoked using an HTTP client, including a web browser. The first activity is connected to the second activityWriteHttpResponse
, which returns a simple response to us.After defined the HelloWorldHttp workflow we need to define this class as workflow. So, open your
ElsaDemoWebModule
and update theConfigureElsa
method as below.
private void ConfigureElsa(ServiceConfigurationContext context)
{
context.Services.AddElsa(options =>
{
options
.AddConsoleActivities()
.AddHttpActivities() //add this line to be able to use the http activities
.AddWorkflow<HelloWorldConsole>()
.AddWorkflow<HelloWorldHttp>(); //workflow that we defined
});
}
- And add the UseHttpActivities middleware to
OnApplicationInitilization
method of yourElsaDemoWebModule
class.
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
// ...
app.UseAuditing();
app.UseAbpSerilogEnrichers();
app.UseHttpActivities(); //add this line
app.UseConfiguredEndpoints();
var workflowRunner = context.ServiceProvider.GetRequiredService<IBuildsAndStartsWorkflow>();
workflowRunner.BuildAndStartWorkflowAsync<HelloWorldConsole>();
}
- If we run the application and navigate to the "/hello-world" route we should see the response message that we've defined (by using WriteHttpResponse activity) in our
HelloWorldHttp
workflow.
Integrate Elsa Dashboard To Application
Until now we've created two workflows programmatically. But also we can create workflows visually by using Elsa's HTML5 Workflow Designer.
Being able to design our workflows easily and taking advantage of HTML5 Workflow Designer we will integrate the Elsa Dashboard to our application.
Install Packages
- Following three packages required for Elsa Server.
dotnet add package Elsa.Activities.Temporal.Quartz
dotnet add package Elsa.Persistence.EntityFramework.SqlServer
dotnet add package Elsa.Server.Api
Also, we need to install the Elsa and Elsa.Activities.Http packages but we've already installed these packages in the previous sections.
- We need to install one more package named
Elsa.Designer.Components.Web
. This package provides us the Elsa Dashboard component.
dotnet add package Elsa.Designer.Components.Web
- After the package installations completed, we need to make the necessary configurations to be able to use the Elsa Server and Elsa Dashboard. Therefore, open your
ElsaDemoWebModule
class and make the necessary changes as below.
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
//...
ConfigureElsa(context, configuration);
}
private void ConfigureElsa(ServiceConfigurationContext context, IConfiguration configuration)
{
var elsaSection = configuration.GetSection("Elsa");
context.Services.AddElsa(elsa =>
{
elsa
.UseEntityFrameworkPersistence(ef =>
DbContextOptionsBuilderExtensions.UseSqlServer(ef,
configuration.GetConnectionString("Default")))
.AddConsoleActivities()
.AddHttpActivities(elsaSection.GetSection("Server").Bind)
.AddQuartzTemporalActivities()
.AddJavaScriptActivities()
.AddWorkflowsFrom<Startup>();
});
context.Services.AddElsaApiEndpoints();
context.Services.Configure<ApiVersioningOptions>(options =>
{
options.UseApiBehavior = false;
});
context.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy
.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
.WithExposedHeaders("Content-Disposition"))
);
//Uncomment the below line if your abp version is lower than v4.4 to register controllers of Elsa .
//See https://github.com/abpframework/abp/pull/9299 (we will no longer need to specify this line of code from v4.4)
// context.Services.AddAssemblyOf<Elsa.Server.Api.Endpoints.WorkflowRegistry.Get>();
//Disable antiforgery validation for elsa
Configure<AbpAntiForgeryOptions>(options =>
{
options.AutoValidateFilter = type =>
type.Assembly != typeof(Elsa.Server.Api.Endpoints.WorkflowRegistry.Get).Assembly;
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
app.UseCors();
//...
app.UseHttpActivities();
app.UseConfiguredEndpoints(endpoints =>
{
endpoints.MapFallbackToPage("/_Host");
});
var workflowRunner = context.ServiceProvider.GetRequiredService<IBuildsAndStartsWorkflow>();
workflowRunner.BuildAndStartWorkflowAsync<HelloWorldConsole>();
}
These services required for the dashboard.
We don't need to register our workflows one by one anymore. Because now we use
.AddWorkflowsFrom<Startup>()
, and this registers workflows on our behalf.As you may notice here, we use a section named
Elsa
and its sub-sections from the configuration system but we didn't define them yet. To define them open yourappsettings.json
and add the following Elsa section into this file.
{
//...
"Elsa": {
"Http": {
"BaseUrl": "https://localhost:44336"
}
}
}
Define Permission For Elsa Dashboard
We can define a permission to be assured of only allowed users can see the Elsa Dashboard.
Open your
ElsaDemoPermissions
class under the Permissions folder (in theElsaDemo.Application.Contracts
layer) and add the following permission name.
namespace ElsaDemo.Permissions
{
public static class ElsaDemoPermissions
{
public const string GroupName = "ElsaDemo";
public const string ElsaDashboard = GroupName + ".ElsaDashboard";
}
}
- After that, open your
ElsaDemoPermissionDefinitionProvider
class and define the permission for Elsa Dashboard.
using ElsaDemo.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
namespace ElsaDemo.Permissions
{
public class ElsaDemoPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup(ElsaDemoPermissions.GroupName);
myGroup.AddPermission(ElsaDemoPermissions.ElsaDashboard, L("Permission:ElsaDashboard"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<ElsaDemoResource>(name);
}
}
}
- As you can notice, we've used a localized value (L("Permission:ElsaDashboard")) but haven't added this localization key and value to the localization file, so let's add this localization key and value. To do this, open your
en.json
file under Localization/ElsaDemo folder (under the DomainShared layer) and add this localization key.
{
"culture": "en",
"texts": {
"Menu:Home": "Home",
"Welcome": "Welcome",
"LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.",
"Permission:ElsaDashboard": "Elsa Dashboard"
}
}
Add Elsa Dashboard Component To Application
- After those configurations, now we can add Elsa Dashboard to our application with an authorization check. To do this, create a razor page named _Host.cshtml (under Pages folder) and update its content as below.
@page "/elsa"
@using ElsaDemo.Permissions
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(ElsaDemoPermissions.ElsaDashboard)]
@{
var serverUrl = $"{Request.Scheme}://{Request.Host}";
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Elsa Workflows</title>
<link rel="icon" type="image/png" sizes="32x32" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-16x16.png">
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.css">
<script src="/_content/Elsa.Designer.Components.Web/monaco-editor/min/vs/loader.js"></script>
<script type="module" src="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.esm.js"></script>
</head>
<body class="h-screen" style="background-size: 30px 30px; background-image: url(/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/tile.png); background-color: #FBFBFB;">
<elsa-studio-root server-url="@serverUrl" monaco-lib-path="_content/Elsa.Designer.Components.Web/monaco-editor/min">
<elsa-studio-dashboard></elsa-studio-dashboard>
</elsa-studio-root>
</body>
</html>
- We've defined an attribute for authorization check here. With this authorization check, only the user who has the Elsa Dashboard permission allowed to see this page.
Add Elsa Dashboard Page To Main Menu
- We can open the
ElsaDemoMenuContributor
class under the Menus folder and define the menu item for reaching the Elsa Dashboard easily.
using System.Threading.Tasks;
using ElsaDemo.Localization;
using ElsaDemo.MultiTenancy;
using ElsaDemo.Permissions;
using Volo.Abp.Identity.Web.Navigation;
using Volo.Abp.SettingManagement.Web.Navigation;
using Volo.Abp.TenantManagement.Web.Navigation;
using Volo.Abp.UI.Navigation;
namespace ElsaDemo.Web.Menus
{
public class ElsaDemoMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name == StandardMenus.Main)
{
await ConfigureMainMenuAsync(context);
}
}
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
var administration = context.Menu.GetAdministration();
var l = context.GetLocalizer<ElsaDemoResource>();
context.Menu.Items.Insert(
0,
new ApplicationMenuItem(
ElsaDemoMenus.Home,
l["Menu:Home"],
"~/",
icon: "fas fa-home",
order: 0
)
);
//add Workflow menu-item
context.Menu.Items.Insert(
1,
new ApplicationMenuItem(
ElsaDemoMenus.Home,
"Workflow",
"~/elsa",
icon: "fas fa-code-branch",
order: 1,
requiredPermissionName: ElsaDemoPermissions.ElsaDashboard
)
);
//...
}
}
}
- With that menu item configuration, only the user who has Elsa Dashboard permission allowed to see the defined menu item.
Result
- Let's run the application and see how it looks like.
If the account you are logged in has the ElsaDemoPermissions.ElsaDashboard permission, you should see the Workflow menu item. If you do not see this menu item, please be assured that your logged-in account has that permission.
- Now we can click the "Workflow" menu item, display the Elsa Dashboard and designing workflows.
Comments
Serdar Genc 180 weeks ago
very good article. thanks.
Engincan Veske 180 weeks ago
Thank you.
behzad 180 weeks ago
THIS IS AWESOME! THANK YOU
Engincan Veske 180 weeks ago
Thank you.
xx xx 180 weeks ago
Thank you.
but I could not understand how to use the workflow in a real world,such as apply a job request,
Engincan Veske 180 weeks ago
Hi, thanks for the feedback. In this article, I just wanted to basically introduce the Elsa Workflow library and show how we can integrate the Elsa Dashboard into our ABP-based application. I may write another article about a real-time scenario in the future. You can check the Elsa Core's documentation for a real-time application, like document-approval (https://elsa-workflows.github.io/elsa-core/docs/next/guides/guides-document-approval).
Henry Chan 180 weeks ago
Could you provide a way to add ABP's Authorization to the Elsa API Server ?
Thanks.
Vivek Koppula 179 weeks ago
Thanks for the article.
If you want the final elsa dashboard to be within the layout of abp, try removing the line Layout = null; from _Host.cshtml file.
My _Host.cshtml file now looks like as follows.
@page "elsa/workflows" @using AbpAppTmpltMvcPvt.Permissions @using Microsoft.AspNetCore.Authorization @attribute [Authorize(AbpAppTmpltMvcPvtPermissions.ElsaDashboard)] @{ var serverUrl = $"{Request.Scheme}://{Request.Host}"; //Layout = null; } ....
Engincan Veske 179 weeks ago
Thanks, Vivek.
viswajwalith 179 weeks ago
Thanks for the article. We implemented this in our ABP application. We can able to create workflow but when we modify workflow, we are facing error.
Error: ArgumentException: A different value already has the Id '6'. Newtonsoft.Json.Utilities.BidirectionalDictionary<TFirst, TSecond>.Set(TFirst first, TSecond second)
Could you provide a way to solve the error.
Engincan Veske 179 weeks ago
Hi @viswajwalith, I think this is related to the Elsa Core library because there are several issues (e.g. https://github.com/elsa-workflows/elsa-core/issues/1008) in the Elsa repository like you're facing. I've encountered this problem as well. I wasn't using it in production so I deleted the workflow and create the new one by exporting the previous workflow's JSON file via the designer.
viswajwalith 178 weeks ago
We are trying to integrate elsa dashboard, it worked perfectly in Application template, but when we try to integrate in microservice based ABP solution getting following error when accessing the elsa dashboard. TypeLoadException: Could not load type 'System.Web.Security.MembershipPasswordAttribute' from assembly 'System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
Massimiliano Rizzuto 175 weeks ago
Great! Thanks! I was looking for a good workflow solution with abp
tolo 171 weeks ago
Great! How to replace ElsaContext with AbpDbContext?Any good ideas?
510423039@qq.com 164 weeks ago
https://localhost:44336/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/styles/tailwind.css net::ERR_ABORTED 404
lic0914@163.com 153 weeks ago
the same to you !! How to resolved ,I have tried to run elsa demo in github ,but it still so