diff --git a/Directory.Build.targets b/Directory.Build.targets index b1fa0d3557e..ea8fb542103 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -33,7 +33,7 @@ $(NoWarn);AD0001 - $(NoWarn);EXTEXP0001;EXTEXP0002;EXTEXP0003;EXTEXP0004;EXTEXP0005;EXTEXP0006;EXTEXP0007;EXTEXP0008;EXTEXP0009;EXTEXP0010;EXTEXP0011 + $(NoWarn);EXTEXP0001;EXTEXP0002;EXTEXP0003;EXTEXP0004;EXTEXP0005;EXTEXP0006;EXTEXP0007;EXTEXP0008;EXTEXP0009;EXTEXP0010;EXTEXP0011;EXTEXP0012 $(NoWarn);NU5104 diff --git a/docs/list-of-diagnostics.md b/docs/list-of-diagnostics.md index 09a85bdfba6..3e2f6cd3653 100644 --- a/docs/list-of-diagnostics.md +++ b/docs/list-of-diagnostics.md @@ -25,4 +25,5 @@ if desired. | `EXTEXP0008` | Resource monitoring experiments | | `EXTEXP0009` | Hosting experiments | | `EXTEXP0010` | Object pool experiments | - +| `EXTEXP0011` | Document database experiments | +| `EXTEXP0012` | Auto-activation experiments | diff --git a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.Keyed.cs b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.Keyed.cs new file mode 100644 index 00000000000..efb2f5c76fa --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.Keyed.cs @@ -0,0 +1,386 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.Shared.DiagnosticIds; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.DependencyInjection; + +public static partial class AutoActivationExtensions +{ + /// + /// Enforces keyed singleton activation at startup time rather then at runtime. + /// + /// The type of the service to activate. + /// The service collection containing the service. + /// An object used to uniquely identity the specific service. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static IServiceCollection ActivateKeyedSingleton( + this IServiceCollection services, + object? serviceKey) + where TService : class + { + _ = Throw.IfNull(services); + + _ = services + .AddHostedService() + .AddOptions() + .Configure(ao => + { + var constructed = typeof(IEnumerable); + if (ao.KeyedAutoActivators.Contains((constructed, serviceKey))) + { + return; + } + + if (ao.KeyedAutoActivators.Remove((typeof(TService), serviceKey))) + { + _ = ao.KeyedAutoActivators.Add((constructed, serviceKey)); + return; + } + + _ = ao.KeyedAutoActivators.Add((typeof(TService), serviceKey)); + }); + + return services; + } + + /// + /// Enforces keyed singleton activation at startup time rather then at runtime. + /// + /// The service collection to add the service to. + /// The type of the service to activate. + /// An object used to uniquely identity the specific service. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "Addressed with [DynamicallyAccessedMembers]")] + public static IServiceCollection ActivateKeyedSingleton( + this IServiceCollection services, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type serviceType, + object? serviceKey) + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(serviceType); + + _ = services + .AddHostedService() + .AddOptions() + .Configure(ao => + { + var constructed = typeof(IEnumerable<>).MakeGenericType(serviceType); + if (ao.KeyedAutoActivators.Contains((constructed, serviceKey))) + { + return; + } + + if (ao.KeyedAutoActivators.Remove((serviceType, serviceKey))) + { + _ = ao.KeyedAutoActivators.Add((constructed, serviceKey)); + return; + } + + _ = ao.KeyedAutoActivators.Add((serviceType, serviceKey)); + }); + + return services; + } + + /// + /// Adds an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// An object used to uniquely identity the specific service. + /// The factory that creates the service. + /// The type of the service to add. + /// The type of the implementation to use. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static IServiceCollection AddActivatedKeyedSingleton( + this IServiceCollection services, + object? serviceKey, + Func implementationFactory) + where TService : class + where TImplementation : class, TService + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(implementationFactory); + + return services + .AddKeyedSingleton(serviceKey, implementationFactory) + .ActivateKeyedSingleton(serviceKey); + } + + /// + /// Adds an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// An object used to uniquely identity the specific service. + /// The type of the service to add. + /// The type of the implementation to use. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static IServiceCollection AddActivatedKeyedSingleton( + this IServiceCollection services, + object? serviceKey) + where TService : class + where TImplementation : class, TService + { + return services + .AddKeyedSingleton(serviceKey) + .ActivateKeyedSingleton(serviceKey); + } + + /// + /// Adds an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// An object used to uniquely identity the specific service. + /// The factory that creates the service. + /// The type of the service to add. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static IServiceCollection AddActivatedKeyedSingleton( + this IServiceCollection services, + object? serviceKey, + Func implementationFactory) + where TService : class + { + return services + .AddKeyedSingleton(serviceKey, implementationFactory) + .ActivateKeyedSingleton(serviceKey); + } + + /// + /// Adds an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// An object used to uniquely identity the specific service. + /// The type of the service to add. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static IServiceCollection AddActivatedKeyedSingleton( + this IServiceCollection services, + object? serviceKey) + where TService : class + { + return services + .AddKeyedSingleton(serviceKey) + .ActivateKeyedSingleton(serviceKey); + } + + /// + /// Adds an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// The type of the service to register and the implementation to use. + /// An object used to uniquely identity the specific service. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static IServiceCollection AddActivatedKeyedSingleton( + this IServiceCollection services, + Type serviceType, + object? serviceKey) + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(serviceType); + + return services + .AddKeyedSingleton(serviceType, serviceKey) + .ActivateKeyedSingleton(serviceType, serviceKey); + } + + /// + /// Adds an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// The type of the service to register. + /// An object used to uniquely identity the specific service. + /// The factory that creates the service. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static IServiceCollection AddActivatedKeyedSingleton( + this IServiceCollection services, + Type serviceType, + object? serviceKey, + Func implementationFactory) + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(serviceType); + _ = Throw.IfNull(implementationFactory); + + return services + .AddKeyedSingleton(serviceType, serviceKey, implementationFactory) + .ActivateKeyedSingleton(serviceType, serviceKey); + } + + /// + /// Adds an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// The type of the service to register. + /// An object used to uniquely identity the specific service. + /// The implementation type of the service. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static IServiceCollection AddActivatedKeyedSingleton( + this IServiceCollection services, + Type serviceType, + object? serviceKey, + Type implementationType) + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(serviceType); + _ = Throw.IfNull(implementationType); + + return services + .AddKeyedSingleton(serviceType, serviceKey, implementationType) + .ActivateKeyedSingleton(serviceType, serviceKey); + } + + /// + /// Tries to add an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// The type of the service to register. + /// An object used to uniquely identity the specific service. + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static void TryAddActivatedKeyedSingleton( + this IServiceCollection services, + Type serviceType, + object? serviceKey) + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(serviceType); + + services.TryAddAndActivateKeyed(ServiceDescriptor.KeyedSingleton(serviceType, serviceKey, serviceType)); + } + + /// + /// Tries to add an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// The type of the service to register. + /// An object used to uniquely identity the specific service. + /// The implementation type of the service. + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static void TryAddActivatedKeyedSingleton( + this IServiceCollection services, + Type serviceType, + object? serviceKey, + Type implementationType) + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(serviceType); + _ = Throw.IfNull(implementationType); + + services.TryAddAndActivateKeyed(ServiceDescriptor.KeyedSingleton(serviceType, serviceKey, implementationType)); + } + + /// + /// Tries to add an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// The type of the service to register. + /// An object used to uniquely identity the specific service. + /// The factory that creates the service. + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static void TryAddActivatedKeyedSingleton( + this IServiceCollection services, + Type serviceType, + object? serviceKey, + Func implementationFactory) + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(serviceType); + _ = Throw.IfNull(implementationFactory); + + services.TryAddAndActivateKeyed(ServiceDescriptor.KeyedSingleton(serviceType, serviceKey, implementationFactory)); + } + + /// + /// Tries to add an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// An object used to uniquely identity the specific service. + /// The type of the service to add. + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static void TryAddActivatedKeyedSingleton( + this IServiceCollection services, + object? serviceKey) + where TService : class + { + _ = Throw.IfNull(services); + + services.TryAddAndActivateKeyed(ServiceDescriptor.KeyedSingleton(serviceKey)); + } + + /// + /// Tries to add an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// An object used to uniquely identity the specific service. + /// The type of the service to add. + /// The type of the implementation to use. + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static void TryAddActivatedKeyedSingleton( + this IServiceCollection services, + object? serviceKey) + where TService : class + where TImplementation : class, TService + { + _ = Throw.IfNull(services); + + services.TryAddAndActivateKeyed(ServiceDescriptor.KeyedSingleton(serviceKey)); + } + + /// + /// Tries to add an auto-activated keyed singleton service. + /// + /// The service collection to add the service to. + /// An object used to uniquely identity the specific service. + /// The factory that creates the service. + /// The type of the service to add. + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static void TryAddActivatedKeyedSingleton( + this IServiceCollection services, + object? serviceKey, + Func implementationFactory) + where TService : class + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(implementationFactory); + + services.TryAddAndActivateKeyed(ServiceDescriptor.KeyedSingleton(serviceKey, implementationFactory)); + } + + private static void TryAddAndActivateKeyed(this IServiceCollection services, ServiceDescriptor descriptor) + where TService : class + { + if (services.Any(d => d.ServiceType == descriptor.ServiceType && d.ServiceKey == descriptor.ServiceKey)) + { + return; + } + + services.Add(descriptor); + _ = services.ActivateKeyedSingleton(descriptor.ServiceKey); + } + + private static void TryAddAndActivateKeyed(this IServiceCollection services, ServiceDescriptor descriptor) + { + if (services.Any(d => d.ServiceType == descriptor.ServiceType && d.ServiceKey == descriptor.ServiceKey)) + { + return; + } + + services.Add(descriptor); + _ = services.ActivateKeyedSingleton(descriptor.ServiceType, descriptor.ServiceKey); + } +} diff --git a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.cs b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.cs index 8230443df71..3f276efd229 100644 --- a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.DependencyInjection; @@ -13,51 +14,90 @@ namespace Microsoft.Extensions.DependencyInjection; /// /// Extension methods for automatically activating singletons after application starts. /// -public static class AutoActivationExtensions +public static partial class AutoActivationExtensions { /// /// Enforces singleton activation at startup time rather then at runtime. /// - /// The type of the service to add. - /// The to add the service to. - /// A reference to this instance after the operation has completed. - public static IServiceCollection Activate(this IServiceCollection services) + /// The type of the service to activate. + /// The service collection containing the service. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + public static IServiceCollection ActivateSingleton(this IServiceCollection services) where TService : class { _ = Throw.IfNull(services); - _ = services.AddHostedService() - .AddOptions() - .Configure(ao => - { - var constructed = typeof(IEnumerable); - if (ao.AutoActivators.Contains(constructed)) - { - return; - } - - if (ao.AutoActivators.Remove(typeof(TService))) - { - _ = ao.AutoActivators.Add(constructed); - return; - } - - _ = ao.AutoActivators.Add(typeof(TService)); - }); + _ = services + .AddHostedService() + .AddOptions() + .Configure(ao => + { + var constructed = typeof(IEnumerable); + if (ao.AutoActivators.Contains(constructed)) + { + return; + } + + if (ao.AutoActivators.Remove(typeof(TService))) + { + _ = ao.AutoActivators.Add(constructed); + return; + } + + _ = ao.AutoActivators.Add(typeof(TService)); + }); + + return services; + } + + /// + /// Enforces singleton activation at startup time rather then at runtime. + /// + /// The service collection containing the service. + /// The type of the service to activate. + /// The value of . + [Experimental(diagnosticId: Experiments.AutoActivation, UrlFormat = Experiments.UrlFormat)] + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "Addressed with [DynamicallyAccessedMembers]")] + public static IServiceCollection ActivateSingleton(this IServiceCollection services, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type serviceType) + { + _ = Throw.IfNull(services); + _ = Throw.IfNull(serviceType); + + _ = services + .AddHostedService() + .AddOptions() + .Configure(ao => + { + var constructed = typeof(IEnumerable<>).MakeGenericType(serviceType); + if (ao.AutoActivators.Contains(constructed)) + { + return; + } + + if (ao.AutoActivators.Remove(serviceType)) + { + _ = ao.AutoActivators.Add(constructed); + return; + } + + _ = ao.AutoActivators.Add(serviceType); + }); return services; } /// - /// Adds an auto-activated singleton service of the type specified in TService with an implementation - /// type specified in TImplementation using the factory specified in implementationFactory - /// to the specified . + /// Adds an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The factory that creates the service. /// The type of the service to add. /// The type of the implementation to use. - /// A reference to this instance after the operation has completed. + /// The value of . public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Func implementationFactory) where TService : class where TImplementation : class, TService @@ -67,63 +107,61 @@ public static IServiceCollection AddActivatedSingleton(implementationFactory) - .Activate(); + .ActivateSingleton(); } /// - /// Adds an auto-activated singleton service of the type specified in TService with an implementation - /// type specified in TImplementation to the specified . + /// Adds an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to add. /// The type of the implementation to use. - /// A reference to this instance after the operation has completed. + /// The value of . public static IServiceCollection AddActivatedSingleton(this IServiceCollection services) where TService : class where TImplementation : class, TService { return services .AddSingleton() - .Activate(); + .ActivateSingleton(); } /// - /// Adds an auto-activated singleton service of the type specified in TService with a factory specified - /// in implementationFactory to the specified . + /// Adds an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The factory that creates the service. /// The type of the service to add. - /// A reference to this instance after the operation has completed. + /// The value of . public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Func implementationFactory) where TService : class { return services .AddSingleton(implementationFactory) - .Activate(); + .ActivateSingleton(); } /// - /// Adds an auto-activated singleton service of the type specified in TService to the specified . + /// Adds an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to add. - /// A reference to this instance after the operation has completed. + /// The value of . public static IServiceCollection AddActivatedSingleton(this IServiceCollection services) where TService : class { return services .AddSingleton() - .Activate(); + .ActivateSingleton(); } /// /// Adds an auto-activated singleton service of the type specified in serviceType to the specified /// . /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to register and the implementation to use. - /// A reference to this instance after the operation has completed. + /// The value of . public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType) { _ = Throw.IfNull(services); @@ -131,17 +169,16 @@ public static IServiceCollection AddActivatedSingleton(this IServiceCollection s return services .AddSingleton(serviceType) - .Activate(serviceType); + .ActivateSingleton(serviceType); } /// - /// Adds an auto-activated singleton service of the type specified in serviceType with a factory - /// specified in implementationFactory to the specified . + /// Adds an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to register. /// The factory that creates the service. - /// A reference to this instance after the operation has completed. + /// The value of . public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType, Func implementationFactory) { _ = Throw.IfNull(services); @@ -150,17 +187,16 @@ public static IServiceCollection AddActivatedSingleton(this IServiceCollection s return services .AddSingleton(serviceType, implementationFactory) - .Activate(serviceType); + .ActivateSingleton(serviceType); } /// - /// Adds an auto-activated singleton service of the type specified in serviceType with an implementation - /// of the type specified in implementationType to the specified . + /// Adds an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to register. /// The implementation type of the service. - /// A reference to this instance after the operation has completed. + /// The value of . public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType, Type implementationType) { _ = Throw.IfNull(services); @@ -169,14 +205,13 @@ public static IServiceCollection AddActivatedSingleton(this IServiceCollection s return services .AddSingleton(serviceType, implementationType) - .Activate(serviceType); + .ActivateSingleton(serviceType); } /// - /// Adds an auto-activated singleton service of the type specified in serviceType to the specified - /// if the service type hasn't already been registered. + /// Tries to add an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to register. public static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType) { @@ -187,11 +222,9 @@ public static void TryAddActivatedSingleton(this IServiceCollection services, Ty } /// - /// Adds an auto-activated singleton service of the type specified in serviceType with an implementation - /// of the type specified in implementationType to the specified - /// if the service type hasn't already been registered. + /// Tries to add an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to register. /// The implementation type of the service. public static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType, Type implementationType) @@ -204,11 +237,9 @@ public static void TryAddActivatedSingleton(this IServiceCollection services, Ty } /// - /// Adds an auto-activated singleton service of the type specified in serviceType with a factory - /// specified in implementationFactory to the specified - /// if the service type hasn't already been registered. + /// Tries to add an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to register. /// The factory that creates the service. public static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType, Func implementationFactory) @@ -221,11 +252,9 @@ public static void TryAddActivatedSingleton(this IServiceCollection services, Ty } /// - /// Adds an auto-activated singleton service of the type specified in TService - /// to the specified - /// if the service type hasn't already been registered. + /// Tries to add an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to add. public static void TryAddActivatedSingleton(this IServiceCollection services) where TService : class @@ -236,12 +265,9 @@ public static void TryAddActivatedSingleton(this IServiceCollection se } /// - /// Adds an auto-activated singleton service of the type specified in TService with an implementation - /// type specified in TImplementation using the factory specified in implementationFactory - /// to the specified - /// if the service type hasn't already been registered. + /// Tries to add an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The type of the service to add. /// The type of the implementation to use. public static void TryAddActivatedSingleton(this IServiceCollection services) @@ -254,11 +280,9 @@ public static void TryAddActivatedSingleton(this ISer } /// - /// Adds an auto-activated singleton service of the type specified in serviceType with a factory - /// specified in implementationFactory to the specified - /// if the service type hasn't already been registered. + /// Tries to add an auto-activated singleton service. /// - /// The to add the service to. + /// The service collection to add the service to. /// The factory that creates the service. /// The type of the service to add. public static void TryAddActivatedSingleton(this IServiceCollection services, Func implementationFactory) @@ -270,54 +294,26 @@ public static void TryAddActivatedSingleton(this IServiceCollection se services.TryAddAndActivate(ServiceDescriptor.Singleton(implementationFactory)); } - [UnconditionalSuppressMessage( - "Trimming", - "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", - Justification = "Addressed with [DynamicallyAccessedMembers]")] - internal static IServiceCollection Activate(this IServiceCollection services, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type serviceType) - { - _ = services.AddHostedService() - .AddOptions() - .Configure(ao => - { - var constructed = typeof(IEnumerable<>).MakeGenericType(serviceType); - if (ao.AutoActivators.Contains(constructed)) - { - return; - } - - if (ao.AutoActivators.Remove(serviceType)) - { - _ = ao.AutoActivators.Add(constructed); - return; - } - - _ = ao.AutoActivators.Add(serviceType); - }); - - return services; - } - private static void TryAddAndActivate(this IServiceCollection services, ServiceDescriptor descriptor) where TService : class { - if (services.Any(d => d.ServiceType == descriptor.ServiceType)) + if (services.Any(d => d.ServiceType == descriptor.ServiceType && d.ServiceKey == descriptor.ServiceKey)) { return; } services.Add(descriptor); - _ = services.Activate(); + _ = services.ActivateSingleton(); } private static void TryAddAndActivate(this IServiceCollection services, ServiceDescriptor descriptor) { - if (services.Any(d => d.ServiceType == descriptor.ServiceType)) + if (services.Any(d => d.ServiceType == descriptor.ServiceType && d.ServiceKey == descriptor.ServiceKey)) { return; } services.Add(descriptor); - _ = services.Activate(descriptor.ServiceType); + _ = services.ActivateSingleton(descriptor.ServiceType); } } diff --git a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationHostedService.cs b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationHostedService.cs index b33c748e44c..7f69d63fd18 100644 --- a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationHostedService.cs +++ b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationHostedService.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.Shared.Diagnostics; @@ -14,24 +12,27 @@ namespace Microsoft.Extensions.DependencyInjection; internal sealed class AutoActivationHostedService : IHostedService { - private readonly Type[] _autoActivators; + private readonly AutoActivatorOptions _options; private readonly IServiceProvider _provider; public AutoActivationHostedService(IServiceProvider provider, IOptions options) { _provider = provider; - var value = Throw.IfMemberNull(options, options.Value); - - _autoActivators = value.AutoActivators.ToArray(); + _options = Throw.IfMemberNull(options, options.Value); } public Task StartAsync(CancellationToken cancellationToken) { - foreach (var singleton in _autoActivators) + foreach (var singleton in _options.AutoActivators) { _ = _provider.GetRequiredService(singleton); } + foreach (var (serviceType, serviceKey) in _options.KeyedAutoActivators) + { + _ = _provider.GetRequiredKeyedService(serviceType, serviceKey); + } + return Task.CompletedTask; } diff --git a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivatorOptions.cs b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivatorOptions.cs index d1a2ed07df3..c2aa14f3177 100644 --- a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivatorOptions.cs +++ b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivatorOptions.cs @@ -9,4 +9,5 @@ namespace Microsoft.Extensions.DependencyInjection; internal sealed class AutoActivatorOptions { public HashSet AutoActivators { get; } = new(); + public HashSet<(Type serviceType, object? serviceKey)> KeyedAutoActivators { get; } = new(); } diff --git a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/Microsoft.Extensions.DependencyInjection.AutoActivation.csproj b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/Microsoft.Extensions.DependencyInjection.AutoActivation.csproj index 9b17c21e82b..94805a22c97 100644 --- a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/Microsoft.Extensions.DependencyInjection.AutoActivation.csproj +++ b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/Microsoft.Extensions.DependencyInjection.AutoActivation.csproj @@ -5,6 +5,10 @@ Fundamentals + + true + + normal 100 diff --git a/src/Shared/DiagnosticIds/Experiments.cs b/src/Shared/DiagnosticIds/Experiments.cs index db9f038280d..dd7cbeed828 100644 --- a/src/Shared/DiagnosticIds/Experiments.cs +++ b/src/Shared/DiagnosticIds/Experiments.cs @@ -10,7 +10,7 @@ namespace Microsoft.Shared.DiagnosticIds; /// /// /// When adding a new experiment, add a corresponding suppression to the root Directory.Build.targets file, and add a documentation entry to -/// docs/Experiments.md. +/// docs/list-of-diagnostics.md. /// internal static class Experiments { @@ -29,4 +29,5 @@ internal static class Experiments internal const string Hosting = "EXTEXP0009"; internal const string ObjectPool = "EXTEXP0010"; internal const string DocumentDb = "EXTEXP0011"; + internal const string AutoActivation = "EXTEXP0012"; } diff --git a/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AcceptanceTest.Keyed.cs b/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AcceptanceTest.Keyed.cs new file mode 100644 index 00000000000..ec45d077598 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AcceptanceTest.Keyed.cs @@ -0,0 +1,426 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection.Test.Fakes; +using Microsoft.Extensions.DependencyInjection.Test.Helpers; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.Testing; +using Xunit; + +namespace Microsoft.Extensions.DependencyInjection.Test; + +public partial class AcceptanceTest +{ + [Fact] + public async Task CanAddAndActivateKeyedSingletonAsync() + { + var instanceCount = new InstanceCreatingCounter(); + Assert.Equal(0, instanceCount.Counter); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(instanceCount) + .AddActivatedKeyedSingleton(serviceKey)) + .StartAsync(); + + Assert.Equal(1, instanceCount.Counter); + + var service = host.Services.GetKeyedService(serviceKey); + await host.StopAsync(); + + Assert.NotNull(service); + Assert.Equal(1, instanceCount.Counter); + } + + [Fact] + public async Task ShouldAddAndActivateOnlyOnce_WhenHasChildAsync_Keyed() + { + var parentCount = new InstanceCreatingCounter(); + var childCount = new InstanceCreatingCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services.AddSingleton(childCount) + .AddSingleton(parentCount) + .AddActivatedKeyedSingleton(typeof(IFakeService), serviceKey, typeof(FakeService)) + .AddActivatedKeyedSingleton(serviceKey, (sp, sk) => + { + return new FactoryService(sp.GetKeyedService(sk)!, sp.GetRequiredService()); + })) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, childCount.Counter); + Assert.Equal(1, parentCount.Counter); + } + + [Fact] + public async Task ShouldResolveComponentsAutomaticallyAsync_Keyed() + { + var parentCount = new InstanceCreatingCounter(); + var childCount = new InstanceCreatingCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(childCount) + .AddSingleton(parentCount) + .AddSingleton() + .AddActivatedKeyedSingleton(serviceKey)) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, childCount.Counter); + Assert.Equal(1, parentCount.Counter); + } + + [Fact] + public async Task CanActivateEnumerableAsync_Keyed() + { + var fakeServiceCount = new InstanceCreatingCounter(); + var fakeFactoryCount = new InstanceCreatingCounter(); + var anotherFakeServiceCount = new AnotherFakeServiceCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(fakeServiceCount) + .AddSingleton(fakeFactoryCount) + .AddSingleton(anotherFakeServiceCount) + .AddActivatedKeyedSingleton(typeof(IFakeService), serviceKey, typeof(FakeService)) + .AddActivatedKeyedSingleton(typeof(IFakeService), serviceKey, typeof(FakeOneMultipleService)) + .AddActivatedKeyedSingleton(typeof(IFakeService), serviceKey, typeof(AnotherFakeService))) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, fakeServiceCount.Counter); + Assert.Equal(1, fakeFactoryCount.Counter); + Assert.Equal(1, anotherFakeServiceCount.Counter); + + await host.StopAsync(); + } + + [Fact] + public async Task CanActivateEnumerableAsync_WithTypeArg_Keyed() + { + var fakeServiceCount = new InstanceCreatingCounter(); + var fakeFactoryCount = new InstanceCreatingCounter(); + var anotherFakeServiceCount = new AnotherFakeServiceCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(fakeServiceCount) + .AddSingleton(fakeFactoryCount) + .AddSingleton(anotherFakeServiceCount) + .AddActivatedKeyedSingleton(serviceKey) + .AddActivatedKeyedSingleton(serviceKey) + .AddActivatedKeyedSingleton(serviceKey)) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, fakeServiceCount.Counter); + Assert.Equal(1, fakeFactoryCount.Counter); + Assert.Equal(1, anotherFakeServiceCount.Counter); + + await host.StopAsync(); + } + + [Fact] + public async Task CanActivateOneServiceAsync_Keyed() + { + var fakeServiceCount = new InstanceCreatingCounter(); + var anotherFakeServiceCount = new AnotherFakeServiceCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(fakeServiceCount) + .AddSingleton(anotherFakeServiceCount) + .AddSingleton() + .AddActivatedKeyedSingleton(serviceKey)) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(0, fakeServiceCount.Counter); + Assert.Equal(1, anotherFakeServiceCount.Counter); + } + + [Fact] + public async Task ShouldActivateService_WhenTypeIsSpecifiedInTypeParameterTService_Keyed() + { + var counter = new InstanceCreatingCounter(); + var anotherFakeServiceCount = new AnotherFakeServiceCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(counter) + .AddSingleton(anotherFakeServiceCount) + .AddActivatedKeyedSingleton(serviceKey) + .AddActivatedKeyedSingleton(serviceKey, (sp, _) => new AnotherFakeService(sp.GetRequiredService()))) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, counter.Counter); + Assert.Equal(1, anotherFakeServiceCount.Counter); + } + + [Fact] + public async Task ShouldActivateService_WhenTypeIsSpecifiedInParameter_Keyed() + { + var counter = new InstanceCreatingCounter(); + var anotherFakeServiceCount = new AnotherFakeServiceCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(counter) + .AddSingleton(anotherFakeServiceCount) + .AddActivatedKeyedSingleton(typeof(FakeService), serviceKey) + .AddActivatedKeyedSingleton(typeof(AnotherFakeService), serviceKey, (sp, _) => new AnotherFakeService(sp.GetService()!))) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, counter.Counter); + Assert.Equal(1, anotherFakeServiceCount.Counter); + } + + [Fact] + public async Task TestStopHostAsync_Keyed() + { + var counter = new InstanceCreatingCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(counter) + .AddActivatedKeyedSingleton(serviceKey)) + .StartAsync(); + + Assert.Equal(1, counter.Counter); + await host.StopAsync(); + } + + [Fact] + public async Task ShouldNotActivate_WhenServiceOfTypeSpecifiedInTypeParameter_WasAlreadyAdded_Keyed() + { + var counter = new InstanceCreatingCounter(); + var anotherFakeServiceCount = new AnotherFakeServiceCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => + { + services + .AddSingleton(counter) + .AddSingleton(anotherFakeServiceCount) + .AddKeyedSingleton(serviceKey) + .AddKeyedSingleton(serviceKey); + services.TryAddActivatedKeyedSingleton(typeof(FakeService), serviceKey); + services.TryAddActivatedKeyedSingleton(typeof(AnotherFakeService), serviceKey, (sp, _) => new AnotherFakeService(sp.GetService()!)); + }) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(0, counter.Counter); + Assert.Equal(0, anotherFakeServiceCount.Counter); + } + + [Fact] + public async Task ShouldNotActivate_WhenServiceOfTypeSpecifiedInParameter_WasAlreadyAdded_Keyed() + { + var counter = new InstanceCreatingCounter(); + var anotherFakeServiceCount = new AnotherFakeServiceCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => + { + services + .AddSingleton(counter) + .AddSingleton(anotherFakeServiceCount) + .AddKeyedSingleton(serviceKey) + .AddKeyedSingleton(serviceKey); + services.TryAddActivatedKeyedSingleton(serviceKey); + services.TryAddActivatedKeyedSingleton(serviceKey, (sp, _) => new AnotherFakeService(sp.GetService()!)); + }) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(0, counter.Counter); + Assert.Equal(0, anotherFakeServiceCount.Counter); + } + + [Fact] + public async Task ShouldActivateOneSingleton_WhenTryAddIsCalled_WithTypeSpecifiedImplementation_Keyed() + { + var counter = new InstanceCreatingCounter(); + var anotherFakeServiceCount = new AnotherFakeServiceCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => + { + services + .AddSingleton(counter) + .AddSingleton(anotherFakeServiceCount); + services.TryAddActivatedKeyedSingleton(typeof(IFakeService), serviceKey, typeof(FakeService)); + services.TryAddActivatedKeyedSingleton(typeof(IFakeService), serviceKey, typeof(AnotherFakeService)); + }) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, counter.Counter); + Assert.Equal(0, anotherFakeServiceCount.Counter); + } + + [Fact] + public async Task ShouldActivateOneSingleton_WhenTryAddIsCalled_WithTypeSpecifiedImplementation_WithTypeArg_Keyed() + { + var counter = new InstanceCreatingCounter(); + var anotherFakeServiceCount = new AnotherFakeServiceCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => + { + services + .AddSingleton(counter) + .AddSingleton(anotherFakeServiceCount); + services.TryAddActivatedKeyedSingleton(serviceKey); + services.TryAddActivatedKeyedSingleton(serviceKey); + }) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, counter.Counter); + Assert.Equal(0, anotherFakeServiceCount.Counter); + } + + [Fact] + public async Task CanActivateSingletonAsync_Keyed() + { + var instanceCount = new InstanceCreatingCounter(); + Assert.Equal(0, instanceCount.Counter); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(instanceCount) + .AddKeyedSingleton(serviceKey) + .ActivateKeyedSingleton(serviceKey)) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, instanceCount.Counter); + + var service = host.Services.GetKeyedService(serviceKey); + + Assert.NotNull(service); + Assert.Equal(1, instanceCount.Counter); + } + + [Fact] + public async Task ActivationOfNotRegisteredType_ThrowsExceptionAsync_Keyed() + { + var serviceKey = new object(); + using var host = FakeHost.CreateBuilder() + .ConfigureServices(services => services.ActivateKeyedSingleton(serviceKey)) + .Build(); + + var exception = await Assert.ThrowsAsync(() => host.StartAsync()); + + Assert.Contains(typeof(IFakeService).FullName!, exception.Message); + } + + [Fact] + public async Task CanActivateEnumerableImplicitlyAddedAsync_Keyed() + { + var fakeServiceCount = new InstanceCreatingCounter(); + var fakeFactoryCount = new InstanceCreatingCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(fakeServiceCount) + .AddSingleton(fakeFactoryCount) + .AddKeyedSingleton(serviceKey).ActivateKeyedSingleton(serviceKey) + .AddKeyedSingleton(serviceKey).ActivateKeyedSingleton(serviceKey)) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, fakeServiceCount.Counter); + Assert.Equal(1, fakeFactoryCount.Counter); + } + + [Fact] + public async Task CanActivateEnumerableExplicitlyAddedAsync_Keyed() + { + var fakeServiceCount = new InstanceCreatingCounter(); + var fakeFactoryCount = new InstanceCreatingCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(fakeServiceCount) + .AddSingleton(fakeFactoryCount) + .AddKeyedSingleton(serviceKey) + .AddKeyedSingleton(serviceKey) + .ActivateKeyedSingleton>(serviceKey)) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, fakeServiceCount.Counter); + Assert.Equal(1, fakeFactoryCount.Counter); + } + + [Fact] + public async Task CanAutoActivateOpenGenericsAsEnumerableAsync_Keyed() + { + var fakeServiceCount = new InstanceCreatingCounter(); + var fakeOpenGenericCount = new InstanceCreatingCounter(); + + var serviceKey = new object(); + using var host = await new HostBuilder() + .ConfigureServices(services => services + .AddSingleton(fakeServiceCount) + .AddSingleton(fakeOpenGenericCount) + .AddTransient() + .AddKeyedSingleton(typeof(IFakeOpenGenericService), serviceKey, typeof(FakeService)) + .AddKeyedSingleton(typeof(IFakeOpenGenericService<>), serviceKey, typeof(FakeOpenGenericService<>)) + .ActivateKeyedSingleton>>(serviceKey) + .ActivateKeyedSingleton>(serviceKey)) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, fakeServiceCount.Counter); + Assert.Equal(2, fakeOpenGenericCount.Counter); + } + + [Fact] + public async Task CanAutoActivateClosedGenericsAsEnumerableAsync_Keyed() + { + var fakeServiceCount = new InstanceCreatingCounter(); + var fakeOpenGenericCount = new InstanceCreatingCounter(); + + var serviceKey = new object(); + using var host = await FakeHost.CreateBuilder() + .ConfigureServices(services => services + .AddSingleton(fakeServiceCount) + .AddSingleton(fakeOpenGenericCount) + .AddTransient() + .AddKeyedSingleton(typeof(IFakeOpenGenericService), serviceKey, typeof(FakeService)) + .AddKeyedSingleton, FakeOpenGenericService>(serviceKey) + .ActivateKeyedSingleton>>(serviceKey)) + .StartAsync(); + await host.StopAsync(); + + Assert.Equal(1, fakeServiceCount.Counter); + Assert.Equal(1, fakeOpenGenericCount.Counter); + } +} diff --git a/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AcceptanceTest.cs b/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AcceptanceTest.cs index 622e9c3957a..b5deaa71725 100644 --- a/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AcceptanceTest.cs +++ b/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AcceptanceTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.Extensions.DependencyInjection.Test; -public class AcceptanceTest +public partial class AcceptanceTest { [Fact] public async Task CanAddAndActivateSingletonAsync() @@ -26,6 +26,8 @@ public async Task CanAddAndActivateSingletonAsync() .AddActivatedSingleton()) .StartAsync(); + Assert.Equal(1, instanceCount.Counter); + var service = host.Services.GetService(); await host.StopAsync(); @@ -34,7 +36,7 @@ public async Task CanAddAndActivateSingletonAsync() } [Fact] - public async Task SouldIgnoreComponent_WhenNoAutoStartAsync() + public async Task ShouldIgnoreComponent_WhenNoAutoStartAsync() { var instanceCount = new InstanceCreatingCounter(); Assert.Equal(0, instanceCount.Counter); @@ -64,9 +66,9 @@ public async Task ShouldAddAndActivateOnlyOnce_WhenHasChildAsync() .ConfigureServices(services => services.AddSingleton(childCount) .AddSingleton(parentCount) .AddActivatedSingleton(typeof(IFakeService), typeof(FakeService)) - .AddActivatedSingleton(_ => + .AddActivatedSingleton(sp => { - return new FactoryService(_.GetService()!, _.GetService()!); + return new FactoryService(sp.GetService()!, sp.GetService()!); })) .StartAsync(); await host.StopAsync(); @@ -174,7 +176,7 @@ public async Task ShouldActivateService_WhenTypeIsSpecifiedInTypeParameterTServi .AddSingleton(counter) .AddSingleton(anotherFakeServiceCount) .AddActivatedSingleton() - .AddActivatedSingleton(_ => new AnotherFakeService(_.GetService()!))) + .AddActivatedSingleton(sp => new AnotherFakeService(sp.GetService()!))) .StartAsync(); await host.StopAsync(); @@ -193,7 +195,7 @@ public async Task ShouldActivateService_WhenTypeIsSpecifiedInParameter() .AddSingleton(counter) .AddSingleton(anotherFakeServiceCount) .AddActivatedSingleton(typeof(FakeService)) - .AddActivatedSingleton(typeof(AnotherFakeService), _ => new AnotherFakeService(_.GetService()!))) + .AddActivatedSingleton(typeof(AnotherFakeService), sp => new AnotherFakeService(sp.GetService()!))) .StartAsync(); await host.StopAsync(); @@ -231,7 +233,7 @@ public async Task ShouldNotActivate_WhenServiceOfTypeSpecifiedInTypeParameter_Wa .AddSingleton() .AddSingleton(); services.TryAddActivatedSingleton(typeof(FakeService)); - services.TryAddActivatedSingleton(typeof(AnotherFakeService), _ => new AnotherFakeService(_.GetService()!)); + services.TryAddActivatedSingleton(typeof(AnotherFakeService), sp => new AnotherFakeService(sp.GetService()!)); }) .StartAsync(); await host.StopAsync(); @@ -255,7 +257,7 @@ public async Task ShouldNotActivate_WhenServiceOfTypeSpecifiedInParameter_WasAlr .AddSingleton() .AddSingleton(); services.TryAddActivatedSingleton(); - services.TryAddActivatedSingleton(_ => new AnotherFakeService(_.GetService()!)); + services.TryAddActivatedSingleton(sp => new AnotherFakeService(sp.GetService()!)); }) .StartAsync(); await host.StopAsync(); @@ -308,7 +310,6 @@ public async Task ShouldActivateOneSingleton_WhenTryAddIsCalled_WithTypeSpecifie Assert.Equal(0, anotherFakeServiceCount.Counter); } - // ------------------------------------------------------------------------------ [Fact] public async Task CanActivateSingletonAsync() { @@ -319,7 +320,7 @@ public async Task CanActivateSingletonAsync() .ConfigureServices(services => services .AddSingleton(instanceCount) .AddSingleton() - .Activate()) + .ActivateSingleton()) .StartAsync(); await host.StopAsync(); @@ -335,7 +336,7 @@ public async Task CanActivateSingletonAsync() public async Task ActivationOfNotRegisteredType_ThrowsExceptionAsync() { using var host = FakeHost.CreateBuilder() - .ConfigureServices(services => services.Activate()) + .ConfigureServices(services => services.ActivateSingleton()) .Build(); var exception = await Assert.ThrowsAsync(() => host.StartAsync()); @@ -353,8 +354,8 @@ public async Task CanActivateEnumerableImplicitlyAddedAsync() .ConfigureServices(services => services .AddSingleton(fakeServiceCount) .AddSingleton(fakeFactoryCount) - .AddSingleton().Activate(typeof(IFakeService)) - .AddSingleton().Activate(typeof(IFakeService))) + .AddSingleton().ActivateSingleton(typeof(IFakeService)) + .AddSingleton().ActivateSingleton(typeof(IFakeService))) .StartAsync(); await host.StopAsync(); @@ -372,8 +373,8 @@ public async Task CanActivateEnumerableImplicitlyAddedAsync_WithTypeArg() .ConfigureServices(services => services .AddSingleton(fakeServiceCount) .AddSingleton(fakeFactoryCount) - .AddSingleton().Activate() - .AddSingleton().Activate()) + .AddSingleton().ActivateSingleton() + .AddSingleton().ActivateSingleton()) .StartAsync(); await host.StopAsync(); @@ -393,7 +394,7 @@ public async Task CanActivateEnumerableExplicitlyAddedAsync() .AddSingleton(fakeFactoryCount) .AddSingleton() .AddSingleton() - .Activate>()) + .ActivateSingleton>()) .StartAsync(); await host.StopAsync(); @@ -414,8 +415,8 @@ public async Task CanAutoActivateOpenGenericsAsEnumerableAsync() .AddTransient() .AddSingleton(typeof(IFakeOpenGenericService), typeof(FakeService)) .AddSingleton(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)) - .Activate>>() - .Activate>()) + .ActivateSingleton>>() + .ActivateSingleton>()) .StartAsync(); await host.StopAsync(); @@ -436,7 +437,7 @@ public async Task CanAutoActivateClosedGenericsAsEnumerableAsync() .AddTransient() .AddSingleton(typeof(IFakeOpenGenericService), typeof(FakeService)) .AddSingleton, FakeOpenGenericService>() - .Activate>>()) + .ActivateSingleton>>()) .StartAsync(); await host.StopAsync(); diff --git a/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AutoActivationExtensionsKeyedTests.cs b/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AutoActivationExtensionsKeyedTests.cs new file mode 100644 index 00000000000..a01af835609 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AutoActivationExtensionsKeyedTests.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Test.Fakes; +using Microsoft.Extensions.DependencyInjection.Test.Helpers; +using Xunit; + +namespace Microsoft.Extensions.DependencyInjection.Test; + +public class AutoActivationExtensionsKeyedTests +{ + [Fact] + public void AddActivatedKeyedSingleton_Throws_WhenArgumentsAreNull() + { + var serviceCollection = new ServiceCollection(); + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(null!, null, (_, _) => null!)); + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(serviceCollection, null, null!)); + + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(null!, null, (_, _) => null!)); + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(serviceCollection, null, null!)); + + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(null!, null)); + + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(null!, typeof(FakeService), null)); + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(serviceCollection, null!, null)); + + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(null!, null)); + + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(null!, typeof(FakeService), null, (_, _) => null!)); + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(serviceCollection, null!, null, (_, _) => null!)); + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(serviceCollection, typeof(FakeService), null, implementationFactory: null!)); + + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(null!, typeof(IFakeService), null, typeof(FakeService))); + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(serviceCollection, null!, null, typeof(FakeService))); + Assert.Throws(() => AutoActivationExtensions.AddActivatedKeyedSingleton(serviceCollection, typeof(IFakeService), null, implementationType: null!)); + } + + [Fact] + public void TryAddActivatedKeyedSingleton_Throws_WhenArgumentsAreNull() + { + var serviceCollection = new ServiceCollection(); + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(null!, typeof(FakeService), null)); + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(serviceCollection, null!, null)); + + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(null!, typeof(IFakeService), null, typeof(FakeService))); + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(serviceCollection, null!, null, typeof(FakeService))); + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(serviceCollection, typeof(IFakeService), null, implementationType: null!)); + + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(null!, typeof(FakeService), null, (_, _) => null!)); + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(serviceCollection, null!, null, (_, _) => null!)); + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(serviceCollection, typeof(FakeService), null, implementationFactory: null!)); + + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(null!, null)); + + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(null!, null)); + + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(null!, null, (_, _) => null!)); + Assert.Throws(() => AutoActivationExtensions.TryAddActivatedKeyedSingleton(serviceCollection, null, null!)); + } + + [Fact] + public void AutoActivate_Adds_OneHostedService() + { + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddSingleton(new InstanceCreatingCounter()); + serviceCollection.AddActivatedKeyedSingleton(null); + Assert.Equal(1, serviceCollection.Count(d => d.ImplementationType == typeof(AutoActivationHostedService))); + + serviceCollection.AddActivatedKeyedSingleton(null); + Assert.Equal(1, serviceCollection.Count(d => d.ImplementationType == typeof(AutoActivationHostedService))); + } +} diff --git a/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AutoActivationExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AutoActivationExtensionsTests.cs index 38079da9ff0..99c5566cf5e 100644 --- a/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AutoActivationExtensionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation.Tests/AutoActivationExtensionsTests.cs @@ -15,7 +15,7 @@ public class AutoActivationExtensionsTests [Fact] public void Activate_Throws_WhenArgumentsAreNull() { - Assert.Throws(() => AutoActivationExtensions.Activate(null!)); + Assert.Throws(() => AutoActivationExtensions.ActivateSingleton(null!)); } [Fact]