From 8fe8b45fe86e257c3fdcf0aff38671addfd90f4a Mon Sep 17 00:00:00 2001 From: Bernhard Richter Date: Thu, 7 Nov 2024 15:00:26 +0100 Subject: [PATCH] 100% coverage and all tests passed for MS.DI --- .vscode/settings.json | 5 +- build/build.csx | 2 +- omnisharp.json | 2 +- ...soft.DependencyInjection.Benchmarks.csproj | 2 +- .../AsyncDisposableTests.cs | 6 - ...Microsoft.DependencyInjection.Tests.csproj | 2 +- .../LightInjectKeyedSpecificationTests.cs | 66 ++++++----- ...ghtInject.Microsoft.DependencyInjection.cs | 105 +++++++----------- ...nject.Microsoft.DependencyInjection.csproj | 9 +- 9 files changed, 92 insertions(+), 107 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index df328d7..2179b3f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "coverage-gutters.lcovname": "../build/Artifacts/TestCoverage/coverage.info" + "coverage-gutters.lcovname": "../build/Artifacts/TestCoverage/coverage.info", + "cSpell.words": [ + "Xunit" + ] } diff --git a/build/build.csx b/build/build.csx index d4d617f..86e06a5 100644 --- a/build/build.csx +++ b/build/build.csx @@ -1,4 +1,4 @@ -#load "nuget:Dotnet.Build, 0.22.0" +#load "nuget:Dotnet.Build, 0.23.0" #load "nuget:dotnet-steps, 0.0.2" [StepDescription("Runs the tests with test coverage")] diff --git a/omnisharp.json b/omnisharp.json index ca03952..6d18689 100644 --- a/omnisharp.json +++ b/omnisharp.json @@ -9,6 +9,6 @@ }, "script": { "enableScriptNuGetReferences": true, - "defaultTargetFramework": "net7.0" + "defaultTargetFramework": "net8.0" } } \ No newline at end of file diff --git a/src/LightInject.Microsoft.DependencyInjection.Benchmarks/LightInject.Microsoft.DependencyInjection.Benchmarks.csproj b/src/LightInject.Microsoft.DependencyInjection.Benchmarks/LightInject.Microsoft.DependencyInjection.Benchmarks.csproj index d1a8b17..f2e7134 100644 --- a/src/LightInject.Microsoft.DependencyInjection.Benchmarks/LightInject.Microsoft.DependencyInjection.Benchmarks.csproj +++ b/src/LightInject.Microsoft.DependencyInjection.Benchmarks/LightInject.Microsoft.DependencyInjection.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 false false diff --git a/src/LightInject.Microsoft.DependencyInjection.Tests/AsyncDisposableTests.cs b/src/LightInject.Microsoft.DependencyInjection.Tests/AsyncDisposableTests.cs index 87d6891..d040bea 100644 --- a/src/LightInject.Microsoft.DependencyInjection.Tests/AsyncDisposableTests.cs +++ b/src/LightInject.Microsoft.DependencyInjection.Tests/AsyncDisposableTests.cs @@ -47,12 +47,6 @@ public async Task ShouldDisposeAsyncDisposableFromRootScope() Assert.Contains(asyncDisposable, disposedObjects); Assert.Single(disposedObjects); } - - [Fact] - public async Task ShouldHandleDisposeAsyncOnServiceProvider() - { - - } } public class AsyncDisposable : IAsyncDisposable diff --git a/src/LightInject.Microsoft.DependencyInjection.Tests/LightInject.Microsoft.DependencyInjection.Tests.csproj b/src/LightInject.Microsoft.DependencyInjection.Tests/LightInject.Microsoft.DependencyInjection.Tests.csproj index d3a2005..5707ce1 100644 --- a/src/LightInject.Microsoft.DependencyInjection.Tests/LightInject.Microsoft.DependencyInjection.Tests.csproj +++ b/src/LightInject.Microsoft.DependencyInjection.Tests/LightInject.Microsoft.DependencyInjection.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 diff --git a/src/LightInject.Microsoft.DependencyInjection.Tests/LightInjectKeyedSpecificationTests.cs b/src/LightInject.Microsoft.DependencyInjection.Tests/LightInjectKeyedSpecificationTests.cs index 648a6d2..9840b8c 100644 --- a/src/LightInject.Microsoft.DependencyInjection.Tests/LightInjectKeyedSpecificationTests.cs +++ b/src/LightInject.Microsoft.DependencyInjection.Tests/LightInjectKeyedSpecificationTests.cs @@ -1,7 +1,9 @@ using System; +using System.Text; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Specification; using Xunit; +using Xunit.Sdk; namespace LightInject.Microsoft.DependencyInjection.Tests; @@ -12,7 +14,6 @@ protected override IServiceProvider CreateServiceProvider(IServiceCollection ser return serviceCollection.CreateLightInjectServiceProvider(new ContainerOptions() { EnableCurrentScope = false }); } - [Fact] public void ShouldHandleKeyedServiceWithEnumKey() { @@ -22,7 +23,6 @@ public void ShouldHandleKeyedServiceWithEnumKey() provider.GetKeyedService(Key.A); } - [Fact] public void ShouldHandleKeyedServiceWithStringServiceKey() { @@ -33,7 +33,7 @@ public void ShouldHandleKeyedServiceWithStringServiceKey() Assert.Equal("A", ((KeyedServiceWithStringServiceKey)instance).ServiceKey); } - [Fact] + [Fact] public void ShouldHandleRequiredKeyedServiceWithStringServiceKeyUsingFactory() { var serviceCollection = new ServiceCollection(); @@ -83,7 +83,33 @@ public void ShouldHandlePassingServiceKeyAsEnum() Assert.Equal(Key.A, ((KeyedServiceWithEnumServiceKey)instance).ServiceKey); } - + [Fact] + public void ShouldThrowExceptionWhenUsingInvalidKeyType() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient(new StringBuilder(), (sp, key) => new KeyedServiceWithEnumServiceKey((Key)key)); + var provider = CreateServiceProvider(serviceCollection); + Assert.Throws(() => provider.GetKeyedService(new StringBuilder())); + } + + [Fact] + public void ShouldHandleConvertibleType() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient(1L, (sp, key) => new KeyedServiceWithLongServiceKey((long)key)); + var provider = CreateServiceProvider(serviceCollection); + var instance = provider.GetKeyedService(1L); + Assert.Equal(1L, ((KeyedServiceWithLongServiceKey)instance).ServiceKey); + } + + [Fact] + public void ShouldHandlePassingNullAsServiceKeyForRequiredService() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient(Key.A); + var provider = CreateServiceProvider(serviceCollection); + Assert.Throws(() => provider.GetRequiredKeyedService(null)); + } } public interface IKeyedService @@ -95,38 +121,28 @@ public class KeyedService : IKeyedService } -public class KeyedServiceWithStringServiceKey : IKeyedService +public class KeyedServiceWithStringServiceKey([ServiceKey] string serviceKey) : IKeyedService { - public KeyedServiceWithStringServiceKey([ServiceKey] string serviceKey) - { - ServiceKey = serviceKey; - } - - public string ServiceKey { get; } + public string ServiceKey { get; } = serviceKey; } -public class KeyedServiceWithEnumServiceKey : IKeyedService +public class KeyedServiceWithEnumServiceKey([ServiceKey] Key serviceKey) : IKeyedService { - public KeyedServiceWithEnumServiceKey([ServiceKey] Key serviceKey) - { - ServiceKey = serviceKey; - } - - public Key ServiceKey { get; } + public Key ServiceKey { get; } = serviceKey; } -public class KeyServiceWithIntKey : IKeyedService +public class KeyServiceWithIntKey([ServiceKey] int serviceKey) : IKeyedService { - public KeyServiceWithIntKey([ServiceKey] int serviceKey) - { - ServiceKey = serviceKey; - } - - public int ServiceKey { get; } + public int ServiceKey { get; } = serviceKey; } +public class KeyedServiceWithLongServiceKey([ServiceKey] long serviceKey) : IKeyedService +{ + public long ServiceKey { get; } = serviceKey; +} + public enum Key { A, diff --git a/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.cs b/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.cs index 3e4e1f7..1ee0022 100644 --- a/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.cs +++ b/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.cs @@ -145,7 +145,6 @@ private static ServiceRegistration CreateServiceRegistration(ServiceDescriptor s } else { - if (serviceDescriptor.ImplementationFactory != null) { return CreateServiceRegistrationForFactoryDelegate(serviceDescriptor, rootScope); @@ -207,7 +206,7 @@ private static ServiceRegistration CreateServiceRegistrationForKeyedFactoryDeleg private static ServiceRegistration CreateBasicServiceRegistration(ServiceDescriptor serviceDescriptor, Scope rootScope) { - ServiceRegistration registration = new ServiceRegistration + ServiceRegistration registration = new () { Lifetime = ResolveLifetime(serviceDescriptor, rootScope), ServiceType = serviceDescriptor.ServiceType, @@ -246,6 +245,7 @@ private static bool NeedsTracking(ServiceDescriptor serviceDescriptor) { return true; } + if (serviceDescriptor.IsKeyedService) { if (serviceDescriptor.KeyedImplementationType != null && typeof(IDisposable).IsAssignableFrom(serviceDescriptor.KeyedImplementationType)) @@ -257,6 +257,7 @@ private static bool NeedsTracking(ServiceDescriptor serviceDescriptor) { return true; } + return false; } @@ -264,7 +265,7 @@ private static Delegate CreateFactoryDelegate(ServiceDescriptor serviceDescripto { var openGenericMethod = typeof(DependencyInjectionContainerExtensions).GetTypeInfo().GetDeclaredMethod("CreateTypedFactoryDelegate"); var closedGenericMethod = openGenericMethod.MakeGenericMethod(serviceDescriptor.ServiceType.UnderlyingSystemType); - return (Delegate)closedGenericMethod.Invoke(null, new object[] { serviceDescriptor }); + return (Delegate)closedGenericMethod.Invoke(null, [serviceDescriptor]); } #pragma warning disable IDE0051 @@ -276,8 +277,9 @@ private static Delegate CreateKeyedFactoryDelegate(ServiceDescriptor serviceDesc { var openGenericMethod = typeof(DependencyInjectionContainerExtensions).GetTypeInfo().GetDeclaredMethod("CreateTypedKeyedFactoryDelegate"); var closedGenericMethod = openGenericMethod.MakeGenericMethod(serviceDescriptor.ServiceType.UnderlyingSystemType); - return (Delegate)closedGenericMethod.Invoke(null, new object[] { serviceDescriptor }); + return (Delegate)closedGenericMethod.Invoke(null, [serviceDescriptor]); } + #pragma warning disable IDE0051 private static Func CreateTypedKeyedFactoryDelegate(ServiceDescriptor serviceDescriptor) { @@ -285,12 +287,7 @@ private static Func CreateTypedKeyedFactoryDelegate< { LightInjectServiceProvider.KeyedServiceTypeCache.TryGetValue(serviceDescriptor.ServiceType, out var serviceKeyType); object key; - if (serviceName == null) - { - key = string.Empty; - } - - else if (serviceKeyType.IsEnum) + if (serviceKeyType.IsEnum) { key = Enum.Parse(serviceKeyType, serviceName); } @@ -344,7 +341,7 @@ public static ContainerOptions WithMicrosoftSettings(this ContainerOptions optio /// /// The for which to create a clone. /// A clone of the given paramref name="containerOptions". - public static ContainerOptions Clone(this ContainerOptions containerOptions) => new ContainerOptions() + public static ContainerOptions Clone(this ContainerOptions containerOptions) => new () { DefaultServiceSelector = containerOptions.DefaultServiceSelector, EnableCurrentScope = containerOptions.EnableCurrentScope, @@ -418,25 +415,20 @@ public IServiceProvider CreateServiceProvider(IServiceContainer containerBuilder /// /// An that uses LightInject as the underlying container. /// +/// +/// Initializes a new instance of the class. +/// +/// The from which this service provider requests services. #if USE_ASYNCDISPOSABLE -internal class LightInjectServiceProvider : IServiceProvider, ISupportRequiredService, IKeyedServiceProvider, IDisposable, IAsyncDisposable +internal class LightInjectServiceProvider(Scope scope) : IServiceProvider, ISupportRequiredService, IKeyedServiceProvider, IDisposable, IAsyncDisposable #else -internal class LightInjectServiceProvider : IServiceProvider, ISupportRequiredService, IDisposable +internal class LightInjectServiceProvider(Scope scope) : IServiceProvider, ISupportRequiredService, IDisposable #endif { - private readonly Scope scope; - private bool isDisposed = false; public static ConcurrentDictionary KeyedServiceTypeCache { get; } = new ConcurrentDictionary(); - /// - /// Initializes a new instance of the class. - /// - /// The from which this service provider requests services. - public LightInjectServiceProvider(Scope scope) - => this.scope = scope; - public void Dispose() { if (isDisposed) @@ -465,7 +457,7 @@ public object GetKeyedService(Type serviceType, object serviceKey) { if (serviceKey != null) { - KeyedServiceTypeCache.TryAdd(serviceType, serviceKey.GetType()); + KeyedServiceTypeCache.AddOrUpdate(serviceType, serviceKey.GetType(), (t, _) => serviceKey.GetType()); } return scope.TryGetInstance(serviceType, serviceKey?.ToString()); @@ -475,8 +467,9 @@ public object GetRequiredKeyedService(Type serviceType, object serviceKey) { if (serviceKey != null) { - KeyedServiceTypeCache.TryAdd(serviceType, serviceKey.GetType()); + KeyedServiceTypeCache.AddOrUpdate(serviceType, serviceKey.GetType(), (t, _) => serviceKey.GetType()); } + return scope.GetInstance(serviceType, serviceKey?.ToString()); } #endif @@ -502,16 +495,12 @@ public object GetService(Type serviceType) /// /// An that uses an to create new scopes. /// -internal class LightInjectServiceScopeFactory : IServiceScopeFactory +/// +/// Initializes a new instance of the class. +/// +/// The used to create new scopes. +internal class LightInjectServiceScopeFactory(IServiceContainer container) : IServiceScopeFactory { - private readonly IServiceContainer container; - - /// - /// Initializes a new instance of the class. - /// - /// The used to create new scopes. - public LightInjectServiceScopeFactory(IServiceContainer container) - => this.container = container; /// public IServiceScope CreateScope() @@ -521,52 +510,40 @@ public IServiceScope CreateScope() /// /// An implementation that wraps a . /// +/// +/// Initializes a new instance of the class. +/// +/// The wrapped by this class. #if USE_ASYNCDISPOSABLE -internal class LightInjectServiceScope : IServiceScope, IAsyncDisposable +internal class LightInjectServiceScope(Scope scope) : IServiceScope, IAsyncDisposable #else -internal class LightInjectServiceScope : IServiceScope +internal class LightInjectServiceScope(Scope scope) : IServiceScope #endif { - private readonly Scope wrappedScope; - - /// - /// Initializes a new instance of the class. - /// - /// The wrapped by this class. - public LightInjectServiceScope(Scope scope) - { - wrappedScope = scope; - ServiceProvider = new LightInjectServiceProvider(scope); - } - - public IServiceProvider ServiceProvider { get; } + public IServiceProvider ServiceProvider { get; } = new LightInjectServiceProvider(scope); /// - public void Dispose() => wrappedScope.Dispose(); + public void Dispose() => scope.Dispose(); #if USE_ASYNCDISPOSABLE /// - public ValueTask DisposeAsync() => wrappedScope.DisposeAsync(); + public ValueTask DisposeAsync() => scope.DisposeAsync(); #endif } /// /// An implementation that makes it possible to mimic the notion of a root scope. /// +/// +/// Initializes a new instance of the class. +/// +/// The root . [LifeSpan(30)] -internal class PerRootScopeLifetime : ILifetime, ICloneableLifeTime +internal class PerRootScopeLifetime(Scope rootScope) : ILifetime, ICloneableLifeTime { - private readonly object syncRoot = new object(); - private readonly Scope rootScope; + private readonly object syncRoot = new(); private object instance; - /// - /// Initializes a new instance of the class. - /// - /// The root . - public PerRootScopeLifetime(Scope rootScope) - => this.rootScope = rootScope; - /// [ExcludeFromCodeCoverage] public object GetInstance(Func createInstance, Scope scope) @@ -611,19 +588,15 @@ private void RegisterForDisposal(object instance) } } -internal class LightInjectIsServiceProviderIsService : IServiceProviderIsKeyedService +internal class LightInjectIsServiceProviderIsService(Func canGetService) : IServiceProviderIsKeyedService { - private readonly Func canGetService; - - public LightInjectIsServiceProviderIsService(Func canGetService) - => this.canGetService = canGetService; - public bool IsKeyedService(Type serviceType, object serviceKey) { if (serviceType.IsGenericTypeDefinition) { return false; } + return canGetService(serviceType, serviceKey?.ToString() ?? string.Empty); } diff --git a/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.csproj b/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.csproj index 5551bc8..b231e6b 100644 --- a/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.csproj +++ b/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.csproj @@ -1,7 +1,7 @@  - net7.0;net6.0;netstandard2.0 + net8.0;netstandard2.0 Bernhard Richter Enables LightInject to be used as the service container in ASP.NET Core and Entity Framework 7 applications. Bernhard Richter @@ -20,11 +20,10 @@ - - - + \ No newline at end of file