Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToke

foreach (var handlerName in options.HandlerNames)
{
var handler = _serviceProvider.GetRequiredKeyedService<EventHandlerDelegateWrapper>(handlerName);
_featureApi.AddHandler(handler.ProviderEventType, handler.EventHandlerDelegate);
var handlers = _serviceProvider.GetKeyedServices<EventHandlerDelegateWrapper>(handlerName);
foreach (var handler in handlers)
{
_featureApi.AddHandler(handler.ProviderEventType, handler.EventHandlerDelegate);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder,
/// <returns></returns>
public static OpenFeatureBuilder AddHandler(this OpenFeatureBuilder builder, ProviderEventTypes type, EventHandlerDelegate eventHandlerDelegate)
{
return AddHandler(builder, GenerateRandomName(), type, sp => eventHandlerDelegate);
return AddHandler(builder, typeof(EventHandlerDelegate).Name, type, sp => eventHandlerDelegate);
}

/// <summary>
Expand All @@ -340,7 +340,7 @@ public static OpenFeatureBuilder AddHandler(this OpenFeatureBuilder builder, str
/// <returns></returns>
public static OpenFeatureBuilder AddHandler(this OpenFeatureBuilder builder, ProviderEventTypes type, Func<IServiceProvider, EventHandlerDelegate> implementationFactory)
{
return AddHandler(builder, GenerateRandomName(), type, implementationFactory);
return AddHandler(builder, typeof(EventHandlerDelegate).Name, type, implementationFactory);
}

/// <summary>
Expand All @@ -353,25 +353,19 @@ public static OpenFeatureBuilder AddHandler(this OpenFeatureBuilder builder, Pro
/// <returns></returns>
public static OpenFeatureBuilder AddHandler(this OpenFeatureBuilder builder, string handlerName, ProviderEventTypes type, Func<IServiceProvider, EventHandlerDelegate> implementationFactory)
{
if (string.IsNullOrWhiteSpace(handlerName))
handlerName = string.Empty;

var key = string.Join(":", handlerName, type.ToString());

builder.Services.PostConfigure<OpenFeatureOptions>(options => options.AddHandlerName(key));

builder.Services.TryAddKeyedSingleton(key, (serviceProvider, _) =>
builder.Services.AddKeyedSingleton(key, (serviceProvider, _) =>
{
var handler = implementationFactory(serviceProvider);
return new EventHandlerDelegateWrapper(type, handler);
});

return builder;
}

static readonly Random _random = new();
private static string GenerateRandomName()
{
const string characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const int length = 4;

return new string([.. Enumerable.Range(0, length).Select(_ => characters[_random.Next(characters.Length)])]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,29 @@ public async Task EnsureInitializedAsync_ShouldSetHandler_WhenHandlersAreRegiste
// Act
await sut.EnsureInitializedAsync().ConfigureAwait(true);
}

[Fact]
public async Task EnsureInitializedAsync_ShouldSetHandler_WhenMultipleHandlersAreRegistered()
{
// Arrange
EventHandlerDelegate eventHandlerDelegate1 = (_) => { };
EventHandlerDelegate eventHandlerDelegate2 = (_) => { };
var featureProvider = new NoOpFeatureProvider();
var handler1 = new EventHandlerDelegateWrapper(ProviderEventTypes.ProviderReady, eventHandlerDelegate1);
var handler2 = new EventHandlerDelegateWrapper(ProviderEventTypes.ProviderReady, eventHandlerDelegate2);

_serviceCollection.AddSingleton<FeatureProvider>(featureProvider)
.AddKeyedSingleton("test:ProviderReady", (_, key) => handler1)
.AddKeyedSingleton("test:ProviderReady", (_, key) => handler2)
.Configure<OpenFeatureOptions>(options =>
{
options.AddHandlerName("test:ProviderReady");
});

var serviceProvider = _serviceCollection.BuildServiceProvider();
var sut = new FeatureLifecycleManager(Api.Instance, serviceProvider, NullLogger<FeatureLifecycleManager>.Instance);

// Act
await sut.EnsureInitializedAsync().ConfigureAwait(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,26 @@ public void AddHandler_AddsEventHandlerDelegateWrapperAsKeyedService()
Assert.Equal(eventHandler, handler.EventHandlerDelegate);
}

[Fact]
public void AddHandlerTwice_MultipleEventHandlerDelegateWrappersAsKeyedServices()
{
// Arrange
EventHandlerDelegate eventHandler1 = (eventDetails) => { };
EventHandlerDelegate eventHandler2 = (eventDetails) => { };
_systemUnderTest.AddHandler(Constant.ProviderEventTypes.ProviderReady, eventHandler1);
_systemUnderTest.AddHandler(Constant.ProviderEventTypes.ProviderReady, eventHandler2);

var serviceProvider = _services.BuildServiceProvider();

// Act
var handler = serviceProvider.GetKeyedServices<EventHandlerDelegateWrapper>("EventHandlerDelegate:ProviderReady");

// Assert
Assert.NotEmpty(handler);
Assert.Equal(eventHandler1, handler.ElementAt(0).EventHandlerDelegate);
Assert.Equal(eventHandler2, handler.ElementAt(1).EventHandlerDelegate);
}

[Fact]
public void AddHandler_SetsHandlerNameInOpenFeatureOptions()
{
Expand Down Expand Up @@ -353,6 +373,25 @@ public void AddHandler_WithoutName_AddsEventHandlerDelegateWrapperAsKeyedService
Assert.Equal(ServiceLifetime.Singleton, handler.Lifetime);
}

[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(null)]
public void AddHandler_WithEmptyName_AddsEventHandlerDelegateWrapperAsKeyedService(string? handlerName)
{
// Arrange
EventHandlerDelegate eventHandler = (eventDetails) => { };
_systemUnderTest.AddHandler(handlerName!, Constant.ProviderEventTypes.ProviderReady, eventHandler);

// Act
var handlers = _services.Where(s => s.ServiceType == typeof(EventHandlerDelegateWrapper)).ToList();
var handler = handlers.First();

// Assert
Assert.True(handler.IsKeyedService);
Assert.Equal(ServiceLifetime.Singleton, handler.Lifetime);
}

[Fact]
public void AddHandler_WithImplementationFactory_AddsEventHandlerDelegateWrapperAsKeyedService()
{
Expand Down