Additional lifecycle registration changes (#1410)

* Added service lifetime to Jobs client

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added service lifetime to messaging client

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added service lifetime to actors registration

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added unit tests for DaprClient

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Minor naming tweaks

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Removed invalid using

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added service lifetime tests for actors

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added unit tests for jobs client lifecycle registrations

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added unit tests for PubSub and lifecycle registration

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed missing registration dependency

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
This commit is contained in:
Whit Waldo 2024-11-24 01:14:10 -07:00 committed by GitHub
parent ef04cad901
commit 0b80c853b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 360 additions and 69 deletions

View File

@ -34,17 +34,18 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" />.</param>
/// <param name="configure">A delegate used to configure actor options and register actor types.</param>
public static void AddActors(this IServiceCollection? services, Action<ActorRuntimeOptions>? configure)
/// <param name="lifetime">The lifetime of the registered services.</param>
public static void AddActors(this IServiceCollection? services, Action<ActorRuntimeOptions>? configure, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
ArgumentNullException.ThrowIfNull(services, nameof(services));
// Routing and health checks are required dependencies.
// Routing, health checks and logging are required dependencies.
services.AddRouting();
services.AddHealthChecks();
services.AddLogging();
services.TryAddSingleton<ActorActivatorFactory, DependencyInjectionActorActivatorFactory>();
services.TryAddSingleton<ActorRuntime>(s =>
{
var actorRuntimeRegistration = new Func<IServiceProvider, ActorRuntime>(s =>
{
var options = s.GetRequiredService<IOptions<ActorRuntimeOptions>>().Value;
ConfigureActorOptions(s, options);
@ -53,11 +54,10 @@ namespace Microsoft.Extensions.DependencyInjection
var proxyFactory = s.GetRequiredService<IActorProxyFactory>();
return new ActorRuntime(options, loggerFactory, activatorFactory, proxyFactory);
});
services.TryAddSingleton<IActorProxyFactory>(s =>
var proxyFactoryRegistration = new Func<IServiceProvider, IActorProxyFactory>(serviceProvider =>
{
var options = s.GetRequiredService<IOptions<ActorRuntimeOptions>>().Value;
ConfigureActorOptions(s, options);
var options = serviceProvider.GetRequiredService<IOptions<ActorRuntimeOptions>>().Value;
ConfigureActorOptions(serviceProvider, options);
var factory = new ActorProxyFactory()
{
@ -72,6 +72,26 @@ namespace Microsoft.Extensions.DependencyInjection
return factory;
});
switch (lifetime)
{
case ServiceLifetime.Scoped:
services.TryAddScoped<ActorActivatorFactory, DependencyInjectionActorActivatorFactory>();
services.TryAddScoped<ActorRuntime>(actorRuntimeRegistration);
services.TryAddScoped<IActorProxyFactory>(proxyFactoryRegistration);
break;
case ServiceLifetime.Transient:
services.TryAddTransient<ActorActivatorFactory, DependencyInjectionActorActivatorFactory>();
services.TryAddTransient<ActorRuntime>(actorRuntimeRegistration);
services.TryAddTransient<IActorProxyFactory>(proxyFactoryRegistration);
break;
default:
case ServiceLifetime.Singleton:
services.TryAddSingleton<ActorActivatorFactory, DependencyInjectionActorActivatorFactory>();
services.TryAddSingleton<ActorRuntime>(actorRuntimeRegistration);
services.TryAddSingleton<IActorProxyFactory>(proxyFactoryRegistration);
break;
}
if (configure != null)
{
services.Configure<ActorRuntimeOptions>(configure);

View File

@ -26,25 +26,40 @@ public static class DaprJobsServiceCollectionExtensions
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">Optionally allows greater configuration of the <see cref="DaprJobsClient"/>.</param>
public static IServiceCollection AddDaprJobsClient(this IServiceCollection serviceCollection, Action<DaprJobsClientBuilder>? configure = null)
/// <param name="lifetime">The lifetime of the registered services.</param>
public static IServiceCollection AddDaprJobsClient(this IServiceCollection serviceCollection, Action<DaprJobsClientBuilder>? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
ArgumentNullException.ThrowIfNull(serviceCollection, nameof(serviceCollection));
//Register the IHttpClientFactory implementation
serviceCollection.AddHttpClient();
serviceCollection.TryAddSingleton(serviceProvider =>
var registration = new Func<IServiceProvider, DaprJobsClient>(serviceProvider =>
{
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var builder = new DaprJobsClientBuilder();
builder.UseHttpClientFactory(httpClientFactory);
configure?.Invoke(builder);
return builder.Build();
});
switch (lifetime)
{
case ServiceLifetime.Scoped:
serviceCollection.TryAddScoped(registration);
break;
case ServiceLifetime.Transient:
serviceCollection.TryAddTransient(registration);
break;
case ServiceLifetime.Singleton:
default:
serviceCollection.TryAddSingleton(registration);
break;
}
return serviceCollection;
}
@ -53,8 +68,9 @@ public static class DaprJobsServiceCollectionExtensions
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">Optionally allows greater configuration of the <see cref="DaprJobsClient"/> using injected services.</param>
/// <param name="lifetime">The lifetime of the registered services.</param>
/// <returns></returns>
public static IServiceCollection AddDaprJobsClient(this IServiceCollection serviceCollection, Action<IServiceProvider, DaprJobsClientBuilder>? configure)
public static IServiceCollection AddDaprJobsClient(this IServiceCollection serviceCollection, Action<IServiceProvider, DaprJobsClientBuilder>? configure, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
ArgumentNullException.ThrowIfNull(serviceCollection, nameof(serviceCollection));

View File

@ -13,15 +13,16 @@ public static class PublishSubscribeServiceCollectionExtensions
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">Optionally allows greater configuration of the <see cref="DaprPublishSubscribeClient"/> using injected services.</param>
/// <param name="lifetime">The lifetime of the registered services.</param>
/// <returns></returns>
public static IServiceCollection AddDaprPubSubClient(this IServiceCollection services, Action<IServiceProvider, DaprPublishSubscribeClientBuilder>? configure = null)
public static IServiceCollection AddDaprPubSubClient(this IServiceCollection services, Action<IServiceProvider, DaprPublishSubscribeClientBuilder>? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
ArgumentNullException.ThrowIfNull(services, nameof(services));
//Register the IHttpClientFactory implementation
services.AddHttpClient();
services.TryAddSingleton(serviceProvider =>
var registration = new Func<IServiceProvider, DaprPublishSubscribeClient>(serviceProvider =>
{
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
@ -33,6 +34,20 @@ public static class PublishSubscribeServiceCollectionExtensions
return builder.Build();
});
switch (lifetime)
{
case ServiceLifetime.Scoped:
services.TryAddScoped(registration);
break;
case ServiceLifetime.Transient:
services.TryAddTransient(registration);
break;
default:
case ServiceLifetime.Singleton:
services.TryAddSingleton(registration);
break;
}
return services;
}
}

View File

@ -0,0 +1,60 @@
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Dapr.Actors.AspNetCore.Test;
public sealed class DaprActorServiceCollectionExtensionsTest
{
[Fact]
public void RegisterActorsClient_ShouldRegisterSingleton_WhenLifetimeIsSingleton()
{
var services = new ServiceCollection();
services.AddActors(options => { }, ServiceLifetime.Singleton);
var serviceProvider = services.BuildServiceProvider();
var daprClient1 = serviceProvider.GetService<Runtime.ActorRuntime>();
var daprClient2 = serviceProvider.GetService<Runtime.ActorRuntime>();
Assert.NotNull(daprClient1);
Assert.NotNull(daprClient2);
Assert.Same(daprClient1, daprClient2);
}
[Fact]
public async Task RegisterActorsClient_ShouldRegisterScoped_WhenLifetimeIsScoped()
{
var services = new ServiceCollection();
services.AddActors(options => { }, ServiceLifetime.Scoped);
var serviceProvider = services.BuildServiceProvider();
await using var scope1 = serviceProvider.CreateAsyncScope();
var daprClient1 = scope1.ServiceProvider.GetService<Runtime.ActorRuntime>();
await using var scope2 = serviceProvider.CreateAsyncScope();
var daprClient2 = scope2.ServiceProvider.GetService<Runtime.ActorRuntime>();
Assert.NotNull(daprClient1);
Assert.NotNull(daprClient2);
Assert.NotSame(daprClient1, daprClient2);
}
[Fact]
public void RegisterActorsClient_ShouldRegisterTransient_WhenLifetimeIsTransient()
{
var services = new ServiceCollection();
services.AddActors(options => { }, ServiceLifetime.Transient);
var serviceProvider = services.BuildServiceProvider();
var daprClient1 = serviceProvider.GetService<Runtime.ActorRuntime>();
var daprClient2 = serviceProvider.GetService<Runtime.ActorRuntime>();
Assert.NotNull(daprClient1);
Assert.NotNull(daprClient2);
Assert.NotSame(daprClient1, daprClient2);
}
}

View File

@ -15,67 +15,120 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Dapr.Client;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Dapr.AspNetCore.Test
namespace Dapr.AspNetCore.Test;
public class DaprServiceCollectionExtensionsTest
{
public class DaprServiceCollectionExtensionsTest
[Fact]
public void AddDaprClient_RegistersDaprClientOnlyOnce()
{
[Fact]
public void AddDaprClient_RegistersDaprClientOnlyOnce()
{
var services = new ServiceCollection();
var services = new ServiceCollection();
var clientBuilder = new Action<DaprClientBuilder>(
builder => builder.UseJsonSerializationOptions(
new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = false
}
)
);
// register with JsonSerializerOptions.PropertyNameCaseInsensitive = true (default)
services.AddDaprClient();
// register with PropertyNameCaseInsensitive = false
services.AddDaprClient(clientBuilder);
var serviceProvider = services.BuildServiceProvider();
DaprClientGrpc? daprClient = serviceProvider.GetService<DaprClient>() as DaprClientGrpc;
Assert.NotNull(daprClient);
Assert.True(daprClient?.JsonSerializerOptions.PropertyNameCaseInsensitive);
}
[Fact]
public void AddDaprClient_RegistersUsingDependencyFromIServiceProvider()
{
var services = new ServiceCollection();
services.AddSingleton<TestConfigurationProvider>();
services.AddDaprClient((provider, builder) =>
{
var configProvider = provider.GetRequiredService<TestConfigurationProvider>();
var caseSensitivity = configProvider.GetCaseSensitivity();
builder.UseJsonSerializationOptions(new JsonSerializerOptions
var clientBuilder = new Action<DaprClientBuilder>(
builder => builder.UseJsonSerializationOptions(
new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = caseSensitivity
});
PropertyNameCaseInsensitive = false
}
)
);
// register with JsonSerializerOptions.PropertyNameCaseInsensitive = true (default)
services.AddDaprClient();
// register with PropertyNameCaseInsensitive = false
services.AddDaprClient(clientBuilder);
var serviceProvider = services.BuildServiceProvider();
DaprClientGrpc? daprClient = serviceProvider.GetService<DaprClient>() as DaprClientGrpc;
Assert.NotNull(daprClient);
Assert.True(daprClient?.JsonSerializerOptions.PropertyNameCaseInsensitive);
}
[Fact]
public void AddDaprClient_RegistersUsingDependencyFromIServiceProvider()
{
var services = new ServiceCollection();
services.AddSingleton<TestConfigurationProvider>();
services.AddDaprClient((provider, builder) =>
{
var configProvider = provider.GetRequiredService<TestConfigurationProvider>();
var caseSensitivity = configProvider.GetCaseSensitivity();
builder.UseJsonSerializationOptions(new JsonSerializerOptions
{
PropertyNameCaseInsensitive = caseSensitivity
});
});
var serviceProvider = services.BuildServiceProvider();
var serviceProvider = services.BuildServiceProvider();
DaprClientGrpc? client = serviceProvider.GetRequiredService<DaprClient>() as DaprClientGrpc;
DaprClientGrpc? client = serviceProvider.GetRequiredService<DaprClient>() as DaprClientGrpc;
//Registers with case-insensitive as true by default, but we set as false above
Assert.NotNull(client);
Assert.False(client?.JsonSerializerOptions.PropertyNameCaseInsensitive);
}
//Registers with case-insensitive as true by default, but we set as false above
Assert.NotNull(client);
Assert.False(client?.JsonSerializerOptions.PropertyNameCaseInsensitive);
}
[Fact]
public void RegisterClient_ShouldRegisterSingleton_WhenLifetimeIsSingleton()
{
var services = new ServiceCollection();
services.AddDaprClient(options => { }, ServiceLifetime.Singleton);
var serviceProvider = services.BuildServiceProvider();
var daprClient1 = serviceProvider.GetService<DaprClient>();
var daprClient2 = serviceProvider.GetService<DaprClient>();
Assert.NotNull(daprClient1);
Assert.NotNull(daprClient2);
Assert.Same(daprClient1, daprClient2);
}
[Fact]
public async Task RegisterDaprClient_ShouldRegisterScoped_WhenLifetimeIsScoped()
{
var services = new ServiceCollection();
services.AddDaprClient(options => { }, ServiceLifetime.Scoped);
var serviceProvider = services.BuildServiceProvider();
await using var scope1 = serviceProvider.CreateAsyncScope();
var daprClient1 = scope1.ServiceProvider.GetService<DaprClient>();
await using var scope2 = serviceProvider.CreateAsyncScope();
var daprClient2 = scope2.ServiceProvider.GetService<DaprClient>();
Assert.NotNull(daprClient1);
Assert.NotNull(daprClient2);
Assert.NotSame(daprClient1, daprClient2);
}
[Fact]
public void RegisterDaprClient_ShouldRegisterTransient_WhenLifetimeIsTransient()
{
var services = new ServiceCollection();
services.AddDaprClient(options => { }, ServiceLifetime.Transient);
var serviceProvider = services.BuildServiceProvider();
var daprClient1 = serviceProvider.GetService<DaprClient>();
var daprClient2 = serviceProvider.GetService<DaprClient>();
Assert.NotNull(daprClient1);
Assert.NotNull(daprClient2);
Assert.NotSame(daprClient1, daprClient2);
}
#if NET8_0_OR_GREATER
@ -96,9 +149,8 @@ namespace Dapr.AspNetCore.Test
}
#endif
private class TestConfigurationProvider
{
public bool GetCaseSensitivity() => false;
}
private class TestConfigurationProvider
{
public bool GetCaseSensitivity() => false;
}
}

View File

@ -13,9 +13,9 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Dapr.Jobs.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Dapr.Jobs.Test.Extensions;
@ -77,6 +77,58 @@ public class DaprJobsServiceCollectionExtensionsTest
Assert.Equal("dapr-api-token", client.apiTokenHeader.Value.Key);
Assert.Equal("abcdef", client.apiTokenHeader.Value.Value);
}
[Fact]
public void RegisterJobsClient_ShouldRegisterSingleton_WhenLifetimeIsSingleton()
{
var services = new ServiceCollection();
services.AddDaprJobsClient(options => { }, ServiceLifetime.Singleton);
var serviceProvider = services.BuildServiceProvider();
var daprJobsClient1 = serviceProvider.GetService<DaprJobsClient>();
var daprJobsClient2 = serviceProvider.GetService<DaprJobsClient>();
Assert.NotNull(daprJobsClient1);
Assert.NotNull(daprJobsClient2);
Assert.Same(daprJobsClient1, daprJobsClient2);
}
[Fact]
public async Task RegisterJobsClient_ShouldRegisterScoped_WhenLifetimeIsScoped()
{
var services = new ServiceCollection();
services.AddDaprJobsClient(options => { }, ServiceLifetime.Scoped);
var serviceProvider = services.BuildServiceProvider();
await using var scope1 = serviceProvider.CreateAsyncScope();
var daprJobsClient1 = scope1.ServiceProvider.GetService<DaprJobsClient>();
await using var scope2 = serviceProvider.CreateAsyncScope();
var daprJobsClient2 = scope2.ServiceProvider.GetService<DaprJobsClient>();
Assert.NotNull(daprJobsClient1);
Assert.NotNull(daprJobsClient2);
Assert.NotSame(daprJobsClient1, daprJobsClient2);
}
[Fact]
public void RegisterJobsClient_ShouldRegisterTransient_WhenLifetimeIsTransient()
{
var services = new ServiceCollection();
services.AddDaprJobsClient(options => { }, ServiceLifetime.Transient);
var serviceProvider = services.BuildServiceProvider();
var daprJobsClient1 = serviceProvider.GetService<DaprJobsClient>();
var daprJobsClient2 = serviceProvider.GetService<DaprJobsClient>();
Assert.NotNull(daprJobsClient1);
Assert.NotNull(daprJobsClient2);
Assert.NotSame(daprJobsClient1, daprJobsClient2);
}
private class TestSecretRetriever
{

View File

@ -0,0 +1,76 @@
using Dapr.Messaging.PublishSubscribe;
using Dapr.Messaging.PublishSubscribe.Extensions;
using Microsoft.Extensions.DependencyInjection;
namespace Dapr.Messaging.Test.Extensions;
public sealed class PublishSubscribeServiceCollectionExtensionsTests
{
[Fact]
public void AddDaprPubSubClient_RegistersIHttpClientFactory()
{
var services = new ServiceCollection();
services.AddDaprPubSubClient();
var serviceProvider = services.BuildServiceProvider();
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
Assert.NotNull(httpClientFactory);
var daprPubSubClient = serviceProvider.GetService<DaprPublishSubscribeClient>();
Assert.NotNull(daprPubSubClient);
}
[Fact]
public void RegisterPubsubClient_ShouldRegisterSingleton_WhenLifetimeIsSingleton()
{
var services = new ServiceCollection();
services.AddDaprPubSubClient(lifetime: ServiceLifetime.Singleton);
var serviceProvider = services.BuildServiceProvider();
var daprPubSubClient1 = serviceProvider.GetService<DaprPublishSubscribeClient>();
var daprPubSubClient2 = serviceProvider.GetService<DaprPublishSubscribeClient>();
Assert.NotNull(daprPubSubClient1);
Assert.NotNull(daprPubSubClient2);
Assert.Same(daprPubSubClient1, daprPubSubClient2);
}
[Fact]
public async Task RegisterPubsubClient_ShouldRegisterScoped_WhenLifetimeIsScoped()
{
var services = new ServiceCollection();
services.AddDaprPubSubClient(lifetime: ServiceLifetime.Scoped);
var serviceProvider = services.BuildServiceProvider();
await using var scope1 = serviceProvider.CreateAsyncScope();
var daprPubSubClient1 = scope1.ServiceProvider.GetService<DaprPublishSubscribeClient>();
await using var scope2 = serviceProvider.CreateAsyncScope();
var daprPubSubClient2 = scope2.ServiceProvider.GetService<DaprPublishSubscribeClient>();
Assert.NotNull(daprPubSubClient1);
Assert.NotNull(daprPubSubClient2);
Assert.NotSame(daprPubSubClient1, daprPubSubClient2);
}
[Fact]
public void RegisterPubsubClient_ShouldRegisterTransient_WhenLifetimeIsTransient()
{
var services = new ServiceCollection();
services.AddDaprPubSubClient(lifetime: ServiceLifetime.Transient);
var serviceProvider = services.BuildServiceProvider();
var daprPubSubClient1 = serviceProvider.GetService<DaprPublishSubscribeClient>();
var daprPubSubClient2 = serviceProvider.GetService<DaprPublishSubscribeClient>();
Assert.NotNull(daprPubSubClient1);
Assert.NotNull(daprPubSubClient2);
Assert.NotSame(daprPubSubClient1, daprPubSubClient2);
}
}