Skip to content

Commit

Permalink
Additional lifecycle registration changes (#1410)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
WhitWaldo authored Nov 24, 2024
1 parent ef04cad commit 0b80c85
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 61 deletions.
38 changes: 29 additions & 9 deletions src/Dapr.Actors.AspNetCore/ActorsServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,18 @@ public static class ActorsServiceCollectionExtensions
/// </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);

Expand All @@ -53,11 +54,10 @@ public static void AddActors(this IServiceCollection? services, Action<ActorRunt
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()
{
Expand All @@ -72,6 +72,26 @@ public static void AddActors(this IServiceCollection? services, Action<ActorRunt
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);
Expand Down
24 changes: 20 additions & 4 deletions src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -53,8 +68,9 @@ public static IServiceCollection AddDaprJobsClient(this IServiceCollection servi
/// </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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>();

Expand All @@ -33,6 +34,20 @@ public static IServiceCollection AddDaprPubSubClient(this IServiceCollection ser
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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
142 changes: 97 additions & 45 deletions test/Dapr.AspNetCore.Test/DaprServiceCollectionExtensionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
)
);
var clientBuilder = new Action<DaprClientBuilder>(
builder => builder.UseJsonSerializationOptions(
new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = false
}
)
);

// register with JsonSerializerOptions.PropertyNameCaseInsensitive = true (default)
services.AddDaprClient();
// register with JsonSerializerOptions.PropertyNameCaseInsensitive = true (default)
services.AddDaprClient();

// register with PropertyNameCaseInsensitive = false
services.AddDaprClient(clientBuilder);
// register with PropertyNameCaseInsensitive = false
services.AddDaprClient(clientBuilder);

var serviceProvider = services.BuildServiceProvider();
var serviceProvider = services.BuildServiceProvider();

DaprClientGrpc? daprClient = serviceProvider.GetService<DaprClient>() as DaprClientGrpc;
DaprClientGrpc? daprClient = serviceProvider.GetService<DaprClient>() as DaprClientGrpc;

Assert.NotNull(daprClient);
Assert.True(daprClient?.JsonSerializerOptions.PropertyNameCaseInsensitive);
}
Assert.NotNull(daprClient);
Assert.True(daprClient?.JsonSerializerOptions.PropertyNameCaseInsensitive);
}

[Fact]
public void AddDaprClient_RegistersUsingDependencyFromIServiceProvider()
[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();

var services = new ServiceCollection();
services.AddSingleton<TestConfigurationProvider>();
services.AddDaprClient((provider, builder) =>
builder.UseJsonSerializationOptions(new JsonSerializerOptions
{
var configProvider = provider.GetRequiredService<TestConfigurationProvider>();
var caseSensitivity = configProvider.GetCaseSensitivity();

builder.UseJsonSerializationOptions(new JsonSerializerOptions
{
PropertyNameCaseInsensitive = caseSensitivity
});
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
Expand All @@ -96,9 +149,8 @@ public void AddDaprClient_WithKeyedServices()
}
#endif

private class TestConfigurationProvider
{
public bool GetCaseSensitivity() => false;
}
private class TestConfigurationProvider
{
public bool GetCaseSensitivity() => false;
}
}
Loading

0 comments on commit 0b80c85

Please sign in to comment.