elsa-workflows / elsa-workflows/elsa-core
AddResilienceStrategyType not registering IResilienceStrategy
- 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
Assessment
This issue has not been assessed yet.