For the complete documentation index, see llms.txt. This page is also available as Markdown.
Elsa Server + Studio (WASM)
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 Studio 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 solutiondotnetnewsln-nElsaServerAndStudio# Create the host projectdotnetnewweb-n"ElsaServer"# Add the host project to the solutiondotnetslnaddElsaServer/ElsaServer.csproj# Create the client projectdotnetnewblazorwasm-n"ElsaStudio"# Add the client project to the solutiondotnetslnaddElsaStudio/ElsaStudio.csproj# Navigate to the directory where the host project is locatedcdElsaServer# Add a reference to the client projectdotnetaddreference../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.
Add Packages
Add the following packages:
Update Program.cs
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
Update appsettings.json
Add the following configuration section to appsettings.json or appsettings.Development.json with the following content:
Create _Host.cshtml
To conclude the setup, create new folder called Pages and add a new file called _Host.cshtml and copy in the code showcased below:
Pages/_Host.cshtml
Setup Client
Next, we will modify the client project.
Add Elsa Studio Packages
Navigate to the root directory of the client project and add the following Elsa Studio packages:
Modify Program.cs
Open Program.cs and replace its existing content with the code provided below:
Program.cs
Configure Client Authentication and Localization
The hosted page still supplies the backend API URL through window.getClientConfig, but the client can use wwwroot/appsettings.json to select the Studio authentication provider and localization settings:
AuthenticationScopes are requested during sign-in. BackendApiScopes are requested when Studio obtains an access token for the Elsa Server API. Some identity providers require the backend API scope in the original sign-in grant as well; if token acquisition or refresh fails for the backend API scope, include the same scope in both AuthenticationScopes and BackendApiScopes.
Because this client is Blazor WebAssembly, register {studio-url}/authentication/login-callback as the redirect URI and {studio-url}/authentication/logout-callback as the logout callback URI. Studio initiates logout at {studio-url}/authentication/logout.
Modify MainLayout.razor
Update Layout/MainLayout.razor with the following code listing:
MainLayout.razor
Launch the Application
To see your application in action, navigate back to the root directory containing the host project:
using System.Text.Json;
using Elsa.Studio.Authentication.ElsaIdentity.BlazorWasm.Extensions;
using Elsa.Studio.Authentication.ElsaIdentity.HttpMessageHandlers;
using Elsa.Studio.Authentication.ElsaIdentity.UI.Extensions;
using Elsa.Studio.Authentication.OpenIdConnect.BlazorWasm.Extensions;
using Elsa.Studio.Authentication.OpenIdConnect.HttpMessageHandlers;
using Elsa.Studio.Contracts;
using Elsa.Studio.Core.BlazorWasm.Extensions;
using Elsa.Studio.Dashboard.Extensions;
using Elsa.Studio.Extensions;
using Elsa.Studio.Localization.BlazorWasm.Extensions;
using Elsa.Studio.Localization.Models;
using Elsa.Studio.Models;
using Elsa.Studio.Options;
using Elsa.Studio.Shell;
using Elsa.Studio.Shell.Extensions;
using Elsa.Studio.Workflows.Designer.Extensions;
using Elsa.Studio.Workflows.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);
var configuration = builder.Configuration;
// Register root components.
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.RootComponents.RegisterCustomElsaStudioElements();
// Choose authentication provider.
// Supported values: "OpenIdConnect" or "ElsaIdentity".
var authProvider = configuration["Authentication:Provider"];
if (string.IsNullOrWhiteSpace(authProvider))
authProvider = "ElsaIdentity";
Type authenticationHandler;
if (authProvider.Equals("ElsaIdentity", StringComparison.OrdinalIgnoreCase))
{
// Elsa Identity (username/password against Elsa backend) + login UI at /login.
builder.Services.AddElsaIdentity();
builder.Services.AddElsaIdentityUI();
authenticationHandler = typeof(ElsaIdentityAuthenticatingApiHttpMessageHandler);
}
else if (authProvider.Equals("OpenIdConnect", StringComparison.OrdinalIgnoreCase))
{
// OpenID Connect.
builder.Services.AddOpenIdConnectAuth(options =>
{
configuration.GetSection("Authentication:OpenIdConnect").Bind(options);
});
authenticationHandler = typeof(OidcAuthenticatingApiHttpMessageHandler);
}
else
{
throw new InvalidOperationException($"Unsupported Authentication:Provider value '{authProvider}'. Supported values are 'OpenIdConnect' and 'ElsaIdentity'.");
}
// Register shell services and modules.
var localizationConfig = new LocalizationConfig
{
ConfigureLocalizationOptions = options => configuration.GetSection("Localization").Bind(options)
};
builder.Services.AddCore();
builder.Services.AddShell();
builder.Services.AddRemoteBackend(new()
{
ConfigureHttpClientBuilder = options => options.AuthenticationHandler = authenticationHandler
});
builder.Services.AddDashboardModule();
builder.Services.AddWorkflowsModule();
builder.Services.AddLocalizationModule(localizationConfig);
// Build the application.
var app = builder.Build();
await app.UseElsaLocalization();
// Apply client config.
var js = app.Services.GetRequiredService<IJSRuntime>();
var clientConfig = await js.InvokeAsync<JsonElement>("getClientConfig");
var apiUrl = clientConfig.GetProperty("apiUrl").GetString() ?? throw new InvalidOperationException("No API URL configured.");
app.Services.GetRequiredService<IOptions<BackendOptions>>().Value.Url = new(apiUrl);
// Run each startup task.
var startupTaskRunner = app.Services.GetRequiredService<IStartupTaskRunner>();
await startupTaskRunner.RunStartupTasksAsync();
// Run the application.
await app.RunAsync();