Skip to content

Commit ad309ad

Browse files
author
Martin Taillefer
committed
Add support for auto-activated keyed singletons
1 parent 6163ce2 commit ad309ad

11 files changed

Lines changed: 991 additions & 57 deletions

File tree

Directory.Build.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<NoWarn>$(NoWarn);AD0001</NoWarn>
3434

3535
<!-- Experimental warnings are for customers, not for this repo -->
36-
<NoWarn>$(NoWarn);EXTEXP0001;EXTEXP0002;EXTEXP0003;EXTEXP0004;EXTEXP0005;EXTEXP0006;EXTEXP0007;EXTEXP0008;EXTEXP0009;EXTEXP0010;EXTEXP0011</NoWarn>
36+
<NoWarn>$(NoWarn);EXTEXP0001;EXTEXP0002;EXTEXP0003;EXTEXP0004;EXTEXP0005;EXTEXP0006;EXTEXP0007;EXTEXP0008;EXTEXP0009;EXTEXP0010;EXTEXP0011;EXTEXP0012</NoWarn>
3737

3838
<!-- NU5104: A stable release of a package should not have a prerelease dependency -->
3939
<NoWarn>$(NoWarn);NU5104</NoWarn>

docs/list-of-diagnostics.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ if desired.
2525
| `EXTEXP0008` | Resource monitoring experiments |
2626
| `EXTEXP0009` | Hosting experiments |
2727
| `EXTEXP0010` | Object pool experiments |
28-
28+
| `EXTEXP0011` | Document database experiments |
29+
| `EXTEXP0012` | Auto-activation experiments |

src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.Keyed.cs

Lines changed: 405 additions & 0 deletions
Large diffs are not rendered by default.

src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.cs

Lines changed: 63 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,85 @@
66
using System.Diagnostics.CodeAnalysis;
77
using System.Linq;
88
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Shared.DiagnosticIds;
910
using Microsoft.Shared.Diagnostics;
1011

1112
namespace Microsoft.Extensions.DependencyInjection;
1213

1314
/// <summary>
1415
/// Extension methods for automatically activating singletons after application starts.
1516
/// </summary>
16-
public static class AutoActivationExtensions
17+
public static partial class AutoActivationExtensions
1718
{
1819
/// <summary>
1920
/// Enforces singleton activation at startup time rather then at runtime.
2021
/// </summary>
21-
/// <typeparam name="TService">The type of the service to add.</typeparam>
22+
/// <typeparam name="TService">The type of the service to activate.</typeparam>
2223
/// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
2324
/// <returns>A reference to this instance after the operation has completed.</returns>
2425
public static IServiceCollection Activate<TService>(this IServiceCollection services)
2526
where TService : class
2627
{
2728
_ = Throw.IfNull(services);
2829

29-
_ = services.AddHostedService<AutoActivationHostedService>()
30-
.AddOptions<AutoActivatorOptions>()
31-
.Configure(ao =>
32-
{
33-
var constructed = typeof(IEnumerable<TService>);
34-
if (ao.AutoActivators.Contains(constructed))
35-
{
36-
return;
37-
}
38-
39-
if (ao.AutoActivators.Remove(typeof(TService)))
40-
{
41-
_ = ao.AutoActivators.Add(constructed);
42-
return;
43-
}
44-
45-
_ = ao.AutoActivators.Add(typeof(TService));
46-
});
30+
_ = services
31+
.AddHostedService<AutoActivationHostedService>()
32+
.AddOptions<AutoActivatorOptions>()
33+
.Configure(ao =>
34+
{
35+
var constructed = typeof(IEnumerable<TService>);
36+
if (ao.AutoActivators.Contains(constructed))
37+
{
38+
return;
39+
}
40+
41+
if (ao.AutoActivators.Remove(typeof(TService)))
42+
{
43+
_ = ao.AutoActivators.Add(constructed);
44+
return;
45+
}
46+
47+
_ = ao.AutoActivators.Add(typeof(TService));
48+
});
49+
50+
return services;
51+
}
52+
53+
/// <summary>
54+
/// Enforces singleton activation at startup time rather then at runtime.
55+
/// </summary>
56+
/// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
57+
/// <param name="serviceType">The type of the service to activate.</param>
58+
/// <returns>A reference to this instance after the operation has completed.</returns>
59+
[Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)]
60+
[UnconditionalSuppressMessage(
61+
"Trimming",
62+
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
63+
Justification = "Addressed with [DynamicallyAccessedMembers]")]
64+
public static IServiceCollection Activate(this IServiceCollection services, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type serviceType)
65+
{
66+
_ = Throw.IfNull(services);
67+
_ = Throw.IfNull(serviceType);
68+
69+
_ = services
70+
.AddHostedService<AutoActivationHostedService>()
71+
.AddOptions<AutoActivatorOptions>()
72+
.Configure(ao =>
73+
{
74+
var constructed = typeof(IEnumerable<>).MakeGenericType(serviceType);
75+
if (ao.AutoActivators.Contains(constructed))
76+
{
77+
return;
78+
}
79+
80+
if (ao.AutoActivators.Remove(serviceType))
81+
{
82+
_ = ao.AutoActivators.Add(constructed);
83+
return;
84+
}
85+
86+
_ = ao.AutoActivators.Add(serviceType);
87+
});
4788

4889
return services;
4990
}
@@ -270,38 +311,10 @@ public static void TryAddActivatedSingleton<TService>(this IServiceCollection se
270311
services.TryAddAndActivate<TService>(ServiceDescriptor.Singleton<TService>(implementationFactory));
271312
}
272313

