Skip to content

Commit 0b80c85

Browse files
authored
Additional lifecycle registration changes (#1410)
* Added service lifetime to Jobs client Signed-off-by: Whit Waldo <[email protected]> * Added service lifetime to messaging client Signed-off-by: Whit Waldo <[email protected]> * Added service lifetime to actors registration Signed-off-by: Whit Waldo <[email protected]> * Added unit tests for DaprClient Signed-off-by: Whit Waldo <[email protected]> * Minor naming tweaks Signed-off-by: Whit Waldo <[email protected]> * Removed invalid using Signed-off-by: Whit Waldo <[email protected]> * Added service lifetime tests for actors Signed-off-by: Whit Waldo <[email protected]> * Added unit tests for jobs client lifecycle registrations Signed-off-by: Whit Waldo <[email protected]> * Added unit tests for PubSub and lifecycle registration Signed-off-by: Whit Waldo <[email protected]> * Fixed missing registration dependency Signed-off-by: Whit Waldo <[email protected]> --------- Signed-off-by: Whit Waldo <[email protected]>
1 parent ef04cad commit 0b80c85

File tree

7 files changed

+352
-61
lines changed

7 files changed

+352
-61
lines changed

src/Dapr.Actors.AspNetCore/ActorsServiceCollectionExtensions.cs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,18 @@ public static class ActorsServiceCollectionExtensions
3434
/// </summary>
3535
/// <param name="services">The <see cref="IServiceCollection" />.</param>
3636
/// <param name="configure">A delegate used to configure actor options and register actor types.</param>
37-
public static void AddActors(this IServiceCollection? services, Action<ActorRuntimeOptions>? configure)
37+
/// <param name="lifetime">The lifetime of the registered services.</param>
38+
public static void AddActors(this IServiceCollection? services, Action<ActorRuntimeOptions>? configure, ServiceLifetime lifetime = ServiceLifetime.Singleton)
3839
{
3940
ArgumentNullException.ThrowIfNull(services, nameof(services));
4041

41-
// Routing and health checks are required dependencies.
42+
// Routing, health checks and logging are required dependencies.
4243
services.AddRouting();
4344
services.AddHealthChecks();
45+
services.AddLogging();
4446

45-
services.TryAddSingleton<ActorActivatorFactory, DependencyInjectionActorActivatorFactory>();
46-
services.TryAddSingleton<ActorRuntime>(s =>
47-
{
47+
var actorRuntimeRegistration = new Func<IServiceProvider, ActorRuntime>(s =>
48+
{
4849
var options = s.GetRequiredService<IOptions<ActorRuntimeOptions>>().Value;
4950
ConfigureActorOptions(s, options);
5051

@@ -53,11 +54,10 @@ public static void AddActors(this IServiceCollection? services, Action<ActorRunt
5354
var proxyFactory = s.GetRequiredService<IActorProxyFactory>();
5455
return new ActorRuntime(options, loggerFactory, activatorFactory, proxyFactory);
5556
});
56-
57-
services.TryAddSingleton<IActorProxyFactory>(s =>
57+
var proxyFactoryRegistration = new Func<IServiceProvider, IActorProxyFactory>(serviceProvider =>
5858
{
59-
var options = s.GetRequiredService<IOptions<ActorRuntimeOptions>>().Value;
60-
ConfigureActorOptions(s, options);
59+
var options = serviceProvider.GetRequiredService<IOptions<ActorRuntimeOptions>>().Value;
60+
ConfigureActorOptions(serviceProvider, options);
6161

6262
var factory = new ActorProxyFactory()
6363
{
@@ -72,6 +72,26 @@ public static void AddActors(this IServiceCollection? services, Action<ActorRunt
7272
return factory;
7373
});
7474

75+
switch (lifetime)
76+
{
77+
case ServiceLifetime.Scoped:
78+
services.TryAddScoped<ActorActivatorFactory, DependencyInjectionActorActivatorFactory>();
79+
services.TryAddScoped<ActorRuntime>(actorRuntimeRegistration);
80+
services.TryAddScoped<IActorProxyFactory>(proxyFactoryRegistration);
81+
break;
82+
case ServiceLifetime.Transient:
83+
services.TryAddTransient<ActorActivatorFactory, DependencyInjectionActorActivatorFactory>();
84+
services.TryAddTransient<ActorRuntime>(actorRuntimeRegistration);
85+
services.TryAddTransient<IActorProxyFactory>(proxyFactoryRegistration);
86+
break;
87+
default:
88+
case ServiceLifetime.Singleton:
89+
services.TryAddSingleton<ActorActivatorFactory, DependencyInjectionActorActivatorFactory>();
90+
services.TryAddSingleton<ActorRuntime>(actorRuntimeRegistration);
91+
services.TryAddSingleton<IActorProxyFactory>(proxyFactoryRegistration);
92+
break;
93+
}
94+
7595
if (configure != null)
7696
{
7797
services.Configure<ActorRuntimeOptions>(configure);

src/Dapr.Jobs/Extensions/DaprJobsServiceCollectionExtensions.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,40 @@ public static class DaprJobsServiceCollectionExtensions
2626
/// </summary>
2727
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
2828
/// <param name="configure">Optionally allows greater configuration of the <see cref="DaprJobsClient"/>.</param>
29-
public static IServiceCollection AddDaprJobsClient(this IServiceCollection serviceCollection, Action<DaprJobsClientBuilder>? configure = null)
29+
/// <param name="lifetime">The lifetime of the registered services.</param>
30+
public static IServiceCollection AddDaprJobsClient(this IServiceCollection serviceCollection, Action<DaprJobsClientBuilder>? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton)
3031
{
3132
ArgumentNullException.ThrowIfNull(serviceCollection, nameof(serviceCollection));
3233

3334
//Register the IHttpClientFactory implementation
3435
serviceCollection.AddHttpClient();
3536

36-
serviceCollection.TryAddSingleton(serviceProvider =>
37+
var registration = new Func<IServiceProvider, DaprJobsClient>(serviceProvider =>
3738
{
3839
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
3940

4041
var builder = new DaprJobsClientBuilder();
4142
builder.UseHttpClientFactory(httpClientFactory);
42-
43+
4344
configure?.Invoke(builder);
4445

4546
return builder.Build();
4647
});
4748

49+
switch (lifetime)
50+
{
51+
case ServiceLifetime.Scoped:
52+
serviceCollection.TryAddScoped(registration);
53+
break;
54+
case ServiceLifetime.Transient:
55+
serviceCollection.TryAddTransient(registration);
56+
break;
57+
case ServiceLifetime.Singleton:
58+
default:
59+
serviceCollection.TryAddSingleton(registration);
60+
break;
61+
}
62+
4863
return serviceCollection;
4964
}
5065

@@ -53,8 +68,9 @@ public static IServiceCollection AddDaprJobsClient(this IServiceCollection servi
5368
/// </summary>
5469
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
5570
/// <param name="configure">Optionally allows greater configuration of the <see cref="DaprJobsClient"/> using injected services.</param>
71+
/// <param name="lifetime">The lifetime of the registered services.</param>
5672
/// <returns></returns>
57-
public static IServiceCollection AddDaprJobsClient(this IServiceCollection serviceCollection, Action<IServiceProvider, DaprJobsClientBuilder>? configure)
73+
public static IServiceCollection AddDaprJobsClient(this IServiceCollection serviceCollection, Action<IServiceProvider, DaprJobsClientBuilder>? configure, ServiceLifetime lifetime = ServiceLifetime.Singleton)
5874
{
5975
ArgumentNullException.ThrowIfNull(serviceCollection, nameof(serviceCollection));
6076

src/Dapr.Messaging/PublishSubscribe/Extensions/PublishSubscribeServiceCollectionExtensions.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ public static class PublishSubscribeServiceCollectionExtensions
1313
/// </summary>
1414
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
1515
/// <param name="configure">Optionally allows greater configuration of the <see cref="DaprPublishSubscribeClient"/> using injected services.</param>
16+
/// <param name="lifetime">The lifetime of the registered services.</param>
1617
/// <returns></returns>
17-
public static IServiceCollection AddDaprPubSubClient(this IServiceCollection services, Action<IServiceProvider, DaprPublishSubscribeClientBuilder>? configure = null)
18+
public static IServiceCollection AddDaprPubSubClient(this IServiceCollection services, Action<IServiceProvider, DaprPublishSubscribeClientBuilder>? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton)
1819
{
1920
ArgumentNullException.ThrowIfNull(services, nameof(services));
2021

2122
//Register the IHttpClientFactory implementation
2223
services.AddHttpClient();
2324

24-
services.TryAddSingleton(serviceProvider =>
25+
var registration = new Func<IServiceProvider, DaprPublishSubscribeClient>(serviceProvider =>
2526
{
2627
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
2728

@@ -33,6 +34,20 @@ public static IServiceCollection AddDaprPubSubClient(this IServiceCollection ser
3334
return builder.Build();
3435
});
3536

37+
switch (lifetime)
38+
{
39+
case ServiceLifetime.Scoped:
40+
services.TryAddScoped(registration);
41+
break;
42+
case ServiceLifetime.Transient:
43+
services.TryAddTransient(registration);
44+
break;
45+
default:
46+
case ServiceLifetime.Singleton:
47+
services.TryAddSingleton(registration);
48+
break;
49+
}
50+
3651
return services;
3752
}
3853
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Xunit;
4+
5+
namespace Dapr.Actors.AspNetCore.Test;
6+
7+
public sealed class DaprActorServiceCollectionExtensionsTest
8+
{
9+
[Fact]
10+
public void RegisterActorsClient_ShouldRegisterSingleton_WhenLifetimeIsSingleton()
11+
{
12+
var services = new ServiceCollection();
13+
14+
services.AddActors(options => { }, ServiceLifetime.Singleton);
15+
var serviceProvider = services.BuildServiceProvider();
16+
17+
var daprClient1 = serviceProvider.GetService<Runtime.ActorRuntime>();
18+
var daprClient2 = serviceProvider.GetService<Runtime.ActorRuntime>();
19+
20+
Assert.NotNull(daprClient1);
21+
Assert.NotNull(daprClient2);
22+
23+
Assert.Same(daprClient1, daprClient2);
24+
}
25+
26+
[Fact]
27+
public async Task RegisterActorsClient_ShouldRegisterScoped_WhenLifetimeIsScoped()
28+
{
29+
var services = new ServiceCollection();
30+
31+
services.AddActors(options => { }, ServiceLifetime.Scoped);
32+
var serviceProvider = services.BuildServiceProvider();
33+
34+
await using var scope1 = serviceProvider.CreateAsyncScope();
35+
var daprClient1 = scope1.ServiceProvider.GetService<Runtime.ActorRuntime>();
36+
37+
await using var scope2 = serviceProvider.CreateAsyncScope();
38+
var daprClient2 = scope2.ServiceProvider.GetService<Runtime.ActorRuntime>();
39+
40+
Assert.NotNull(daprClient1);
41+
Assert.NotNull(daprClient2);
42+
Assert.NotSame(daprClient1, daprClient2);
43+
}
44+
45+
[Fact]
46+
public void RegisterActorsClient_ShouldRegisterTransient_WhenLifetimeIsTransient()
47+
{
48+
var services = new ServiceCollection();
49+
50+
services.AddActors(options => { }, ServiceLifetime.Transient);
51+
var serviceProvider = services.BuildServiceProvider();
52+
53+
var daprClient1 = serviceProvider.GetService<Runtime.ActorRuntime>();
54+
var daprClient2 = serviceProvider.GetService<Runtime.ActorRuntime>();
55+
56+
Assert.NotNull(daprClient1);
57+
Assert.NotNull(daprClient2);
58+
Assert.NotSame(daprClient1, daprClient2);
59+
}
60+
}

test/Dapr.AspNetCore.Test/DaprServiceCollectionExtensionsTest.cs

Lines changed: 97 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,67 +15,120 @@
1515

1616
using System;
1717
using System.Text.Json;
18+
using System.Threading.Tasks;
1819
using Dapr.Client;
1920
using Microsoft.Extensions.DependencyInjection;
2021
using Xunit;
2122

22-
namespace Dapr.AspNetCore.Test
23+
namespace Dapr.AspNetCore.Test;
24+
25+
public class DaprServiceCollectionExtensionsTest
2326
{
24-
public class DaprServiceCollectionExtensionsTest
27+
[Fact]
28+
public void AddDaprClient_RegistersDaprClientOnlyOnce()
2529
{
26-
[Fact]
27-
public void AddDaprClient_RegistersDaprClientOnlyOnce()
28-
{
29-
var services = new ServiceCollection();
30+
var services = new ServiceCollection();
3031

31-
var clientBuilder = new Action<DaprClientBuilder>(
32-
builder => builder.UseJsonSerializationOptions(
33-
new JsonSerializerOptions()
34-
{
35-
PropertyNameCaseInsensitive = false
36-
}
37-
)
38-
);
32+
var clientBuilder = new Action<DaprClientBuilder>(
33+
builder => builder.UseJsonSerializationOptions(
34+
new JsonSerializerOptions()
35+
{
36+
PropertyNameCaseInsensitive = false
37+
}
38+
)
39+
);
3940

40-
// register with JsonSerializerOptions.PropertyNameCaseInsensitive = true (default)
41-
services.AddDaprClient();
41+
// register with JsonSerializerOptions.PropertyNameCaseInsensitive = true (default)
42+
services.AddDaprClient();
4243

43-
// register with PropertyNameCaseInsensitive = false
44-
services.AddDaprClient(clientBuilder);
44+
// register with PropertyNameCaseInsensitive = false
45+
services.AddDaprClient(clientBuilder);
4546

46-
var serviceProvider = services.BuildServiceProvider();
47+
var serviceProvider = services.BuildServiceProvider();
4748

48-
DaprClientGrpc? daprClient = serviceProvider.GetService<DaprClient>() as DaprClientGrpc;
49+
DaprClientGrpc? daprClient = serviceProvider.GetService<DaprClient>() as DaprClientGrpc;
4950

50-
Assert.NotNull(daprClient);
51-
Assert.True(daprClient?.JsonSerializerOptions.PropertyNameCaseInsensitive);
52-
}
51+
Assert.NotNull(daprClient);
52+
Assert.True(daprClient?.JsonSerializerOptions.PropertyNameCaseInsensitive);
53+
}
5354

54-
[Fact]
55-
public void AddDaprClient_RegistersUsingDependencyFromIServiceProvider()
55+
[Fact]
56+
public void AddDaprClient_RegistersUsingDependencyFromIServiceProvider()
57+
{
58+
59+
var services = new ServiceCollection();
60+
services.AddSingleton<TestConfigurationProvider>();
61+
services.AddDaprClient((provider, builder) =>
5662
{
63+
var configProvider = provider.GetRequiredService<TestConfigurationProvider>();
64+
var caseSensitivity = configProvider.GetCaseSensitivity();
5765

58-
var services = new ServiceCollection();
59-
services.AddSingleton<TestConfigurationProvider>();
60-
services.AddDaprClient((provider, builder) =>
66+
builder.UseJsonSerializationOptions(new JsonSerializerOptions
6167
{
62-
var configProvider = provider.GetRequiredService<TestConfigurationProvider>();
63-
var caseSensitivity = configProvider.GetCaseSensitivity();
64-
65-
builder.UseJsonSerializationOptions(new JsonSerializerOptions
66-
{
67-
PropertyNameCaseInsensitive = caseSensitivity
68-
});
68+
PropertyNameCaseInsensitive = caseSensitivity
6969
});
70+
});
7071

71-
var serviceProvider = services.BuildServiceProvider();
72+
var serviceProvider = services.BuildServiceProvider();
7273

73-
DaprClientGrpc? client = serviceProvider.GetRequiredService<DaprClient>() as DaprClientGrpc;
74+
DaprClientGrpc? client = serviceProvider.GetRequiredService<DaprClient>() as DaprClientGrpc;
7475

75-
//Registers with case-insensitive as true by default, but we set as false above
76-
Assert.NotNull(client);
77-
Assert.False(client?.JsonSerializerOptions.PropertyNameCaseInsensitive);
78-
}
76+
//Registers with case-insensitive as true by default, but we set as false above
77+
Assert.NotNull(client);
78+
Assert.False(client?.JsonSerializerOptions.PropertyNameCaseInsensitive);
79+
}
80+
81+
[Fact]
82+
public void RegisterClient_ShouldRegisterSingleton_WhenLifetimeIsSingleton()
83+
{
84+
var services = new ServiceCollection();
85+
86+
services.AddDaprClient(options => { }, ServiceLifetime.Singleton);
87+
var serviceProvider = services.BuildServiceProvider();
88+
89+
var daprClient1 = serviceProvider.GetService<DaprClient>();
90+
var daprClient2 = serviceProvider.GetService<DaprClient>();
91+
92+
Assert.NotNull(daprClient1);
93+
Assert.NotNull(daprClient2);
94+
95+
Assert.Same(daprClient1, daprClient2);
96+
}
97+
98+
[Fact]
99+
public async Task RegisterDaprClient_ShouldRegisterScoped_WhenLifetimeIsScoped()
100+
{
101+
var services = new ServiceCollection();
102+
103+
services.AddDaprClient(options => { }, ServiceLifetime.Scoped);
104+
var serviceProvider = services.BuildServiceProvider();
105+
106+
await using var scope1 = serviceProvider.CreateAsyncScope();
107+
var daprClient1 = scope1.ServiceProvider.GetService<DaprClient>();
108+
109+
await using var scope2 = serviceProvider.CreateAsyncScope();
110+
var daprClient2 = scope2.ServiceProvider.GetService<DaprClient>();
111+
112+
Assert.NotNull(daprClient1);
113+
Assert.NotNull(daprClient2);
114+
Assert.NotSame(daprClient1, daprClient2);
115+
}
116+
117+
[Fact]
118+
public void RegisterDaprClient_ShouldRegisterTransient_WhenLifetimeIsTransient()
119+
{
120+
var services = new ServiceCollection();
121+
122+
services.AddDaprClient(options => { }, ServiceLifetime.Transient);
123+
var serviceProvider = services.BuildServiceProvider();
124+
125+
var daprClient1 = serviceProvider.GetService<DaprClient>();
126+
var daprClient2 = serviceProvider.GetService<DaprClient>();
127+
128+
Assert.NotNull(daprClient1);
129+
Assert.NotNull(daprClient2);
130+
Assert.NotSame(daprClient1, daprClient2);
131+
}
79132

80133

81134
#if NET8_0_OR_GREATER
@@ -96,9 +149,8 @@ public void AddDaprClient_WithKeyedServices()
96149
}
97150
#endif
98151

99-
private class TestConfigurationProvider
100-
{
101-
public bool GetCaseSensitivity() => false;
102-
}
152+
private class TestConfigurationProvider
153+
{
154+
public bool GetCaseSensitivity() => false;
103155
}
104156
}

0 commit comments

Comments
 (0)