docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md
UPDATE: There is a newer version of this article, which covers Elsa 3 with ABP Framework. You can check it at https://abp.io/community/articles/using-elsa-3-workflow-with-abp-framework-usqk8afg
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.
🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. 👉 Subscribe here → engincanveske.substack.com 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv
You can find the source of the example solution used in this article here.
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).
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*
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.
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
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
ElsaDemoWebModule class and update your ElsaDemoWebModule 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 our OnApplicationInitialization method we started the HelloWorldConsole workflow.
If we run the application and examine the console outputs, we should see the message that we defined in our workflow.
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.
Elsa (we've already added in the previous section) and Elsa.Activities.Http packages into our web application.dotnet add package Elsa.Activities.Http
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 activity WriteHttpResponse, 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 the ConfigureElsa 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
});
}
OnApplicationInitilization method of your ElsaDemoWebModule 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>();
}
HelloWorldHttp workflow.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.
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.
Elsa.Designer.Components.Web. This package provides us the Elsa Dashboard component.dotnet add package Elsa.Designer.Components.Web
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 your appsettings.json and add the following Elsa section into this file.
{
//...
"Elsa": {
"Http": {
"BaseUrl": "https://localhost:44336"
}
}
}
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 the ElsaDemo.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";
}
}
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);
}
}
}
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"
}
}
@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>
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
)
);
//...
}
}
}
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.
🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. 👉 Subscribe here → engincanveske.substack.com 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv