elsa-workflows / elsa-workflows/elsa-core

AddResilienceStrategyType not registering IResilienceStrategy

Open
#7,085 1 comment 0 reactions 0 assignees View on GitHub
triaged
Dominant language
C#
Stars
7.9k
Forks
1.5k
Avg merge
15h 22m
Merged PRs (30d)
114

Description

## Description
I am trying to register an IResilienceStrategy via ResilienceFeature (rather than in appsettings/config). I am getting an error when the activity executes and tries to resolve the IResilienceStrategy via the provider by Id. It appears that having no strategies in configuration causes the ResilienceStrategyCatalog to fail.

```

services
.AddElsa(elsa =>
{
elsa.UseResilience(resilience => resilience.AddResilienceStrategyType());
elsa.AddWorkflow();
elsa.AddWorkflow();
elsa.AddWorkflow();
elsa.AddWorkflow();
elsa.AddWorkflow();
elsa.UseWorkflowManagement(management =>
{
management.UseEntityFrameworkCore(ef =>
ef.UseSqlServer(elsaOptions.ConnectionString));
});

elsa.UseWorkflowRuntime(runtime =>
{
runtime.UseEntityFrameworkCore(ef =>
ef.UseSqlServer(elsaOptions.ConnectionString));
});

elsa.UseScheduling();
elsa.AddSwagger();
});
```

My strategy:

```
public class Retry5TimesExponentialBackoffStrategy : IResilienceStrategy
{
public string Id { get; set; } = RetryStrategyIdConstants.Retry5TimesExponentialBackoff.StrategyId;
public string DisplayName { get; set; } = RetryStrategyIdConstants.Retry5TimesExponentialBackoff.DisplayName;

public int MaxRetryAttempts { get; set; } = 5;
public bool UseJitter { get; set; }
public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(1);
public DelayBackoffType BackoffType { get; set; } = DelayBackoffType.Exponential;

public Task ConfigurePipeline(
ResiliencePipelineBuilder builder,
ResilienceContext context)
{

var options = new RetryStrategyOptions
{
MaxRetryAttempts = MaxRetryAttempts,
BackoffType = BackoffType,
Delay = Delay,
UseJitter = UseJitter,
Name = DisplayName
};

builder.AddRetry(options);
return Task.CompletedTask;
}

```
```

public static class RetryStrategyIdConstants
{
public static readonly RetryStrategyInfo Retry5TimesExponentialBackoff = new()
{
StrategyId = "Retry5TimesExponentialBackoff",
DisplayName = "Retry 5 Times with Exponential Backoff"
};
}

```

My activity:

```
public class TestEventResiliantActivity(ILogger logger)
: CodeActivity, IResilientActivity
{
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
var resilienceService = context.GetRequiredService();
await resilienceService.InvokeAsync(this, context, () =>
{
var workflowContext = context.WorkflowExecutionContext;

const string attemptKey = "TestEventResiliantActivity_AttemptCount";
if (!context.TransientProperties.TryGetValue(attemptKey, out var attemptObj))
{
context.TransientProperties[attemptKey] = 1;
throw new InvalidOperationException("Simulated failure - attempt 1");
}

var attemptCount = (int)attemptObj;
if (attemptCount < 3)
{
context.TransientProperties[attemptKey] = attemptCount + 1;
throw new InvalidOperationException($"Simulated failure - attempt {attemptCount}");
}

logger.LogInformation("Activity succeeded on attempt {Attempt}", attemptCount);

return Task.FromResult(ValueTask.CompletedTask);
}, context.CancellationToken);
}

public IDictionary CollectRetryDetails(ActivityExecutionContext context, RetryAttempt attempt)
{
return new Dictionary
{
["AttemptNumber"] = attempt.AttemptNumber.ToString(),
["RetryDelay"] = attempt.RetryDelay.ToString()
};
}
}

```

## Steps to Reproduce
I trigger the above activity in a workflow via an event. When the ResilienceStrategyCatalog tries to get a strategy from the provider, it errors on .AddRange()

```
private async Task> GetStrategiesInternalAsync()
{
var strategies = new List();
foreach (var provider in _providers) strategies.AddRange(await provider.GetStrategiesAsync());
return strategies;
}
```

```
private IEnumerable GetStrategies()
{
var json = configuration.GetSectionAsJson("Resilience:Strategies");
return string.IsNullOrWhiteSpace(json) ? [] : serializer.DeserializeMany(json);
}
```

## Log Output
```
[2025-11-20 08:43:47] [Warning] An exception was caught from a downstream middleware component
System.ArgumentNullException: Value cannot be null. (Parameter 'collection')
at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection)
at Elsa.Resilience.ResilienceStrategyCatalog.GetStrategiesInternalAsync()
at Elsa.Resilience.ResilienceStrategyCatalog.GetAsync(String id, CancellationToken cancellationToken)
at Elsa.Resilience.ResilienceStrategyConfigEvaluator.ResolveByIdentifierAsync(ResilienceStrategyConfig config, CancellationToken cancellationToken)
at Elsa.Resilience.ResilienceStrategyConfigEvaluator.EvaluateAsync(ResilienceStrategyConfig config, ExpressionExecutionContext context, CancellationToken cancellationToken)
at Elsa.Resilience.ResilientActivityInvoker.InvokeAsync[T](IResilientActivity activity, ActivityExecutionContext context, Func`1 action, CancellationToken cancellationToken)
at IntegrationHub.Service.Workflows.Activities.TestEventResiliantActivity.ExecuteAsync(ActivityExecutionContext context) in C:\code\integration-hub\IntegrationHub.Service\Workflows\Activities\TestEventResiliantActivity.cs:line 13
at Elsa.Workflows.Activity.Elsa.Workflows.IActivity.ExecuteAsync(ActivityExecutionContext context)
at Elsa.Workflows.Middleware.Activities.DefaultActivityInvokerMiddleware.ExecuteActivityAsync(ActivityExecutionContext context)
at Elsa.Workflows.Runtime.Middleware.Activities.BackgroundActivityInvokerMiddleware.ExecuteActivityAsync(ActivityExecutionContext context)
at Elsa.Workflows.Middleware.Activities.DefaultActivityInvokerMiddleware.InvokeAsync(ActivityExecutionContext context)
at Elsa.Workflows.Runtime.Middleware.EvaluateLogPersistenceModesMiddleware.InvokeAsync(ActivityExecutionContext context)
at Elsa.Workflows.Middleware.Activities.NotificationPublishingMiddleware.InvokeAsync(ActivityExecutionContext context)
at Elsa.Workflows.Middleware.Activities.ExecutionLogMiddleware.InvokeAsync(ActivityExecutionContext context)
at Elsa.Workflows.Middleware.Activities.ExceptionHandlingMiddleware.InvokeAsync(ActivityExecutionContext context)
```

## Expected Behavior
I would expect GetStrategies to handle the lack of strategies in appsettings gracefully. The documentation is light but deepwiki strongly suggests that code-first is supported and strategies don't need to be duplicated in code and in appsettings.

## Actual Behavior
GetStrategies errors, activity can't execute.

## Environment
- **Elsa Package Version**: 3.5.1
- **Operating System**: Windows 11
- **Browser and Version**: Edge latest

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.