Module System

May 16, 2026 ยท View on GitHub

The module system is the backbone of Elsa. It is a thin abstraction over IServiceCollection that lets packages register cohesive feature sets with dependency ordering.

Core Types

TypeFileRole
IModulesrc/common/Elsa.Features/Services/IModule.csHolds IServiceCollection, module properties, configured features, hosted service descriptors, and Apply().
Modulesrc/common/Elsa.Features/Implementations/Module.csConcrete feature graph builder and applier.
IFeaturesrc/common/Elsa.Features/Services/IFeature.csFeature lifecycle contract.
FeatureBasesrc/common/Elsa.Features/Abstractions/FeatureBase.csBase class for most code-first features.
DependsOnAttributesrc/common/Elsa.Features/Attributes/DependsOn.csDeclares feature dependencies.
DependencyOfAttributesrc/common/Elsa.Features/Attributes/DependencyOf.csDeclares optional dependency relationships.

Lifecycle

Feature classes usually use three lifecycle methods:

  1. Configure(): declare additional feature configuration, scan activities, or add endpoint assemblies.
  2. ConfigureHostedServices(): register hosted services with optional priority.
  3. Apply(): add concrete services, options, stores, handlers, endpoints, and providers to DI.

Module.Apply() topologically sorts configured features and dependencies, configures them once, filters features with missing optional dependencies, registers hosted services, applies services, and finally registers installed-feature metadata.

Entry Points

The common public path is:

services.AddElsa(elsa =>
{
    elsa
        .UseWorkflowManagement()
        .UseWorkflowRuntime()
        .UseWorkflowsApi();
});

Implementation links:

AppFeature is a small wrapper that lets application-specific configuration run after the default ElsaFeature dependencies.

Feature Dependencies

Feature dependencies are explicit attributes. Examples:

  • WorkflowsFeature depends on system clock, expressions, mediator, default formatters, multitenancy, and commit strategies.
  • WorkflowManagementFeature depends on string compression, mediator, memory cache, system clock, workflows, workflow definitions, and workflow instances.
  • WorkflowsApiFeature depends on workflow instances, management, runtime, and SAS tokens.

This is why feature classes are the best way to learn a module. They encode its runtime assumptions.

Module Properties

IModule.Properties is used as a shared bag during feature configuration. A concrete example is FastEndpoints assembly collection in Elsa.Api.Common/Extensions/ModuleExtensions.cs. Features call AddFastEndpointsAssembly, and later AddFastEndpointsFromModule registers all collected assemblies with FastEndpoints.

Shell Features

Many modules also have ShellFeatures/*Feature.cs. These implement CShells interfaces and allow modular server hosts to activate feature sets from configuration or packages. Shell features are parallel to code-first features:

Use shell features when working on modular hosting, package discovery, or Elsa.ModularServer.Web. Use code-first features for normal host configuration and tests.

Extension Method Pattern

Modules expose fluent extension methods in Extensions/ModuleExtensions.cs or related files. The method usually calls module.Configure<TFeature>() and returns IModule:

public static IModule UseWorkflowsApi(this IModule module, Action<WorkflowsApiFeature>? configure = default)
{
    module.Configure(configure);
    return module;
}

When adding a new module, follow this shape:

  • one Features/*Feature.cs
  • one ShellFeatures/*Feature.cs if the module must work with CShells
  • one Extensions/ModuleExtensions.cs
  • tests that prove the feature registers its core contracts

Common Pitfalls

  • Do not register services in extension methods when the module already has a feature class. Put service registration in Apply().
  • Do not bypass dependencies with direct service provider access in unrelated modules. Add a contract and dependency if the relationship is real.
  • Use TryAdd* for overridable defaults and normal Add* for deliberate multiple registrations such as handlers, validators, and descriptors.
  • If a feature uses Module.Configure<OtherFeature>(), verify that the other feature is already a dependency or that optional behavior is intentional.