In this topic, we will create an ASP.NET Core application that acts as both an Elsa Server and an Elsa Studio.
Instead of running Elsa Server and Elsa Stusio as separate ASP.NET Core applications, you can also setup an ASP.NET Core application that hosts both the workflow server and the UI. The UI will still make HTTP calls to the backend as if they were hosted separately, but the difference is that they are now served from the same application and therefore deployable as a single unit.
For Elsa Studio, we will setup the Blazor parts using Blazor WebAssembly, which static files will be served from the ASP.NET Core host application.
Create Solution
In this chapter, we will scaffold a new solution and two projects:
The Host
The Client
The host will host both Elsa Server and the Blazor WebAssembly application representing Elsa Studio.
Run the following commands to create a solution with two projects:
# Create a new solution
dotnet new sln -n ElsaServerAndStudio
# Create the host project
dotnet new web -n "ElsaServer"
# Add the host project to the solution
dotnet sln add ElsaServer/ElsaServer.csproj
# Create the client project
dotnet new blazorwasm-empty -n "ElsaStudio"
# Add the client project to the solution
dotnet sln add ElsaStudio/ElsaStudio.csproj
# Navigate to the directory where the host project is located
cd ElsaServer
# Add a reference to the client project
dotnet add reference ../ElsaStudio/ElsaStudio.csproj
Setup Host
In this chapter, we will setup the host, which will host both the Elsa Server engine as well as the webassembly files for serving the Elsa Studio client assets to the browser.
Open the Program.cs file in your project and replace its contents with the code provided below. This code does a lot of things like setting up database connections, enabling user authentication, and preparing the server to handle workflows.
Program.cs
using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.Extensions;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseStaticWebAssets();
var services = builder.Services;
var configuration = builder.Configuration;
services
.AddElsa(elsa => elsa
.UseIdentity(identity =>
{
identity.TokenOptions = options => options.SigningKey = "large-signing-key-for-signing-JWT-tokens";
identity.UseAdminUserProvider();
})
.UseDefaultAuthentication()
.UseWorkflowManagement(management => management.UseEntityFrameworkCore())
.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore())
.UseScheduling()
.UseJavaScript()
.UseLiquid()
.UseCSharp()
.UseHttp(http => http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options))
.UseWorkflowsApi()
.AddActivitiesFrom<Program>()
.AddWorkflowsFrom<Program>()
);
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("*")));
services.AddRazorPages(options => options.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseRouting();
app.UseCors();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseWorkflowsApi();
app.UseWorkflows();
app.MapFallbackToPage("/_Host");
app.Run();
Update appsettings.json
Add the following configuration section to appsettings.json or appsettings.Development.json with the following content:
Open Program.cs and replace its existing content with the code provided below:
Program.cs
using System.Text.Json;
using Elsa.Studio.Dashboard.Extensions;
using Elsa.Studio.Shell;
using Elsa.Studio.Shell.Extensions;
using Elsa.Studio.Workflows.Extensions;
using Elsa.Studio.Contracts;
using Elsa.Studio.Core.BlazorWasm.Extensions;
using Elsa.Studio.Extensions;
using Elsa.Studio.Login.BlazorWasm.Extensions;
using Elsa.Studio.Login.HttpMessageHandlers;
using Elsa.Studio.Models;
using Elsa.Studio.Options;
using Elsa.Studio.Workflows.Designer.Extensions;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
// Build the host.
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// Register root components.
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.RootComponents.RegisterCustomElsaStudioElements();
// Register shell services and modules.
var backendApiConfig = new BackendApiConfig
{
ConfigureBackendOptions = options => builder.Configuration.GetSection("Backend").Bind(options),
ConfigureHttpClientBuilder = options => options.AuthenticationHandler = typeof(AuthenticatingApiHttpMessageHandler)
};
builder.Services.AddCore();
builder.Services.AddShell();
builder.Services.AddRemoteBackend(backendApiConfig);
builder.Services.AddLoginModule();
builder.Services.AddDashboardModule();
builder.Services.AddWorkflowsModule();
builder.Services.AddAgentsModule(backendApiConfig);
// Build the application.
var app = builder.Build();
// Apply client config.
var js = app.Services.GetRequiredService<IJSRuntime>();
var clientConfig = await js.InvokeAsync<JsonElement>("getClientConfig");
var apiUrl = clientConfig.GetProperty("apiUrl").GetString()!;
app.Services.GetRequiredService<IOptions<BackendOptions>>().Value.Url = new Uri(apiUrl);
// Run each startup task.
var startupTaskRunner = app.Services.GetRequiredService<IStartupTaskRunner>();
await startupTaskRunner.RunStartupTasksAsync();
// Run the application.
await app.RunAsync();
Modify MainLayout.razor
Update MainLayout.razor with the following code listing: