# 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. ![elsa-overview](./elsa-overview.gif) 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](https://github.com/abpframework/abp-samples/tree/master/ElsaDemo). ## 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](https://docs.abp.io/en/abp/latest/CLI): ```bash 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: ```bash 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`. ```csharp 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](https://elsa-workflows.github.io/elsa-core/docs/next/concepts/concepts-workflows#activity) * 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 your `ElsaDemoWebModule` with the following lines. Most of the codes are abbreviated for simplicity. ```csharp 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(); }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) { //... var workflowRunner = context.ServiceProvider.GetRequiredService(); workflowRunner.BuildAndStartWorkflowAsync(); } ``` * 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. ![hello-world-workflow](./hello-world-workflow.jpg) ## 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) and `Elsa.Activities.Http` packages into our web application. ```bash 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. ```csharp 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, "

Hello World!

", "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. ```csharp private void ConfigureElsa(ServiceConfigurationContext context) { context.Services.AddElsa(options => { options .AddConsoleActivities() .AddHttpActivities() //add this line to be able to use the http activities .AddWorkflow() .AddWorkflow(); //workflow that we defined }); } ``` * And add the **UseHttpActivities** middleware to `OnApplicationInitilization` method of your `ElsaDemoWebModule` class. ```csharp public override void OnApplicationInitialization(ApplicationInitializationContext context) { // ... app.UseAuditing(); app.UseAbpSerilogEnrichers(); app.UseHttpActivities(); //add this line app.UseConfiguredEndpoints(); var workflowRunner = context.ServiceProvider.GetRequiredService(); workflowRunner.BuildAndStartWorkflowAsync(); } ``` * 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. ![hello-world-http](./hello-world-http.jpg) ## 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. ```bash 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. ```bash 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. ```csharp 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(); }); context.Services.AddElsaApiEndpoints(); context.Services.Configure(options => { options.UseApiBehavior = false; }); context.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy .AllowAnyHeader() .AllowAnyMethod() .AllowAnyOrigin() .WithExposedHeaders("Content-Disposition")) ); //register controllers inside elsa context.Services.AddAssemblyOf(); //Disable antiforgery validation for elsa Configure(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(); workflowRunner.BuildAndStartWorkflowAsync(); } ``` * In here we've specified the Elsa Server Api's assembly by using the `AddAssemblyOf<>` extension method to register the required services (controllers). These services required for the dashboard (if we create a workflow by using **Elsa Workflow Designer** it calls some services under the hook, therefore we need to be assured about these services get registered). * With [v4.4](https://github.com/abpframework/abp/pull/9299), we will no longer need to specify this line of code. > **Note:** `AddAssemblyOf<>` extension method can help you to register all your services by convention. You can check [here](https://docs.abp.io/en/abp/latest/Dependency-Injection#conventional-registration) for more information about conventional registration. * We don't need to register our workflows one by one anymore. Because now we use `.AddWorkflowsFrom()`, 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. ```json { //... "Elsa": { "Http": { "BaseUrl": "https://localhost:44336" } } } ``` #### Define Permission For Elsa Dashboard * We can define a [permission](https://docs.abp.io/en/abp/latest/Authorization#permission-system) 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. ```csharp 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. ```csharp 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(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. ```json { "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. ```html @page "/elsa" @using ElsaDemo.Permissions @using Microsoft.AspNetCore.Authorization @attribute [Authorize(ElsaDemoPermissions.ElsaDashboard)] @{ var serverUrl = $"{Request.Scheme}://{Request.Host}"; Layout = null; } Elsa Workflows ``` * 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. ```csharp 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(); 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. ![elsa-demo-result](./elsa-demo-result.gif)