mirror of https://github.com/abpframework/abp
				
				
				
			Merge pull request #9367 from abpframework/EngincanV/community-article
	
		
	
				
					
				
			Community Article: Using Elsa Workflow with ABP Frameworkpull/9382/head
						commit
						bfac69173d
					
				| @ -0,0 +1,457 @@ | ||||
| # 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](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<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. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 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, "<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. | ||||
| 
 | ||||
| ```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<HelloWorldConsole>() | ||||
|             .AddWorkflow<HelloWorldHttp>(); //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<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. | ||||
| 
 | ||||
| ```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<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")) | ||||
|     ); | ||||
|      | ||||
|     //register controllers inside elsa | ||||
|     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>(); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| * 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<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. | ||||
| 
 | ||||
| ```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<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. | ||||
| 
 | ||||
| ```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; | ||||
| } | ||||
| <!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/assets/styles/tailwind.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-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. | ||||
| 
 | ||||
| ```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<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. | ||||
| 
 | ||||
|  | ||||
| After Width: | Height: | Size: 2.0 MiB | 
| After Width: | Height: | Size: 2.5 MiB | 
| After Width: | Height: | Size: 17 KiB | 
| After Width: | Height: | Size: 60 KiB | 
					Loading…
					
					
				
		Reference in new issue
	
	 Halil İbrahim Kalkan
						Halil İbrahim Kalkan