273-
[UnconditionalSuppressMessage(
274-
"Trimming",
275-
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
276-
Justification = "Addressed with [DynamicallyAccessedMembers]")]
277-
internal static IServiceCollection Activate(this IServiceCollection services, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type serviceType)
278-
{
279-
_ = services.AddHostedService<AutoActivationHostedService>()
280-
.AddOptions<AutoActivatorOptions>()
281-
.Configure(ao =>
282-
{
283-
var constructed = typeof(IEnumerable<>).MakeGenericType(serviceType);
284-
if (ao.AutoActivators.Contains(constructed))
285-
{
286-
return;
287-
}
288-
289-
if (ao.AutoActivators.Remove(serviceType))
290-
{
291-
_ = ao.AutoActivators.Add(constructed);
292-
return;
293-
}
294-
295-
_ = ao.AutoActivators.Add(serviceType);
296-
});
297-
298-
return services;
299-
}
300-
301314
private static void TryAddAndActivate<TService>(this IServiceCollection services, ServiceDescriptor descriptor)
302315
where TService : class
303316
{
304-
if (services.Any(d => d.ServiceType == descriptor.ServiceType))
317+
if (services.Any(d => d.ServiceType == descriptor.ServiceType && d.ServiceKey == descriptor.ServiceKey))
305318
{
306319
return;
307320
}
@@ -312,7 +325,7 @@ private static void TryAddAndActivate<TService>(this IServiceCollection services
312325

313326
private static void TryAddAndActivate(this IServiceCollection services, ServiceDescriptor descriptor)
314327
{
315-
if (services.Any(d => d.ServiceType == descriptor.ServiceType))
328+
if (services.Any(d => d.ServiceType == descriptor.ServiceType && d.ServiceKey == descriptor.ServiceKey))
316329
{
317330
return;
318331
}

src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationHostedService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace Microsoft.Extensions.DependencyInjection;
1515
internal sealed class AutoActivationHostedService : IHostedService
1616
{
1717
private readonly Type[] _autoActivators;
18+
private readonly (Type, object?)[] _keyedAutoActivators;
1819
private readonly IServiceProvider _provider;
1920

2021
public AutoActivationHostedService(IServiceProvider provider, IOptions<AutoActivatorOptions> options)
@@ -23,6 +24,7 @@ public AutoActivationHostedService(IServiceProvider provider, IOptions<AutoActiv
2324
var value = Throw.IfMemberNull(options, options.Value);
2425

2526
_autoActivators = value.AutoActivators.ToArray();
27+
_keyedAutoActivators = value.KeyedAutoActivators.ToArray();
2628
}
2729

2830
public Task StartAsync(CancellationToken cancellationToken)
@@ -32,6 +34,11 @@ public Task StartAsync(CancellationToken cancellationToken)
3234
_ = _provider.GetRequiredService(singleton);
3335
}
3436

37+
foreach (var (serviceType, serviceKey) in _keyedAutoActivators)
38+
{
39+
_ = _provider.GetRequiredKeyedService(serviceType, serviceKey);
40+
}
41+
3542
return Task.CompletedTask;
3643
}
3744

src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivatorOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ namespace Microsoft.Extensions.DependencyInjection;
99
internal sealed class AutoActivatorOptions
1010
{
1111
public HashSet<Type> AutoActivators { get; } = new();
12+
public HashSet<(Type serviceType, object? serviceKey)> KeyedAutoActivators { get; } = new();
1213
}

src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/Microsoft.Extensions.DependencyInjection.AutoActivation.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
<Workstream>Fundamentals</Workstream>
66
</PropertyGroup>
77

8+
<PropertyGroup>
9+
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
10+
</PropertyGroup>
11+
812
<PropertyGroup>
913
<Stage>normal</Stage>
1014
<MinCodeCoverage>100</MinCodeCoverage>

src/Shared/DiagnosticIds/Experiments.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.Shared.DiagnosticIds;
1010
/// </summary>
1111
/// <remarks>
1212
/// When adding a new experiment, add a corresponding suppression to the root <c>Directory.Build.targets</c> file, and add a documentation entry to
13-
/// <c>docs/Experiments.md</c>.
13+
/// <c>docs/list-of-diagnostics.md</c>.
1414
/// </remarks>
1515
internal static class Experiments
1616
{
@@ -29,4 +29,5 @@ internal static class Experiments
2929
internal const string Hosting = "EXTEXP0009";
3030
internal const string ObjectPool = "EXTEXP0010";
3131
internal const string DocumentDb = "EXTEXP0011";
32+
internal const string AutoActivation = "EXTEXP0012";
3233
}

0 commit comments

Comments
 (0)