diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a525a6e0..cb659bcf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,8 +12,7 @@ jobs: uses: actions/setup-dotnet@v2.0.0 with: dotnet-version: | - 6.0.x - 7.0.x + 8.0.x - name: Install dotnet-script run: dotnet tool install --global dotnet-script - name: Install dotnet-ilverify diff --git a/.vscode/settings.json b/.vscode/settings.json index 65cb25af..194ece8a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,9 +3,13 @@ "coverage-gutters.xmlname": "./CoverageResult/coverage.opencover.xml", "omnisharp.path": "latest", "cSpell.words": [ + "APIKEY", + "BUILDENVIRONMENT", "Callvirt", "Castclass", + "Cloneable", "Conv", + "decoratee", "Initobj", "Ldarg", "Ldelem", @@ -15,10 +19,12 @@ "Ldnull", "Ldstr", "MSIL", + "NETCOREAPP", "Newarr", "Newobj", "Stelem", "Stloc", + "Unkeyed", "Xunit" ], "dotnet-test-explorer.testArguments": "-c release -f netcoreapp2.0" diff --git a/build/build.csx b/build/build.csx index 42db60d0..c3af7a8d 100644 --- a/build/build.csx +++ b/build/build.csx @@ -11,14 +11,13 @@ using static ReleaseManagement; [StepDescription("Runs the tests with test coverage")] Step testcoverage = () => { - DotNet.TestWithCodeCoverage(Path.GetDirectoryName(BuildContext.TestProjects[0]), BuildContext.TestCoverageArtifactsFolder, BuildContext.CodeCoverageThreshold, "net7.0"); + DotNet.TestWithCodeCoverage(Path.GetDirectoryName(BuildContext.TestProjects[0]), BuildContext.TestCoverageArtifactsFolder, BuildContext.CodeCoverageThreshold, "net8.0"); }; [StepDescription("Runs all the tests for all target frameworks")] Step test = () => { Test(); - // DotNet.Test(); }; public static void Test() diff --git a/global.json b/global.json index f04f9d06..9df6ea19 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.100", + "version": "8.0.402", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/packlocal.sh b/packlocal.sh new file mode 100755 index 00000000..a01dce6f --- /dev/null +++ b/packlocal.sh @@ -0,0 +1 @@ +dotnet pack "src/LightInject/LightInject.csproj" --configuration Release --output "build/Artifacts/NuGet" /property:Version=$1 diff --git a/src/LightInject.Tests/ContainerMock.cs b/src/LightInject.Tests/ContainerMock.cs index ee752462..85c5f762 100644 --- a/src/LightInject.Tests/ContainerMock.cs +++ b/src/LightInject.Tests/ContainerMock.cs @@ -438,5 +438,20 @@ public object GetInstance(Type serviceType, Scope scope, string serviceName) { throw new NotImplementedException(); } + + public IServiceRegistry RegisterOrdered(ServiceRegistration[] serviceRegistrations) + { + throw new NotImplementedException(); + } + + public IServiceRegistry Register(Func factory, string serviceName) + { + throw new NotImplementedException(); + } + + public IServiceRegistry Register(Func factory, string serviceName, ILifetime lifetime) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/LightInject.Tests/DecoratorTests.cs b/src/LightInject.Tests/DecoratorTests.cs index 56aff0da..267d3c8d 100644 --- a/src/LightInject.Tests/DecoratorTests.cs +++ b/src/LightInject.Tests/DecoratorTests.cs @@ -350,7 +350,7 @@ public void GetInstance_LazyDecoratorFollowedByNonLazyDecorator_ReturnsDecorated [Fact] - public void GetInstance_SingletonInjectecIntoTwoDifferentClasses_DoesNotReapplyDecorators() + public void GetInstance_SingletonInjectedIntoTwoDifferentClasses_DoesNotReapplyDecorators() { BarDecorator.Instances = 0; var container = CreateContainer(); @@ -366,7 +366,7 @@ public void GetInstance_SingletonInjectecIntoTwoDifferentClasses_DoesNotReapplyD } [Fact] - public void GetAllInstances_SingletonInjectecIntoTwoDifferentClasses_DoesNotReapplyDecorators() + public void GetAllInstances_SingletonInjectedIntoTwoDifferentClasses_DoesNotReapplyDecorators() { BarDecorator.Instances = 0; var container = CreateContainer(); @@ -381,7 +381,7 @@ public void GetAllInstances_SingletonInjectecIntoTwoDifferentClasses_DoesNotReap } [Fact] - public void GetInstance_PerScopeInjectecIntoTwoDifferentClasses_DoesNotReapplyDecorators() + public void GetInstance_PerScopeInjectedIntoTwoDifferentClasses_DoesNotReapplyDecorators() { BarDecorator.Instances = 0; var container = CreateContainer(); @@ -399,7 +399,7 @@ public void GetInstance_PerScopeInjectecIntoTwoDifferentClasses_DoesNotReapplyDe } [Fact] - public void GetAllInstances_PerScopeInjectecIntoTwoDifferentClasses_DoesNotReapplyDecorators() + public void GetAllInstances_PerScopeInjectedIntoTwoDifferentClasses_DoesNotReapplyDecorators() { BarDecorator.Instances = 0; var container = CreateContainer(); @@ -492,7 +492,7 @@ public void GetInstance_ClassWithConstructorArguments_ReturnsDecoratedInstance() } [Fact] - public void GetInstance_ClassWithConstructorArgumentsAndLazyDecorator_CanCrTCreeateTarget() + public void GetInstance_ClassWithConstructorArgumentsAndLazyDecorator_CanCreateTarget() { var container = CreateContainer(); container.Register((factory, i) => new FooWithValueTypeDependency(i)); @@ -599,7 +599,7 @@ public void GetInstance_DecoratorImplementingDisposableRegisteredAsSingleton_Dis } [Fact] - public void GetInstance_ServicaeAsFactoryAndDecoratorImplementingDisposableRegisteredAsSingleton_DisposesDecorator() + public void GetInstance_ServiceAsFactoryAndDecoratorImplementingDisposableRegisteredAsSingleton_DisposesDecorator() { var container = CreateContainer(); container.Register(sf => new DisposableFoo(), new PerContainerLifetime()); diff --git a/src/LightInject.Tests/DisposableTests.cs b/src/LightInject.Tests/DisposableTests.cs index 9771e376..f1ee1082 100644 --- a/src/LightInject.Tests/DisposableTests.cs +++ b/src/LightInject.Tests/DisposableTests.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using LightInject.SampleLibrary; using Xunit; namespace LightInject.Tests @@ -35,7 +37,7 @@ public void Dispose_ServiceWithPerRequestLifetime_IsDisposed() } [Fact] - public void Dispose_Singeltons_DisposesInReverseOrderOfCreation() + public void Dispose_Singletons_DisposesInReverseOrderOfCreation() { var container = CreateContainer(); container.Register(new PerContainerLifetime()); @@ -59,7 +61,7 @@ public void Dispose_Singeltons_DisposesInReverseOrderOfCreation() } [Fact] - public void Dispose_SingeltonWithFactory_DisposesInReverseOrderOfCreation() + public void Dispose_SingletonWithFactory_DisposesInReverseOrderOfCreation() { var container = CreateContainer(); container.Register(new PerContainerLifetime()); @@ -317,49 +319,64 @@ public FakeDisposableCallbackOuterService(IFakeService singleService, IEnumerabl } } + /// + /// An implementation that makes it possible to mimic the notion of a root scope. + /// + [LifeSpan(30)] internal class PerRootScopeLifetime : ILifetime, ICloneableLifeTime { - private readonly ThreadSafeDictionary instances = new ThreadSafeDictionary(); - + private readonly object syncRoot = new object(); private readonly Scope rootScope; + private object instance; + /// + /// Initializes a new instance of the class. + /// + /// The root . public PerRootScopeLifetime(Scope rootScope) - { - this.rootScope = rootScope; - } + => this.rootScope = rootScope; + /// + [ExcludeFromCodeCoverage] public object GetInstance(Func createInstance, Scope scope) - { - return instances.GetOrAdd(rootScope, s => CreateScopedInstance(s, createInstance)); - } + => throw new NotImplementedException("Uses optimized non closing method"); + + /// + public ILifetime Clone() + => new PerRootScopeLifetime(rootScope); - private void RegisterForDisposal(Scope scope, object instance) + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable IDE0060 + public object GetInstance(GetInstanceDelegate createInstance, Scope scope, object[] arguments) { - if (instance is IDisposable disposable) +#pragma warning restore IDE0060 + if (instance != null) { - rootScope.TrackInstance(disposable); + return instance; } - } - private object CreateScopedInstance(Scope scope, Func createInstance) - { - rootScope.Completed += OnScopeCompleted; - var instance = createInstance(); + lock (syncRoot) + { + if (instance == null) + { + instance = createInstance(arguments, rootScope); + RegisterForDisposal(instance); + } + } - RegisterForDisposal(rootScope, instance); return instance; } - private void OnScopeCompleted(object sender, EventArgs e) - { - var scope = (Scope)sender; - scope.Completed -= OnScopeCompleted; - instances.TryRemove(scope, out object removedInstance); - } - - public ILifetime Clone() + private void RegisterForDisposal(object instance) { - return new PerRootScopeLifetime(rootScope); + if (instance is IDisposable disposable) + { + rootScope.TrackInstance(disposable); + } + else if (instance is IAsyncDisposable asyncDisposable) + { + rootScope.TrackInstance(asyncDisposable); + } } } } \ No newline at end of file diff --git a/src/LightInject.Tests/EmitterTests.cs b/src/LightInject.Tests/EmitterTests.cs index cfafe0cd..a4224bf4 100644 --- a/src/LightInject.Tests/EmitterTests.cs +++ b/src/LightInject.Tests/EmitterTests.cs @@ -3,7 +3,7 @@ namespace LightInject.Tests using System; using System.Reflection; using System.Reflection.Emit; - + using LightInject.SampleLibrary; using Xunit; @@ -103,7 +103,7 @@ public void Push_Seven_EmitsMostEffectiveInstruction() } [Fact] - public void Push_Eigth_EmitsMostEffectiveInstruction() + public void Push_Eighth_EmitsMostEffectiveInstruction() { var emitter = CreateEmitter(); @@ -652,7 +652,7 @@ public void Emit_InvalidOpCodeForString_ThrowsException() { var emitter = new Emitter(null, new Type[] { }); - Assert.Throws(() => emitter.Emit(OpCodes.Call, "somestring")); + Assert.Throws(() => emitter.Emit(OpCodes.Call, "SomeString")); } [Fact] @@ -679,6 +679,14 @@ public void ToString_InstructionOfT_ReturnsCodeAndArgumentAsString() Assert.Equal("ldarg 1", instruction.ToString(), StringComparer.OrdinalIgnoreCase); } + [Fact] + public void EmitMethodInfoShouldHaveServiceType() + { + var emitMethodInfo = new EmitMethodInfo(typeof(IFoo), (e) => { }, 0, false); + Assert.Equal(typeof(IFoo), emitMethodInfo.ServiceType); + + } + #if !USE_EXPRESSIONS private ILGenerator CreateDummyGenerator(Type[] parameterTypes) { diff --git a/src/LightInject.Tests/EnumerableTests.cs b/src/LightInject.Tests/EnumerableTests.cs new file mode 100644 index 00000000..f08aa9f5 --- /dev/null +++ b/src/LightInject.Tests/EnumerableTests.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using LightInject.SampleLibrary; +using Xunit; + +namespace LightInject.Tests; + +public class EnumerableTests +{ + [Fact] + public void Method() + { + var container = new ServiceContainer(); + container.RegisterSingleton>(f => new IFoo[] { new Foo(), new AnotherFoo() }); + var test = container.GetAllInstances(); + } +} \ No newline at end of file diff --git a/src/LightInject.Tests/KeyedMicrosoftTests.cs b/src/LightInject.Tests/KeyedMicrosoftTests.cs new file mode 100644 index 00000000..4a35870c --- /dev/null +++ b/src/LightInject.Tests/KeyedMicrosoftTests.cs @@ -0,0 +1,1005 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using LightInject.SampleLibrary; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Specification.Fakes; +using Xunit; +using Xunit.Sdk; + +namespace LightInject.Tests; + +public class KeyedMicrosoftTests : TestBase +{ + internal override IServiceContainer CreateContainer() + { + var container = new ServiceContainer(options => + { + options.EnableMicrosoftCompatibility = true; + options.EnableCurrentScope = false; + options.OptimizeForLargeObjectGraphs = false; + options.EnableOptionalArguments = true; + }) + { + AssemblyScanner = new NoOpAssemblyScanner() + }; + container.ConstructorDependencySelector = new AnnotatedConstructorDependencySelector(); + container.ConstructorSelector = new AnnotatedConstructorSelector(container.CanGetInstance); + return container; + } + + [Fact] + public void ResolveKeyedService() + { + var service1 = new Service(); + var service2 = new Service(); + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + container.RegisterInstance(service1, "service1"); + container.RegisterInstance(service2, "service2"); + + Assert.Null(container.TryGetInstance()); + Assert.Same(service1, container.GetInstance("service1")); + Assert.Same(service2, container.GetInstance("service2")); + } + + [Fact] + public void ResolveNullKeyedService() + { + var service1 = new Service(); + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + container.RegisterInstance(service1, null); + + var nonKeyed = container.TryGetInstance(); + var nullKey = container.TryGetInstance(null); + + Assert.Same(service1, nonKeyed); + Assert.Same(service1, nullKey); + } + + [Fact] + public void ResolveNonKeyedService() + { + var service1 = new Service(); + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + container.RegisterInstance(service1); + + var nonKeyed = container.GetInstance(); + var nullKey = container.GetInstance(null); + + Assert.Same(service1, nonKeyed); + Assert.Same(service1, nullKey); + } + + + [Fact] + public void ResolveKeyedOpenGenericService() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>), "my-service"); + container.Register(new PerRootScopeLifetime(rootScope)); + + // Act + var genericService = rootScope.GetInstance>("my-service"); + var singletonService = rootScope.GetInstance(); + + // Assert + Assert.Same(singletonService, genericService.Value); + } + + + [Fact] + public void ResolveKeyedServices() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var service1 = new Service(); + var service2 = new Service(); + var service3 = new Service(); + var service4 = new Service(); + + container.RegisterInstance(service1, "first-service"); + container.RegisterInstance(service2, "service"); + container.RegisterInstance(service3, "service"); + container.RegisterInstance(service4, "service"); + + // var serviceCollection = new ServiceCollection(); + // serviceCollection.AddKeyedSingleton("first-service", service1); + // serviceCollection.AddKeyedSingleton("service", service2); + // serviceCollection.AddKeyedSingleton("service", service3); + // serviceCollection.AddKeyedSingleton("service", service4); + + // var provider = CreateServiceProvider(serviceCollection); + + var firstSvc = rootScope.GetInstance>("first-service").ToList(); + Assert.Single(firstSvc); + Assert.Same(service1, firstSvc[0]); + + var services = rootScope.GetInstance>("service").ToList(); + Assert.Equal(new[] { service2, service3, service4 }, services); + } + + [Fact] + public void ResolveKeyedServicesAnyKey() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + + var service1 = new Service(); + var service2 = new Service(); + var service3 = new Service(); + var service4 = new Service(); + var service5 = new Service(); + var service6 = new Service(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton("first-service", service1); + serviceCollection.AddKeyedSingleton("service", service2); + serviceCollection.AddKeyedSingleton("service", service3); + serviceCollection.AddKeyedSingleton("service", service4); + serviceCollection.AddKeyedSingleton(null, service5); + serviceCollection.AddSingleton(service6); + + container.RegisterInstance(service1, "first-service"); + container.RegisterInstance(service2, "service"); + container.RegisterInstance(service3, "service"); + container.RegisterInstance(service4, "service"); + container.RegisterInstance(service5, null); + container.RegisterInstance(service6); + + var test = KeyedService.AnyKey.ToString(); + // var provider = CreateServiceProvider(serviceCollection); + + // // Return all services registered with a non null key + // //var allServices = provider.GetKeyedServices(KeyedService.AnyKey).ToList(); + var allServices = rootScope.GetInstance>(KeyedService.AnyKey.ToString()).ToList(); + Assert.Equal(4, allServices.Count); + Assert.Equal(new[] { service1, service2, service3, service4 }, allServices); + + // Check again (caching) + var allServices2 = rootScope.GetInstance>(KeyedService.AnyKey.ToString()).ToList(); + Assert.Equal(allServices, allServices2); + } + + [Fact] + public void ResolveKeyedServicesAnyKeyWithAnyKeyRegistration() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var service1 = new Service(); + var service2 = new Service(); + var service3 = new Service(); + var service4 = new Service(); + var service5 = new Service(); + var service6 = new Service(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient(KeyedService.AnyKey, (sp, key) => new Service()); + serviceCollection.AddKeyedSingleton("first-service", service1); + serviceCollection.AddKeyedSingleton("service", service2); + serviceCollection.AddKeyedSingleton("service", service3); + serviceCollection.AddKeyedSingleton("service", service4); + serviceCollection.AddKeyedSingleton(null, service5); + serviceCollection.AddSingleton(service6); + + container.RegisterTransient((factory) => new Service(), KeyedService.AnyKey.ToString()); + container.RegisterInstance(service1, "first-service"); + container.RegisterInstance(service2, "service"); + container.RegisterInstance(service3, "service"); + container.RegisterInstance(service4, "service"); + container.RegisterInstance(service5, null); + container.RegisterInstance(service6); + + + //var provider = CreateServiceProvider(serviceCollection); + + + // _ = provider.GetKeyedService("something-else"); + // _ = provider.GetKeyedService("something-else-again"); + + container.TryGetInstance("something-else"); + container.TryGetInstance("something-else-again"); + + // Return all services registered with a non null key, but not the one "created" with KeyedService.AnyKey + var allServices = rootScope.GetInstance>(KeyedService.AnyKey.ToString()).ToList(); + Assert.Equal(5, allServices.Count); + Assert.Equal(new[] { service1, service2, service3, service4 }, allServices.Skip(1)); + } + + [Fact] + public void ResolveKeyedGenericServices() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var service1 = new FakeService(); + var service2 = new FakeService(); + var service3 = new FakeService(); + var service4 = new FakeService(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton>("first-service", service1); + serviceCollection.AddKeyedSingleton>("service", service2); + serviceCollection.AddKeyedSingleton>("service", service3); + serviceCollection.AddKeyedSingleton>("service", service4); + + container.RegisterInstance>(service1, "first-service"); + container.RegisterInstance>(service2, "service"); + container.RegisterInstance>(service3, "service"); + container.RegisterInstance>(service4, "service"); + + + + //var provider = CreateServiceProvider(serviceCollection); + + //var firstSvc = provider.GetKeyedServices>("first-service").ToList(); + var firstSvc = rootScope.GetInstance>>("first-service").ToList(); + Assert.Single(firstSvc); + Assert.Same(service1, firstSvc[0]); + + var services = rootScope.GetInstance>>("service").ToList(); + Assert.Equal(new[] { service2, service3, service4 }, services); + } + + [Fact] + public void ResolveKeyedServiceSingletonInstance() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var service = new Service(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton("service1", service); + container.RegisterInstance(service, "service1"); + + + + + Assert.Null(rootScope.TryGetInstance()); + Assert.Same(service, rootScope.GetInstance("service1")); + Assert.Same(service, rootScope.GetInstance(typeof(IService), "service1")); + } + + [Fact] + public void ResolveKeyedServiceSingletonInstanceWithKeyInjection() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceKey = "this-is-my-service"; + var serviceCollection = new ServiceCollection(); + + + serviceCollection.AddKeyedSingleton(serviceKey); + + // Note: This has to be solved in the adapter layer + // Alternative is to look for the ServiceKeyAttribute rewrite the registration + // using a function factory. + //container.RegisterSingleton(serviceKey); + container.RegisterSingleton((factory) => new Service(serviceKey), serviceKey); + + // var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + var svc = rootScope.GetInstance(serviceKey); + Assert.NotNull(svc); + Assert.Equal(serviceKey, svc.ToString()); + } + + [Fact] + public void ResolveKeyedServiceSingletonInstanceWithAnyKey() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton(KeyedService.AnyKey); + container.Register(KeyedService.AnyKey.ToString(), new PerRootScopeLifetime(rootScope)); + + Assert.Null(rootScope.TryGetInstance()); + + var serviceKey1 = "some-key"; + var svc1 = rootScope.GetInstance(serviceKey1); + Assert.NotNull(svc1); + Assert.Equal(serviceKey1, svc1.ToString()); + + var serviceKey2 = "some-other-key"; + var svc2 = rootScope.GetInstance(serviceKey2); + Assert.NotNull(svc2); + Assert.Equal(serviceKey2, svc2.ToString()); + } + + [Fact] + public void ResolveKeyedServicesSingletonInstanceWithAnyKey() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + var service1 = new FakeService(); + var service2 = new FakeService(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton>(KeyedService.AnyKey, service1); + serviceCollection.AddKeyedSingleton>("some-key", service2); + container.Register>(sf => service1, KeyedService.AnyKey.ToString(), new PerRootScopeLifetime(rootScope)); + container.Register>(sf => service2, "some-key", new PerRootScopeLifetime(rootScope)); + + // container.RegisterInstance>(service1, KeyedService.AnyKey.ToString()); + // container.RegisterInstance>(service2, "some-key"); + + + // var provider = CreateServiceProvider(serviceCollection); + var services = rootScope.GetInstance>>("some-key").ToList(); + // var services = provider.GetKeyedServices>("some-key").ToList(); + Assert.Equal(new[] { service1, service2 }, services); + } + + [Fact] + public void ResolveKeyedServiceSingletonInstanceWithKeyedParameter() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton("service1"); + serviceCollection.AddKeyedSingleton("service2"); + serviceCollection.AddSingleton(); + container.Register("service1", new PerRootScopeLifetime(rootScope)); + container.Register("service2", new PerRootScopeLifetime(rootScope)); + container.Register(new PerRootScopeLifetime(rootScope)); + + + //var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + var svc = rootScope.GetInstance(); + Assert.NotNull(svc); + Assert.Equal("service1", svc.Service1.ToString()); + Assert.Equal("service2", svc.Service2.ToString()); + } + + + [Fact] + public void ResolveKeyedServiceWithKeyedParameter_MissingRegistration_SecondParameter() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddKeyedSingleton("service1"); + container.Register("service1", new PerRootScopeLifetime(rootScope)); + // We are missing the registration for "service2" here and OtherService requires it. + + serviceCollection.AddSingleton(); + container.Register(new PerRootScopeLifetime(rootScope)); + + // var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + Assert.Throws(() => rootScope.GetInstance()); + } + + [Fact] + public void ResolveKeyedServiceWithKeyedParameter_MissingRegistration_FirstParameter() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + + // We are not registering "service1" and "service1" keyed IService services and OtherService requires them. + + serviceCollection.AddSingleton(); + container.Register(new PerRootScopeLifetime(rootScope)); + + // var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + Assert.Throws(() => rootScope.GetInstance()); + } + + [Fact] + public void ResolveKeyedServiceWithKeyedParameter_MissingRegistrationButWithDefaults() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + + // We are not registering "service1" and "service1" keyed IService services and OtherServiceWithDefaultCtorArgs + // specifies them but has argument defaults if missing. + + serviceCollection.AddSingleton(); + container.Register(new PerRootScopeLifetime(rootScope)); + + //var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + Assert.NotNull(rootScope.GetInstance()); + } + + [Fact] + public void ResolveKeyedServiceWithKeyedParameter_MissingRegistrationButWithUnkeyedService() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + + // We are not registering "service1" and "service1" keyed IService services and OtherService requires them, + // but we are registering an unkeyed IService service which should not be injected into OtherService. + serviceCollection.AddSingleton(); + container.Register(new PerRootScopeLifetime(rootScope)); + + + serviceCollection.AddSingleton(); + container.Register(new PerRootScopeLifetime(rootScope)); + + // var provider = CreateServiceProvider(serviceCollection); + + Assert.NotNull(rootScope.GetInstance()); + Assert.Throws(() => rootScope.GetInstance()); + } + + [Fact] + public void CreateServiceWithKeyedParameter() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + serviceCollection.AddKeyedSingleton("service1"); + serviceCollection.AddKeyedSingleton("service2"); + + container.Register(new PerRootScopeLifetime(rootScope)); + container.Register("service1", new PerRootScopeLifetime(rootScope)); + container.Register("service2", new PerRootScopeLifetime(rootScope)); + + // var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + // var svc = ActivatorUtilities.CreateInstance(provider); + // Assert.NotNull(svc); + // Assert.Equal("service1", svc.Service1.ToString()); + // Assert.Equal("service2", svc.Service2.ToString()); + } + + + + // [Fact] + // public void ResolveKeyedServiceSingletonFactory() + // { + // var service = new Service(); + // var serviceCollection = new ServiceCollection(); + // serviceCollection.AddKeyedSingleton("service1", (sp, key) => service); + + // var provider = CreateServiceProvider(serviceCollection); + + // Assert.Null(provider.GetService()); + // Assert.Same(service, provider.GetKeyedService("service1")); + // Assert.Same(service, provider.GetKeyedService(typeof(IService), "service1")); + // } + + + [Fact] + public void ResolveKeyedServiceSingletonFactory() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var service = new Service(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton("service1", (sp, key) => service); + container.Register((factory, key) => service, "service1", new PerRootScopeLifetime(rootScope)); + + //var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + Assert.Same(service, rootScope.GetInstance("service1")); + Assert.Same(service, rootScope.GetInstance(typeof(IService), "service1")); + } + + [Fact] + public void ResolveKeyedServiceSingletonFactoryWithAnyKey() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton(KeyedService.AnyKey, (sp, key) => new Service((string)key)); + container.Register((factory, key) => new Service(key), KeyedService.AnyKey.ToString(), new PerRootScopeLifetime(rootScope)); + + Assert.Null(rootScope.TryGetInstance()); + + for (int i = 0; i < 3; i++) + { + var key = "service" + i; + var s1 = rootScope.GetInstance(key); + var s2 = rootScope.GetInstance(key); + Assert.Same(s1, s2); + Assert.Equal(key, s1.ToString()); + } + } + + [Fact] + public void ResolveKeyedServiceSingletonFactoryWithAnyKeyIgnoreWrongType() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient(KeyedService.AnyKey); + container.Register(KeyedService.AnyKey.ToString()); + //var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + Assert.NotNull(rootScope.GetInstance(87.ToString())); + Assert.ThrowsAny(() => rootScope.GetInstance(new object().ToString())); + Assert.ThrowsAny(() => rootScope.GetInstance(typeof(IService), new object().ToString())); + } + + [Fact] + public void ResolveKeyedServiceSingletonType() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton("service1"); + container.Register("service1", new PerRootScopeLifetime(rootScope)); + + // var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + Assert.Equal(typeof(Service), rootScope.GetInstance("service1")!.GetType()); + } + + [Fact] + public void ResolveKeyedServiceTransientFactory() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient("service1", (sp, key) => new Service(key as string)); + container.Register((factory, key) => new Service(key as string), "service1", null); + + //var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + var first = rootScope.GetInstance("service1"); + var second = rootScope.GetInstance("service1"); + Assert.NotSame(first, second); + Assert.Equal("service1", first.ToString()); + Assert.Equal("service1", second.ToString()); + } + + [Fact] + public void ResolveKeyedServiceTransientType() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient("service1"); + container.Register("service1"); + + //var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + var first = rootScope.GetInstance("service1"); + var second = rootScope.GetInstance("service1"); + Assert.NotSame(first, second); + } + + [Fact] + public void ResolveKeyedServiceTransientTypeWithAnyKey() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient(KeyedService.AnyKey); + container.Register(KeyedService.AnyKey.ToString()); + + // var provider = CreateServiceProvider(serviceCollection); + + Assert.Null(rootScope.TryGetInstance()); + var first = rootScope.GetInstance("service1"); + var second = rootScope.GetInstance("service1"); + Assert.NotSame(first, second); + } + + [Fact] + public void ResolveKeyedSingletonFromInjectedServiceProvider() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton("key"); + serviceCollection.AddSingleton(); + container.RegisterInstance(container); + container.Register("key", new PerRootScopeLifetime(rootScope)); + container.Register(new PerRootScopeLifetime(rootScope)); + + + // var provider = CreateServiceProvider(serviceCollection); + var accessor = rootScope.GetInstance(); + + Assert.Null(accessor.ServiceFactory.TryGetInstance()); + + var service1 = accessor.ServiceFactory.GetInstance("key"); + var service2 = accessor.ServiceFactory.GetInstance("key"); + + Assert.Same(service1, service2); + } + + [Fact] + public void ResolveKeyedTransientFromInjectedServiceProvider() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient("key"); + // serviceCollection.AddSingleton(); + container.RegisterInstance(container); + container.Register("key"); + container.Register(new PerRootScopeLifetime(rootScope)); + + // var provider = CreateServiceProvider(serviceCollection); + var accessor = rootScope.GetInstance(); + + Assert.Null(accessor.ServiceFactory.TryGetInstance()); + + var service1 = accessor.ServiceFactory.GetInstance("key"); + var service2 = accessor.ServiceFactory.GetInstance("key"); + + Assert.NotSame(service1, service2); + } + + [Fact] + public void ResolveKeyedSingletonFromScopeServiceProvider() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton("key"); + container.Register("key", new PerRootScopeLifetime(rootScope)); + + // var provider = CreateServiceProvider(serviceCollection); + // var scopeA = provider.GetRequiredService().CreateScope(); + // var scopeB = provider.GetRequiredService().CreateScope(); + + var scopeA = rootScope.BeginScope(); + var scopeB = rootScope.BeginScope(); + + + + Assert.Null(scopeA.TryGetInstance()); + Assert.Null(scopeB.TryGetInstance()); + + var serviceA1 = scopeA.GetInstance("key"); + var serviceA2 = scopeA.GetInstance("key"); + + var serviceB1 = scopeB.GetInstance("key"); + var serviceB2 = scopeB.GetInstance("key"); + + Assert.Same(serviceA1, serviceA2); + Assert.Same(serviceB1, serviceB2); + Assert.Same(serviceA1, serviceB1); + } + + [Fact] + public void ResolveKeyedScopedFromScopeServiceProvider() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedScoped("key"); + container.RegisterScoped("key"); + + // var provider = CreateServiceProvider(serviceCollection); + var scopeA = rootScope.BeginScope(); + var scopeB = rootScope.BeginScope(); + + Assert.Null(scopeA.TryGetInstance()); + Assert.Null(scopeB.TryGetInstance()); + + var serviceA1 = scopeA.GetInstance("key"); + var serviceA2 = scopeA.GetInstance("key"); + + var serviceB1 = scopeB.GetInstance("key"); + var serviceB2 = scopeB.GetInstance("key"); + + Assert.Same(serviceA1, serviceA2); + Assert.Same(serviceB1, serviceB2); + Assert.NotSame(serviceA1, serviceB1); + } + + [Fact] + public void ResolveKeyedTransientFromScopeServiceProvider() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient("key"); + container.Register("key"); + + + // var provider = CreateServiceProvider(serviceCollection); + var scopeA = rootScope.BeginScope(); + var scopeB = rootScope.BeginScope(); + + Assert.Null(scopeA.TryGetInstance()); + Assert.Null(scopeB.TryGetInstance()); + + var serviceA1 = scopeA.GetInstance("key"); + var serviceA2 = scopeA.GetInstance("key"); + + var serviceB1 = scopeB.GetInstance("key"); + var serviceB2 = scopeB.GetInstance("key"); + + Assert.NotSame(serviceA1, serviceA2); + Assert.NotSame(serviceB1, serviceB2); + Assert.NotSame(serviceA1, serviceB1); + } + + + [Fact] + public void ShouldHandleKeyedServiceWithEnumServiceKey() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.Register(Key.A.ToString(), new PerRootScopeLifetime(rootScope)); + var instance = rootScope.GetInstance(Key.A.ToString()); + Assert.Equal(Key.A, ((KeyedServiceWithEnumServiceKey)instance).ServiceKey); + } + + [Fact] + public void ShouldHandleKeyedServiceWithIntServiceKey() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.Register("42", new PerRootScopeLifetime(rootScope)); + var instance = rootScope.GetInstance("42"); + Assert.Equal(42, ((KeyServiceWithIntServiceKey)instance).ServiceKey); + } + + [Fact] + public void ShouldHandleRequestingServiceAsAnyKeyWithoutAnyRegistrations() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + var instance = rootScope.TryGetInstance(KeyedService.AnyKey.ToString()); + Assert.Null(instance); + } + + + + // [Fact] + // public void ResolveKeyedServiceThrowsIfNotSupported() + // { + // var provider = new NonKeyedServiceProvider(); + // var serviceKey = new object(); + + // Assert.Throws(() => provider.GetKeyedService(serviceKey)); + // Assert.Throws(() => provider.GetKeyedService(typeof(IService), serviceKey)); + // Assert.Throws(() => provider.GetKeyedServices(serviceKey)); + // Assert.Throws(() => provider.GetKeyedServices(typeof(IService), serviceKey)); + // Assert.Throws(() => provider.GetRequiredKeyedService(serviceKey)); + // Assert.Throws(() => provider.GetRequiredKeyedService(typeof(IService), serviceKey)); + // } + + + [Fact] + public void Test() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.Register("foo"); + container.Register("Bar"); + container.Register("AnotherBar"); + + var foo = rootScope.GetInstance("foo"); + } + + // [Fact] + // public void AnotherTest() + // { + // var serviceCollection = new ServiceCollection(); + // serviceCollection.AddKeyedSingleton(KeyedService.AnyKey, (sp, key) => new Service((string)key)); + + // Func factory = (sp, key) => new ServiceWithIntKey((int)key); + + // factory(null, 32); + // } + + + public interface IKeyedService + { + } + + public enum Key + { + A, + B + } + + public class KeyedServiceWithEnumServiceKey : IKeyedService + { + public KeyedServiceWithEnumServiceKey([ServiceKey] Key serviceKey) + { + ServiceKey = serviceKey; + } + + public Key ServiceKey { get; } + } + + public class KeyServiceWithIntServiceKey : IKeyedService + { + public KeyServiceWithIntServiceKey([ServiceKey] int serviceKey) + { + ServiceKey = serviceKey; + } + + public int ServiceKey { get; } + } + + + internal class ServiceFactoryAccessor + { + public ServiceFactoryAccessor(IServiceFactory serviceFactory) + { + ServiceFactory = serviceFactory; + } + + public IServiceFactory ServiceFactory { get; } + } + + + internal class OtherServiceWithDefaultCtorArgs + { + public OtherServiceWithDefaultCtorArgs( + [FromKeyedServices("service1")] IService service1 = null, + [FromKeyedServices("service2")] IService service2 = null) + { + Service1 = service1; + Service2 = service2; + } + + public IService Service1 { get; } + + public IService Service2 { get; } + } + + internal class ServiceWithIntKey : IService + { + private readonly int _id; + + public ServiceWithIntKey([ServiceKey] int id) => _id = id; + } + + + internal interface IService { } + + internal class Service : IService + { + private readonly string _id; + + public Service() => _id = Guid.NewGuid().ToString(); + + public Service([ServiceKey] string id) => _id = id; + + public override string ToString() => _id; + } + + internal class OtherService + { + public OtherService( + [FromKeyedServices("service1")] IService service1, + [FromKeyedServices("service2")] IService service2) + { + Service1 = service1; + Service2 = service2; + } + + public IService Service1 { get; } + + public IService Service2 { get; } + } + + +} + +internal class NoOpAssemblyScanner : IAssemblyScanner +{ + public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func lifetime, Func shouldRegister, Func serviceNameProvider) + { + + } + + public void Scan(Assembly assembly, IServiceRegistry serviceRegistry) + { + + } +} + + +/// +/// A that looks for the +/// to determine the name of service to be injected. +/// +public class AnnotatedConstructorDependencySelector : ConstructorDependencySelector +{ + /// + /// Selects the constructor dependencies for the given . + /// + /// The for which to select the constructor dependencies. + /// A list of instances that represents the constructor + /// dependencies for the given . + public override IEnumerable Execute(ConstructorInfo constructor) + { + var constructorDependencies = base.Execute(constructor).ToArray(); + foreach (var constructorDependency in constructorDependencies) + { + var injectAttribute = + (FromKeyedServicesAttribute) + constructorDependency.Parameter.GetCustomAttributes(typeof(FromKeyedServicesAttribute), true).FirstOrDefault(); + if (injectAttribute != null) + { + constructorDependency.ServiceName = injectAttribute.Key.ToString(); + } + } + + return constructorDependencies; + } +} + +/// +/// A implementation that uses information +/// from the to determine if a given service can be resolved. +/// +public class AnnotatedConstructorSelector : MostResolvableConstructorSelector +{ + /// + /// Initializes a new instance of the class. + /// + /// A function delegate that determines if a service type can be resolved. + public AnnotatedConstructorSelector(Func canGetInstance) + : base(canGetInstance) + { + } + + /// + /// Gets the service name based on the given . + /// + /// The for which to get the service name. + /// The name of the service for the given . + protected override string GetServiceName(ParameterInfo parameter) + { + var injectAttribute = + (FromKeyedServicesAttribute) + parameter.GetCustomAttributes(typeof(FromKeyedServicesAttribute), true).FirstOrDefault(); + + return injectAttribute != null ? injectAttribute.Key.ToString() : base.GetServiceName(parameter); + } +} + diff --git a/src/LightInject.Tests/KeyedMicrosoftTestsWithoutVariance.cs b/src/LightInject.Tests/KeyedMicrosoftTestsWithoutVariance.cs new file mode 100644 index 00000000..3eb3b249 --- /dev/null +++ b/src/LightInject.Tests/KeyedMicrosoftTestsWithoutVariance.cs @@ -0,0 +1,22 @@ +namespace LightInject.Tests; + +public class KeyedMicrosoftTestsWithoutVariance : KeyedMicrosoftTests +{ + internal override IServiceContainer CreateContainer() + { + var container = new ServiceContainer(options => + { + options.EnableMicrosoftCompatibility = true; + options.EnableCurrentScope = false; + options.OptimizeForLargeObjectGraphs = false; + options.EnableOptionalArguments = true; + options.EnableVariance = false; + }) + { + AssemblyScanner = new NoOpAssemblyScanner() + }; + container.ConstructorDependencySelector = new AnnotatedConstructorDependencySelector(); + container.ConstructorSelector = new AnnotatedConstructorSelector(container.CanGetInstance); + return container; + } +} \ No newline at end of file diff --git a/src/LightInject.Tests/LightInject.Tests.csproj b/src/LightInject.Tests/LightInject.Tests.csproj index 66bb94cd..2007d012 100644 --- a/src/LightInject.Tests/LightInject.Tests.csproj +++ b/src/LightInject.Tests/LightInject.Tests.csproj @@ -1,10 +1,10 @@  - net7.0 + net8.0 $(NoWarn);CS0579 - net6.0;netstandard2.0 - net6.0 + net8.0;netstandard2.0 + net8.0 @@ -13,7 +13,7 @@ - + USE_ASSEMBLY_VERIFICATION;USE_ASYNCDISPOSABLE @@ -32,6 +32,7 @@ + all runtime; build; native; contentfiles; analyzers diff --git a/src/LightInject.Tests/MicrosoftTests.cs b/src/LightInject.Tests/MicrosoftTests.cs new file mode 100644 index 00000000..08891849 --- /dev/null +++ b/src/LightInject.Tests/MicrosoftTests.cs @@ -0,0 +1,1048 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using LightInject.SampleLibrary; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Specification.Fakes; +using Xunit; +using static LightInject.Tests.KeyedMicrosoftTests; + +namespace LightInject.Tests; + +public class MicrosoftTests : TestBase +{ + internal override IServiceContainer CreateContainer() + { + var container = new ServiceContainer(options => + { + options.EnableMicrosoftCompatibility = true; + options.EnableCurrentScope = false; + options.OptimizeForLargeObjectGraphs = false; + options.EnableOptionalArguments = false; + options.EnableVariance = true; + }) + { + AssemblyScanner = new NoOpAssemblyScanner() + }; + container.ConstructorDependencySelector = new AnnotatedConstructorDependencySelector(); + container.ConstructorSelector = new AnnotatedConstructorSelector(container.CanGetInstance); + return container; + } + + [Fact] + public void ServicesRegisteredWithImplementationTypeCanBeResolved() + { + // Arrange + var container = CreateContainer(); + container.RegisterTransient(typeof(IFoo), typeof(Foo)); + + // Act + var service = container.GetInstance(); + + // Assert + Assert.NotNull(service); + Assert.IsType(service); + } + + + [Fact] + public void ServicesRegisteredWithImplementationType_ReturnDifferentInstancesPerResolution_ForTransientServices() + { + // Arrange + var container = CreateContainer(); + container.RegisterTransient(typeof(IFakeService), typeof(FakeService)); + + // Act + var service1 = container.GetInstance(); + var service2 = container.GetInstance(); + + // Assert + Assert.IsType(service1); + Assert.IsType(service2); + Assert.NotSame(service1, service2); + } + + [Fact] + public void ServicesRegisteredWithImplementationType_ReturnSameInstancesPerResolution_ForSingletons() + { + // Arrange + var container = CreateContainer(); + container.RegisterSingleton(typeof(IFakeService), typeof(FakeService)); + + // Act + var service1 = container.GetInstance(); + var service2 = container.GetInstance(); + + + // Assert + Assert.IsType(service1); + Assert.IsType(service2); + Assert.Same(service1, service2); + } + + [Fact] + public void ServiceInstanceCanBeResolved() + { + // Arrange + var instance = new FakeService(); + var container = CreateContainer(); + container.RegisterInstance(instance); + + // Act + var service = container.GetInstance(); + + // Assert + Assert.Same(instance, service); + } + + [Fact] + public void TransientServiceCanBeResolvedFromProvider() + { + // Arrange + var container = CreateContainer(); + container.RegisterTransient(typeof(IFakeService), typeof(FakeService)); + + // Act + var service1 = container.GetInstance(); + var service2 = container.GetInstance(); + + // Assert + Assert.NotNull(service1); + Assert.NotSame(service1, service2); + } + + + [Fact] + public void TransientServiceCanBeResolvedFromScope() + { + // Arrange + var container = CreateContainer(); + container.RegisterTransient(typeof(IFakeService), typeof(FakeService)); + + // Act + var service1 = container.GetInstance(); + + using (var scope = container.BeginScope()) + { + var scopedService1 = scope.GetInstance(); + var scopedService2 = scope.GetInstance(); + + // Assert + Assert.NotSame(service1, scopedService1); + Assert.NotSame(service1, scopedService2); + Assert.NotSame(scopedService1, scopedService2); + } + } + + [Theory] + [InlineData(ServiceLifetime.Scoped)] + [InlineData(ServiceLifetime.Transient)] + public void NonSingletonService_WithInjectedProvider_ResolvesScopeProvider(ServiceLifetime lifetime) + { + // Arrange + var container = CreateContainer(); + + container.RegisterScoped(); + container.RegisterScoped(f => f); + if (lifetime == ServiceLifetime.Scoped) + { + container.RegisterScoped(); + } + else + { + container.RegisterTransient(); + } + + // Act + IFakeService fakeServiceFromScope1 = null; + IFakeService otherFakeServiceFromScope1 = null; + IFakeService fakeServiceFromScope2 = null; + IFakeService otherFakeServiceFromScope2 = null; + + using (var scope1 = container.BeginScope()) + { + var serviceWithServiceFactory = scope1.GetInstance(); + fakeServiceFromScope1 = serviceWithServiceFactory.ServiceFactory.GetInstance(); + + serviceWithServiceFactory = scope1.GetInstance(); + otherFakeServiceFromScope1 = serviceWithServiceFactory.ServiceFactory.GetInstance(); + } + + using (var scope2 = container.BeginScope()) + { + var serviceWithServiceFactory = scope2.GetInstance(); + fakeServiceFromScope2 = serviceWithServiceFactory.ServiceFactory.GetInstance(); + + serviceWithServiceFactory = scope2.GetInstance(); + otherFakeServiceFromScope2 = serviceWithServiceFactory.ServiceFactory.GetInstance(); + } + + // Assert + Assert.Same(fakeServiceFromScope1, otherFakeServiceFromScope1); + Assert.Same(fakeServiceFromScope2, otherFakeServiceFromScope2); + Assert.NotSame(fakeServiceFromScope1, fakeServiceFromScope2); + } + + [Fact] + public void SingletonServiceCanBeResolvedFromScope() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.Register(new RootScopeLifetime(rootScope)); + container.RegisterScoped(f => f); + + // Act + IServiceFactory scopedSp1 = null; + IServiceFactory scopedSp2 = null; + ClassWithServiceFactory instance1 = null; + ClassWithServiceFactory instance2 = null; + + using (var scope1 = container.BeginScope()) + { + scopedSp1 = scope1.GetInstance(); + instance1 = scope1.GetInstance(); + } + + using (var scope2 = container.BeginScope()) + { + scopedSp2 = scope2.GetInstance(); + instance2 = scope2.GetInstance(); + } + + // Assert + Assert.Same(instance1, instance2); + Assert.Same(instance1.ServiceFactory, instance2.ServiceFactory); + Assert.NotSame(instance1.ServiceFactory, scopedSp1); + Assert.NotSame(instance2.ServiceFactory, scopedSp2); + } + + [Fact] + public void SingleServiceCanBeIEnumerableResolved() + { + // Arrange + var container = new ServiceContainer(); + container.RegisterTransient(); + + // Act + var services = container.GetInstance>(); + + // Assert + Assert.NotNull(services); + var service = Assert.Single(services); + Assert.IsType(service); + } + + [Fact] + public void MultipleServiceCanBeIEnumerableResolved() + { + // Arrange + var container = CreateContainer(); + container.RegisterTransient(); + container.RegisterTransient(); + + // Act + var services = container.GetInstance>(); + + // Assert + Assert.Collection(services.OrderBy(s => s.GetType().FullName), + service => Assert.IsType(service), + service => Assert.IsType(service)); + } + + [Fact] + public void RegistrationOrderIsPreservedWhenServicesAreIEnumerableResolved() + { + // Arrange + var container = CreateContainer(); + container.RegisterTransient(); + container.RegisterTransient(); + + + var containerReversed = CreateContainer(); + containerReversed.RegisterTransient(); + containerReversed.RegisterTransient(); + + // Act + var services = container.GetInstance>(); + var servicesReversed = containerReversed.GetInstance>(); + + // Assert + Assert.Collection(services, + service => Assert.IsType(service), + service => Assert.IsType(service)); + + Assert.Collection(servicesReversed, + service => Assert.IsType(service), + service => Assert.IsType(service)); + } + + [Fact] + public void OuterServiceCanHaveOtherServicesInjected() + { + // Arrange + var fakeService = new FakeService(); + var container = CreateContainer(); + container.RegisterTransient(); + container.RegisterInstance(fakeService); + container.RegisterTransient(); + container.RegisterTransient(); + + // Act + var services = container.GetInstance(); + + // Assert + Assert.Same(fakeService, services.SingleService); + Assert.Collection(services.MultipleServices.OrderBy(s => s.GetType().FullName), + service => Assert.IsType(service), + service => Assert.IsType(service)); + } + + [Fact] + public void FactoryServicesCanBeCreatedByGetService() + { + // Arrange + var container = CreateContainer(); + container.RegisterTransient(); + + container.RegisterTransient(sf => + { + var fakeService = sf.GetInstance(); + return new TransientFactoryService + { + FakeService = fakeService, + Value = 42 + }; + }); + + // Act + var service = container.GetInstance(); + + // Assert + Assert.NotNull(service); + Assert.Equal(42, service.Value); + Assert.NotNull(service.FakeService); + Assert.IsType(service.FakeService); + } + + [Fact] + public void FactoryServicesAreCreatedAsPartOfCreatingObjectGraph() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.RegisterTransient(); + container.RegisterTransient(sf => + { + var fakeService = sf.GetInstance(); + return new TransientFactoryService + { + FakeService = fakeService, + Value = 42 + }; + }); + container.RegisterScoped(sf => + { + var fakeService = sf.GetInstance(); + return new ScopedFactoryService + { + FakeService = fakeService, + }; + }); + + container.RegisterTransient(); + + // Act + var service1 = rootScope.GetInstance(); + var service2 = rootScope.GetInstance(); + + // Assert + Assert.Equal(42, service1.TransientService.Value); + Assert.NotNull(service1.TransientService.FakeService); + + Assert.Equal(42, service2.TransientService.Value); + Assert.NotNull(service2.TransientService.FakeService); + + Assert.NotNull(service1.ScopedService.FakeService); + + // Verify scoping works + Assert.NotSame(service1.TransientService, service2.TransientService); + Assert.Same(service1.ScopedService, service2.ScopedService); + } + + [Fact] + public void LastServiceReplacesPreviousServices() + { + // Arrange + var container = CreateContainer(); + container.RegisterTransient(); + container.RegisterTransient(); + + // Act + var service = container.GetInstance(); + + // Assert + Assert.IsType(service); + } + + [Fact] + public void SingletonServiceCanBeResolved() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + container.Register(new RootScopeLifetime(rootScope)); + + // Act + var service1 = container.GetInstance(); + var service2 = container.GetInstance(); + + // Assert + Assert.NotNull(service1); + Assert.Same(service1, service2); + } + + // Note: These tests are not relevant here. Consider moving them to LightInject.Microsoft.DependencyInjection.Tests + // public void ServiceProviderRegistersServiceScopeFactory() + // public void ServiceScopeFactoryIsSingleton() + + [Fact] + public void ScopedServiceCanBeResolved() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.RegisterScoped(); + + // Act + using (var scope = container.BeginScope()) + { + var providerScopedService = rootScope.GetInstance(); + var scopedService1 = scope.GetInstance(); + var scopedService2 = scope.GetInstance(); + + // Assert + Assert.NotSame(providerScopedService, scopedService1); + Assert.Same(scopedService1, scopedService2); + } + } + + [Fact] + public void NestedScopedServiceCanBeResolved() + { + // Arrange + var container = CreateContainer(); + container.RegisterScoped(); + + // Act + using (var outerScope = container.BeginScope()) + using (var innerScope = outerScope.BeginScope()) + { + var outerScopedService = outerScope.GetInstance(); + var innerScopedService = innerScope.GetInstance(); + + // Assert + Assert.NotNull(outerScopedService); + Assert.NotNull(innerScopedService); + Assert.NotSame(outerScopedService, innerScopedService); + } + } + + // Note: These tests are not relevant here. Consider moving them to LightInject.Microsoft.DependencyInjection.Tests + // public void ScopedServices_FromCachedScopeFactory_CanBeResolvedAndDisposed() + + [Fact] + public void ScopesAreFlatNotHierarchical() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.Register(new RootScopeLifetime(rootScope)); + + // Act + var outerScope = container.BeginScope(); + using var innerScope = outerScope.BeginScope(); + outerScope.Dispose(); + var innerScopedService = innerScope.GetInstance(); + + // Assert + Assert.NotNull(innerScopedService); + } + + // Note: These tests are not relevant here. Consider moving them to LightInject.Microsoft.DependencyInjection.Tests + // public void ServiceProviderIsDisposable() + + + [Fact] + public void DisposingScopeDisposesService() + { + // Arrange + + var container = CreateContainer(); + var rootScope = container.BeginScope(); + FakeService disposableService; + FakeService transient1; + FakeService transient2; + FakeService transient3; + FakeService singleton; + + container.Register(new RootScopeLifetime(rootScope)); + container.RegisterScoped(); + container.Register(new PerRequestLifeTime()); + + // Act and Assert + transient3 = Assert.IsType(rootScope.GetInstance()); + using (var scope = container.BeginScope()) + { + disposableService = (FakeService)scope.GetInstance(); + transient1 = (FakeService)scope.GetInstance(); + transient2 = (FakeService)scope.GetInstance(); + singleton = (FakeService)scope.GetInstance(); + + Assert.False(disposableService.Disposed); + Assert.False(transient1.Disposed); + Assert.False(transient2.Disposed); + Assert.False(singleton.Disposed); + } + + + Assert.True(disposableService.Disposed); + Assert.True(transient1.Disposed); + Assert.True(transient2.Disposed); + Assert.False(singleton.Disposed); + + (rootScope as IDisposable).Dispose(); + + Assert.True(singleton.Disposed); + Assert.True(transient3.Disposed); + } + + // Note: These tests are not relevant here. Consider moving them to LightInject.Microsoft.DependencyInjection.Tests + // public void SelfResolveThenDispose() + // public void SafelyDisposeNestedProviderReferences() + + [Fact] + public void SingletonServicesComeFromRootProvider() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.Register(new RootScopeLifetime(rootScope)); + + FakeService disposableService1; + FakeService disposableService2; + + // Act and Assert + using (var scope = container.BeginScope()) + { + var service = scope.GetInstance(); + disposableService1 = Assert.IsType(service); + Assert.False(disposableService1.Disposed); + } + + Assert.False(disposableService1.Disposed); + + using (var scope = container.BeginScope()) + { + var service = scope.GetInstance(); + disposableService2 = Assert.IsType(service); + Assert.False(disposableService2.Disposed); + } + + Assert.False(disposableService2.Disposed); + Assert.Same(disposableService1, disposableService2); + } + + [Fact] + public void NestedScopedServiceCanBeResolvedWithNoFallbackProvider() + { + // Arrange + var container = CreateContainer(); + container.RegisterScoped(); + + + // Act + using (var outerScope = container.BeginScope()) + using (var innerScope = outerScope.BeginScope()) + { + var outerScopedService = outerScope.GetInstance(); + var innerScopedService = innerScope.GetInstance(); + + // Assert + Assert.NotSame(outerScopedService, innerScopedService); + } + } + + [Fact] + public void OpenGenericServicesCanBeResolved() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.Register(new RootScopeLifetime(rootScope)); + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + + // Act + var genericService = container.GetInstance>(); + var singletonService = container.GetInstance(); + + // Assert + Assert.Same(singletonService, genericService.Value); + } + + [Fact] + public void ConstrainedOpenGenericServicesCanBeResolved() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(ConstrainedFakeOpenGenericService<>)); + + var poco = new PocoClass(); + container.RegisterInstance(poco); + container.Register(new RootScopeLifetime(rootScope)); + + // Act + var allServices = container.GetAllInstances>().ToList(); + var constrainedServices = container.GetAllInstances>().ToList(); + var singletonService = container.GetInstance(); + // Assert + Assert.Equal(2, allServices.Count); + Assert.Same(poco, allServices[0].Value); + Assert.Same(poco, allServices[1].Value); + Assert.Single(constrainedServices); + Assert.Same(singletonService, constrainedServices[0].Value); + } + + [Fact] + public void ConstrainedOpenGenericServicesReturnsEmptyWithNoMatches() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(ConstrainedFakeOpenGenericService<>)); + container.Register(new RootScopeLifetime(rootScope)); + + // Act + var constrainedServices = rootScope.GetAllInstances>().ToList(); + // Assert + Assert.Empty(constrainedServices); + } + + [Fact] + public void InterfaceConstrainedOpenGenericServicesCanBeResolved() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + // Arrange + + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithInterfaceConstraint<>)); + + var enumerableVal = new ClassImplementingIEnumerable(); + container.RegisterInstance(enumerableVal); + container.Register(new RootScopeLifetime(rootScope)); + + // Act + var allServices = rootScope.GetAllInstances>().ToList(); + var constrainedServices = rootScope.GetAllInstances>().ToList(); + var singletonService = rootScope.GetInstance(); + // Assert + Assert.Equal(2, allServices.Count); + Assert.Same(enumerableVal, allServices[0].Value); + Assert.Same(enumerableVal, allServices[1].Value); + Assert.Single(constrainedServices); + Assert.Same(singletonService, constrainedServices[0].Value); + } + + [Fact] + public void AbstractClassConstrainedOpenGenericServicesCanBeResolved() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithAbstractClassConstraint<>)); + + + var poco = new PocoClass(); + + container.RegisterInstance(poco); + var classInheritingClassInheritingAbstractClass = new ClassInheritingClassInheritingAbstractClass(); + + container.RegisterInstance(classInheritingClassInheritingAbstractClass); + + // Act + var allServices = rootScope.GetAllInstances>().ToList(); + var constrainedServices = rootScope.GetAllInstances>().ToList(); + // Assert + Assert.Equal(2, allServices.Count); + Assert.Same(classInheritingClassInheritingAbstractClass, allServices[0].Value); + Assert.Same(classInheritingClassInheritingAbstractClass, allServices[1].Value); + Assert.Single(constrainedServices); + Assert.Same(poco, constrainedServices[0].Value); + } + + [Fact] + public void ClosedServicesPreferredOverOpenGenericServices() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.RegisterTransient(typeof(IFakeOpenGenericService), typeof(FakeService)); + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + container.Register(new RootScopeLifetime(rootScope)); + + // Act + var service = rootScope.GetInstance>(); + + // Assert + Assert.IsType(service); + } + + [Fact] + public void ResolvingEnumerableContainingOpenGenericServiceUsesCorrectSlot() + { + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + // Arrange + // TestServiceCollection collection = new(); + // collection.AddTransient, FakeService>(); + // collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + // collection.AddSingleton(); // needed for FakeOpenGenericService<> + // IServiceProvider provider = CreateServiceProvider(collection); + + container.RegisterTransient, FakeService>(); + container.RegisterTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + container.Register(new RootScopeLifetime(rootScope)); + + // Act + IFakeOpenGenericService service = rootScope.GetInstance>(); + IFakeOpenGenericService[] services = rootScope.GetAllInstances>().ToArray(); + + // Assert + Assert.IsType(service); + Assert.Equal(2, services.Length); + Assert.Contains(services, s => s.GetType() == typeof(FakeService)); + Assert.Contains(services, s => s.GetType() == typeof(FakeOpenGenericService)); + } + + + [Fact] + public void AttemptingToResolveNonexistentServiceReturnsNull() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + // Act + var service = rootScope.TryGetInstance(); + + // Assert + Assert.Null(service); + } + + [Fact] + public void NonexistentServiceCanBeIEnumerableResolved() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + // Act + var services = rootScope.GetInstance>(); + + // Assert + Assert.Empty(services); + } + + public static TheoryData ServiceContainerPicksConstructorWithLongestMatchesData + { + get + { + var containerOptions = new ContainerOptions { EnableCurrentScope = false, EnableMicrosoftCompatibility = true }; + + var fakeService = new FakeService(); + var multipleService = new FakeService(); + var factoryService = new TransientFactoryService(); + var scopedService = new FakeService(); + + return new TheoryData + { + { + new ServiceContainer(containerOptions) + .RegisterInstance(fakeService), + new TypeWithSupersetConstructors(fakeService) + }, + { + new ServiceContainer(containerOptions) + .RegisterInstance(factoryService), + new TypeWithSupersetConstructors(factoryService) + }, + { + new ServiceContainer(containerOptions) + .RegisterInstance(fakeService) + .RegisterInstance(factoryService), + new TypeWithSupersetConstructors(fakeService, factoryService) + }, + { + new ServiceContainer(containerOptions) + .RegisterInstance(fakeService) + .RegisterInstance(multipleService) + .RegisterInstance(factoryService), + new TypeWithSupersetConstructors(fakeService, multipleService, factoryService) + }, + { + new ServiceContainer(containerOptions) + .RegisterInstance(fakeService) + .RegisterInstance(multipleService) + .RegisterInstance(scopedService) + .RegisterInstance(factoryService), + new TypeWithSupersetConstructors(multipleService, factoryService, fakeService, scopedService) + } + }; + } + } + + [Theory] + [MemberData(nameof(ServiceContainerPicksConstructorWithLongestMatchesData))] + public void ServiceContainerPicksConstructorWithLongestMatches( + IServiceRegistry serviceRegistry, + TypeWithSupersetConstructors expected) + { + // Arrange + + serviceRegistry.RegisterTransient(); + var container = (IServiceContainer)serviceRegistry; + + // Act + var actual = container.GetInstance(); + + // Assert + Assert.NotNull(actual); + Assert.Same(expected.Service, actual.Service); + Assert.Same(expected.FactoryService, actual.FactoryService); + Assert.Same(expected.MultipleService, actual.MultipleService); + Assert.Same(expected.ScopedService, actual.ScopedService); + } +#if !USE_EXPRESSIONS + [Fact] + public void DisposesInReverseOrderOfCreation() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + container.Register(new PerRootScopeLifetime(rootScope)); + container.Register(new PerRequestLifeTime()); + container.Register(new PerRootScopeLifetime(rootScope)); + container.RegisterScoped(); + container.Register(new PerRequestLifeTime()); + container.Register(new PerRootScopeLifetime(rootScope)); + + var callback = rootScope.GetInstance(); + var outer = rootScope.GetInstance(); + var multipleServices = outer.MultipleServices.ToArray(); + + // Act + rootScope.Dispose(); + + // Assert + Assert.Equal(outer, callback.Disposed[0]); + //Assert.Equal(multipleServices.Reverse(), callback.Disposed.Skip(1).Take(3).OfType()); + Assert.Equal(outer.SingleService, callback.Disposed[4]); + } +#endif + [Fact] + public void ResolvesMixedOpenClosedGenericsAsEnumerable() + { + // Arrange + var container = CreateContainer(); + var rootScope = container.BeginScope(); + + var instance = new FakeOpenGenericService(null); + + container.RegisterTransient(); + container.Register(typeof(IFakeOpenGenericService), typeof(FakeService), new PerRootScopeLifetime(rootScope)); + container.Register(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>), new PerRootScopeLifetime(rootScope)); + container.RegisterInstance>(instance); + + // var serviceCollection = new TestServiceCollection(); + + + // serviceCollection.AddTransient(); + // serviceCollection.AddSingleton(typeof(IFakeOpenGenericService), typeof(FakeService)); + // serviceCollection.AddSingleton(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + // serviceCollection.AddSingleton>(instance); + + // var serviceProvider = CreateServiceProvider(serviceCollection); + + var enumerable = container.GetInstance>>().ToArray(); + + // Assert + Assert.Equal(3, enumerable.Length); + Assert.NotNull(enumerable[0]); + Assert.NotNull(enumerable[1]); + Assert.NotNull(enumerable[2]); + + Assert.Equal(instance, enumerable[2]); + // NOTE IS this important? + // Since we register the open generic service in flight, this comes last. + Assert.True(enumerable[0] is FakeService, string.Join(", ", enumerable.Select(e => e.GetType()))); + Assert.IsType(enumerable[0]); + } + + [Theory] + [InlineData(typeof(IFakeService), typeof(FakeService), typeof(IFakeService), ServiceLifetime.Scoped)] + [InlineData(typeof(IFakeService), typeof(FakeService), typeof(IFakeService), ServiceLifetime.Singleton)] + [InlineData(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>), typeof(IFakeOpenGenericService), ServiceLifetime.Scoped)] + [InlineData(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>), typeof(IFakeOpenGenericService), ServiceLifetime.Singleton)] + public void ResolvesDifferentInstancesForServiceWhenResolvingEnumerable(Type serviceType, Type implementation, Type resolve, ServiceLifetime lifetime) + { + // Arrange + var container = CreateContainer(); + container.RegisterScoped(f => f); + var rootScope = container.BeginScope(); + + if (lifetime == ServiceLifetime.Scoped) + { + container.RegisterScoped(serviceType, implementation); + container.RegisterScoped(serviceType, implementation); + container.RegisterScoped(serviceType, implementation); + } + else + { + container.Register(serviceType, implementation, new PerRootScopeLifetime(rootScope)); + container.Register(serviceType, implementation, new PerRootScopeLifetime(rootScope)); + container.Register(serviceType, implementation, new PerRootScopeLifetime(rootScope)); + } + + + // var serviceCollection = new TestServiceCollection + // { + // ServiceDescriptor.Describe(serviceType, implementation, lifetime), + // ServiceDescriptor.Describe(serviceType, implementation, lifetime), + // ServiceDescriptor.Describe(serviceType, implementation, lifetime) + // }; + + // var serviceProvider = CreateServiceProvider(serviceCollection); + + + using (var scope = container.BeginScope()) + { + var enumerable = (scope.GetInstance(typeof(IEnumerable<>).MakeGenericType(resolve)) as IEnumerable) + .OfType().ToArray(); + var service = scope.GetInstance(resolve); + + // Assert + Assert.Equal(3, enumerable.Length); + Assert.NotNull(enumerable[0]); + Assert.NotNull(enumerable[1]); + Assert.NotNull(enumerable[2]); + + Assert.NotEqual(enumerable[0], enumerable[1]); + Assert.NotEqual(enumerable[1], enumerable[2]); + Assert.Equal(service, enumerable[2]); + } + } + + + + + [Fact] + public void ShouldAllowMultipleRegistrations() + { + var container = CreateContainer(); + container.Register(); + container.Register(); + + var instances = container.GetAllInstances(); + Assert.Equal(2, instances.Count()); + + var foo = container.GetInstance(); + Assert.IsType(foo); + } + + [Fact] + public void ShouldAllowMultipleNamedRegistrations() + { + var container = CreateContainer(); + + } +} + +public class ClassWithServiceFactory +{ + public ClassWithServiceFactory(IServiceFactory serviceFactory) + { + ServiceFactory = serviceFactory; + } + + public IServiceFactory ServiceFactory { get; } +} + +/// +/// An implementation that makes it possible to mimic the notion of a root scope. +/// +[LifeSpan(30)] +internal class RootScopeLifetime : ILifetime, ICloneableLifeTime +{ + private readonly object syncRoot = new object(); + private readonly Scope rootScope; + private object instance; + + /// + /// Initializes a new instance of the class. + /// + /// The root . + public RootScopeLifetime(Scope rootScope) + => this.rootScope = rootScope; + + /// + [ExcludeFromCodeCoverage] + public object GetInstance(Func createInstance, Scope scope) + => throw new NotImplementedException("Uses optimized non closing method"); + + /// + public ILifetime Clone() + => new PerRootScopeLifetime(rootScope); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable IDE0060 + public object GetInstance(GetInstanceDelegate createInstance, Scope scope, object[] arguments) + { +#pragma warning restore IDE0060 + if (instance != null) + { + return instance; + } + + lock (syncRoot) + { + if (instance == null) + { + instance = createInstance(arguments, rootScope); + RegisterForDisposal(instance); + } + } + + return instance; + } + + private void RegisterForDisposal(object instance) + { + if (instance is IDisposable disposable) + { + rootScope.TrackInstance(disposable); + } + else if (instance is IAsyncDisposable asyncDisposable) + { + rootScope.TrackInstance(asyncDisposable); + } + } +} diff --git a/src/LightInject.Tests/MicrosoftTestsWithoutVariance.cs b/src/LightInject.Tests/MicrosoftTestsWithoutVariance.cs new file mode 100644 index 00000000..aab2df32 --- /dev/null +++ b/src/LightInject.Tests/MicrosoftTestsWithoutVariance.cs @@ -0,0 +1,23 @@ + +namespace LightInject.Tests; + +public class MicrosoftTestsWithoutVariance : MicrosoftTests +{ + internal override IServiceContainer CreateContainer() + { + var container = new ServiceContainer(options => + { + options.EnableMicrosoftCompatibility = true; + options.EnableCurrentScope = false; + options.OptimizeForLargeObjectGraphs = false; + options.EnableOptionalArguments = false; + options.EnableVariance = false; + }) + { + AssemblyScanner = new NoOpAssemblyScanner() + }; + container.ConstructorDependencySelector = new AnnotatedConstructorDependencySelector(); + container.ConstructorSelector = new AnnotatedConstructorSelector(container.CanGetInstance); + return container; + } +} \ No newline at end of file diff --git a/src/LightInject.Tests/OrderedTests.cs b/src/LightInject.Tests/OrderedTests.cs index dba8bd9d..dbfbc07c 100644 --- a/src/LightInject.Tests/OrderedTests.cs +++ b/src/LightInject.Tests/OrderedTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Diagnostics.Contracts; +using System.Linq; using LightInject.SampleLibrary; using Xunit; @@ -38,7 +39,7 @@ public void ShouldOrderServicesByNameWithVarianceDisabled() public void ShouldOrderServicesWhenRegisteredAsOrdered() { var container = CreateContainer(); - container.RegisterOrdered(typeof(IFoo), new[] {typeof(Foo1), typeof(Foo2), typeof(Foo3)}, + container.RegisterOrdered(typeof(IFoo), new[] { typeof(Foo1), typeof(Foo2), typeof(Foo3) }, type => new PerContainerLifetime()); var instances = container.GetAllInstances().ToArray(); @@ -48,6 +49,19 @@ public void ShouldOrderServicesWhenRegisteredAsOrdered() Assert.IsType(instances[2]); } + [Fact] + public void ShouldResolveLastService() + { + var container = CreateContainer(); + var foo1 = new Foo(); + var foo2 = new Foo(); + container.RegisterInstance(foo1); + container.RegisterInstance(foo2); + + var test = container.AvailableServices; + } + + [Fact] public void ShouldOrderOpenGenericServicesWhenRegisteredAsOrdered() { @@ -58,7 +72,7 @@ public void ShouldOrderOpenGenericServicesWhenRegisteredAsOrdered() var instances = container.GetAllInstances>().ToArray(); Assert.IsType>(instances[0]); Assert.IsType>(instances[1]); - Assert.IsType>(instances[2]); + Assert.IsType>(instances[2]); } [Fact] @@ -66,7 +80,7 @@ public void ShouldUseCustomServiceNameFormatter() { var container = CreateContainer(); container.RegisterOrdered(typeof(IFoo<>), new[] { typeof(Foo1<>), typeof(Foo2<>), typeof(Foo3<>) }, - type => new PerContainerLifetime(), i => $"A{i.ToString().PadLeft(3,'0')}"); + type => new PerContainerLifetime(), i => $"A{i.ToString().PadLeft(3, '0')}"); var services = container.AvailableServices.Where(sr => sr.ServiceType == typeof(IFoo<>)) .OrderBy(sr => sr.ServiceName).ToArray(); @@ -89,5 +103,5 @@ public class Foo3 : IFoo { } } - + } \ No newline at end of file diff --git a/src/LightInject.Tests/SampleServices/Foo.cs b/src/LightInject.Tests/SampleServices/Foo.cs index af3f8989..9c72888c 100644 --- a/src/LightInject.Tests/SampleServices/Foo.cs +++ b/src/LightInject.Tests/SampleServices/Foo.cs @@ -8,6 +8,7 @@ namespace LightInject.SampleLibrary using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; public interface IFooWithProperty { @@ -143,7 +144,7 @@ public class AnotherFoo : IFoo { } public class FooWithDependency : IFoo { - public FooWithDependency(IBar bar) + public FooWithDependency([FromKeyedServices("AnotherBar")] IBar bar) { Bar = bar; } diff --git a/src/LightInject.Tests/ServiceContainerTests.cs b/src/LightInject.Tests/ServiceContainerTests.cs index c23d620b..175428db 100644 --- a/src/LightInject.Tests/ServiceContainerTests.cs +++ b/src/LightInject.Tests/ServiceContainerTests.cs @@ -83,13 +83,7 @@ public void RegisterInstance_NullInstance_ThrowsArgumentNullException() Assert.Equal("instance", exception.ParamName); } - [Fact] - public void RegisterInstance_NullServiceName_ThrowsArgumentNullException() - { - var container = CreateContainer(); - var exception = Assert.Throws(() => container.RegisterInstance(typeof(object), new object(), null)); - Assert.Equal("serviceName", exception.ParamName); - } + #endregion @@ -144,7 +138,7 @@ public void GetInstance_NamedValue_ReturnsNamedValue() } [Fact] - public void GetInstance_ValueTypeAsSingeton_ReturnsValue() + public void GetInstance_ValueTypeAsSingleton_ReturnsValue() { var container = CreateContainer(); container.Register(factory => 42, new PerContainerLifetime()); @@ -696,14 +690,7 @@ public void GetInstance_FuncWithTransientTarget_ReturnsTransientInstance() var instance2 = factory(); Assert.NotSame(instance1, instance2); } - - //[Fact] - //public void GetInstance_Func_FailesWhenUnderlyingServiceIsMissing() - //{ - // var container = CreateContainer(new ContainerOptions(){EnableStrictDeferredResolution = true}); - // Assert.Throws(() => container.GetInstance>()); - //} - + #endregion #region Func Factory @@ -780,7 +767,7 @@ public void GetInstance_NamedSingletonFuncFactory_ReturnsFactoryCreatedInstance( } [Fact] - public void GetInstance_Funcfactory_ReturnsInstanceWithDependencies() + public void GetInstance_FuncFactory_ReturnsInstanceWithDependencies() { var container = CreateContainer(); container.Register(c => new Bar()); @@ -790,7 +777,7 @@ public void GetInstance_Funcfactory_ReturnsInstanceWithDependencies() } [Fact] - public void GetInstance_FuncFactoryWithReferenceTypeDepenedency_ReturnsInstanceWithDependencies() + public void GetInstance_FuncFactoryWithReferenceTypeDependency_ReturnsInstanceWithDependencies() { var container = CreateContainer(); container.Register(c => new FooWithReferenceTypeDependency("SomeStringValue")); @@ -799,7 +786,7 @@ public void GetInstance_FuncFactoryWithReferenceTypeDepenedency_ReturnsInstanceW } [Fact] - public void GetInstance_FuncFactoryWithValueTypeDepenedency_ReturnsInstanceWithDependencies() + public void GetInstance_FuncFactoryWithValueTypeDependency_ReturnsInstanceWithDependencies() { var container = CreateContainer(); container.Register(c => new FooWithValueTypeDependency(42)); @@ -808,7 +795,7 @@ public void GetInstance_FuncFactoryWithValueTypeDepenedency_ReturnsInstanceWithD } [Fact] - public void GetInstance_FuncFactoryWithEnumDepenedency_ReturnsInstanceWithDependencies() + public void GetInstance_FuncFactoryWithEnumDependency_ReturnsInstanceWithDependencies() { var container = CreateContainer(); container.Register(c => new FooWithEnumDependency(Encoding.UTF8)); @@ -1019,7 +1006,7 @@ public void GetInstance_KnownOpenGenericReadonlyCollection_ReturnsKnownCollectio } [Fact] - public void GetInstance_KnownOpenGenercEnumerable_ReturnsKnownEnumerable() + public void GetInstance_KnownOpenGenericEnumerable_ReturnsKnownEnumerable() { var container = CreateContainer(); container.Register(typeof(IEnumerable<>), typeof(FooList<>)); @@ -1087,7 +1074,7 @@ public void GetInstance_PerContainerLifetimeUsingServicePredicate_ReturnsSameIns } [Fact] - public void GetInstance_UsingFallBack_ProvidesServiceReuest() + public void GetInstance_UsingFallBack_ProvidesServiceRequest() { var container = CreateContainer(); ServiceRequest serviceRequest = null; diff --git a/src/LightInject.Tests/ServiceKeyTests.cs b/src/LightInject.Tests/ServiceKeyTests.cs new file mode 100644 index 00000000..744f27e4 --- /dev/null +++ b/src/LightInject.Tests/ServiceKeyTests.cs @@ -0,0 +1,39 @@ +using LightInject.SampleLibrary; +using Xunit; + +namespace LightInject.Tests; + + +public class ServiceKeyTests +{ + [Fact] + public void ShouldBeEqualWhenServiceTypeAndServiceNameIsSame() + { + var serviceKey1 = new ServiceKey(typeof(IFoo), "Foo"); + var serviceKey2 = new ServiceKey(typeof(IFoo), "Foo"); + Assert.Equal(serviceKey1, serviceKey2); + } + + [Fact] + public void ShouldNotBeEqualWhenServiceTypeIsDifferent() + { + var serviceKey1 = new ServiceKey(typeof(IFoo), "Foo"); + var serviceKey2 = new ServiceKey(typeof(IBar), "Foo"); + Assert.NotEqual(serviceKey1, serviceKey2); + } + + [Fact] + public void ShouldNotBeEqualWhenServiceNameIsDifferent() + { + var serviceKey1 = new ServiceKey(typeof(IFoo), "Foo"); + var serviceKey2 = new ServiceKey(typeof(IFoo), "Bar"); + Assert.NotEqual(serviceKey1, serviceKey2); + } + + [Fact] + public void ShouldHaveToStringWithServiceTypeAndServiceName() + { + var serviceKey = new ServiceKey(typeof(IFoo), "Foo"); + Assert.Equal("LightInject.SampleLibrary.IFoo - Foo", serviceKey.ToString()); + } +} \ No newline at end of file diff --git a/src/LightInject.Tests/ServiceRegistrationEqualsTests.cs b/src/LightInject.Tests/ServiceRegistrationEqualsTests.cs new file mode 100644 index 00000000..0938d463 --- /dev/null +++ b/src/LightInject.Tests/ServiceRegistrationEqualsTests.cs @@ -0,0 +1,65 @@ +using LightInject.SampleLibrary; +using Xunit; + +namespace LightInject.Tests; + +public class ServiceRegistrationEqualsTests +{ + [Fact] + public void ShouldBeEqualWhenServiceTypeAndServiceNameAreTheSame() + { + var serviceRegistration1 = new ServiceRegistration + { + ServiceType = typeof(int), + ServiceName = string.Empty + }; + + var serviceRegistration2 = new ServiceRegistration + { + ServiceType = typeof(int), + ServiceName = string.Empty + }; + + Assert.Equal(serviceRegistration1, serviceRegistration2); + } + + [Fact] + public void ShouldNotBeEqualIfImplementingTypeIsDifferent() + { + var serviceRegistration1 = new ServiceRegistration + { + ServiceType = typeof(IFoo), + ServiceName = string.Empty, + ImplementingType = typeof(Foo) + }; + + var serviceRegistration2 = new ServiceRegistration + { + ServiceType = typeof(IFoo), + ServiceName = string.Empty, + ImplementingType = typeof(AnotherFoo) + }; + + Assert.NotEqual(serviceRegistration1, serviceRegistration2); + } + + [Fact] + public void ShouldNotBeEqualWithMixedImplementingTypeAndFactoryExpression() + { + var serviceRegistration1 = new ServiceRegistration + { + ServiceType = typeof(IFoo), + ServiceName = string.Empty, + ImplementingType = typeof(Foo) + }; + + var serviceRegistration2 = new ServiceRegistration + { + ServiceType = typeof(IFoo), + ServiceName = string.Empty, + FactoryExpression = (IServiceFactory f) => new Foo() + }; + + Assert.NotEqual(serviceRegistration1, serviceRegistration2); + } +} \ No newline at end of file diff --git a/src/LightInject.Tests/ServiceRegistrationTests.cs b/src/LightInject.Tests/ServiceRegistrationTests.cs index c6443e16..9e6bc1ca 100644 --- a/src/LightInject.Tests/ServiceRegistrationTests.cs +++ b/src/LightInject.Tests/ServiceRegistrationTests.cs @@ -174,15 +174,13 @@ public void Register_ServiceAfterFirstGetInstance_TracesWarning() { string message = null; var container = new ServiceContainer(new ContainerOptions { LogFactory = t => m => message = m.Message }); - - //SampleTraceListener sampleTraceListener = new SampleTraceListener(m => message = m); + try { - /// Trace.Listeners.Add(sampleTraceListener); container.Register(); container.GetInstance(); container.Register(); - Assert.StartsWith("Cannot overwrite existing serviceregistration", message); + Assert.StartsWith("Cannot overwrite existing service registration", message); } finally { diff --git a/src/LightInject.Tests/TypeNameFormatter.cs b/src/LightInject.Tests/TypeNameFormatter.cs new file mode 100644 index 00000000..f2842ca2 --- /dev/null +++ b/src/LightInject.Tests/TypeNameFormatter.cs @@ -0,0 +1,45 @@ +using System; +using System.Text; + +namespace LightInject; + +/// +/// [assembly: DebuggerDisplay("{LightInject.TypeNameFormatter.GetHumanFriendlyTypeName(this)}", Target = typeof(Type))] +/// +public static class TypeNameFormatter +{ + public static string GetHumanFriendlyTypeName(Type type) + { + StringBuilder humanFriendlyName = new StringBuilder(); + if (type.IsGenericType && !type.IsGenericTypeDefinition) + { + + humanFriendlyName.Append(type.Name.Substring(0, type.Name.IndexOf('`'))); + humanFriendlyName.Append('<'); + foreach (Type argument in type.GenericTypeArguments) + { + humanFriendlyName.Append(GetHumanFriendlyTypeName(argument)); + humanFriendlyName.Append(", "); + } + humanFriendlyName.Remove(humanFriendlyName.Length - 2, 2); + humanFriendlyName.Append('>'); + } + else if (type.IsGenericTypeDefinition) + { + humanFriendlyName.Append(type.Name.Substring(0, type.Name.IndexOf('`'))); + humanFriendlyName.Append('<'); + foreach (Type parameter in type.GetGenericArguments()) + { + humanFriendlyName.Append(parameter.Name); + humanFriendlyName.Append(", "); + } + humanFriendlyName.Remove(humanFriendlyName.Length - 2, 2); + humanFriendlyName.Append('>'); + } + else + { + humanFriendlyName.Append(type.Name); + } + return humanFriendlyName.ToString(); + } +} diff --git a/src/LightInject/LightInject.cs b/src/LightInject/LightInject.cs index 6990970b..4683e557 100644 --- a/src/LightInject/LightInject.cs +++ b/src/LightInject/LightInject.cs @@ -4,14 +4,13 @@ #endif #if NETCOREAPP3_1_OR_GREATER || NET5_0_OR_GREATER -#define USE_ASYNCDISPOSABLE +#define USE_ASYNCDISPOSABLE #endif - /********************************************************************************* The MIT License (MIT) - Copyright (c) 2023 bernhard.richter@gmail.com + Copyright (c) 2024 bernhard.richter@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -31,11 +30,20 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************** - LightInject version 6.6.4 + LightInject version 7.0.0 http://www.lightinject.net/ http://twitter.com/bernhardrichter ******************************************************************************/ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using LightInject; + [module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1126:PrefixCallsCorrectly", Justification = "Reviewed")] [module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:PrefixLocalCallsWithThis", Justification = "No inheritance")] [module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Single source file deployment.")] @@ -59,6 +67,7 @@ namespace LightInject using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -89,6 +98,41 @@ public enum LogLevel Warning, } + /// + /// Describes the type of factory used to create a service. + /// + public enum FactoryType + { + /// + /// A regular factory. + /// + Service, + + /// + /// A factory that creates a service with a service key. + /// + ServiceWithServiceKey, + + /// + /// A factory that creates a service with runtime arguments. + /// + ServiceWithRuntimeArguments, + } + + /// + /// Represents a class that is capable converting the service name to a service key of a given type. + /// + public interface IServiceKeyConverter + { + /// + /// Converts the to a service key of type . + /// + /// The type for which to convert the into. + /// The service name to be converted into . + /// The serviceName represented as . + TServiceKey Convert(string serviceName); + } + /// /// Defines a set of methods used to register services into the service container. /// @@ -376,6 +420,17 @@ IServiceRegistry Register(string serviceName, ILifeti /// The , for chaining calls. IServiceRegistry Register(Func factory, string serviceName, ILifetime lifetime); + /// + /// Registers the with the that + /// describes the dependencies of the service. + /// + /// The service type to register. + /// The lambdaExpression that describes the dependencies of the service. The requested service name is passed in as a parameter. + /// The name of the service. + /// The instance that controls the lifetime of the registered service. + /// The , for chaining calls. + IServiceRegistry Register(Func factory, string serviceName, ILifetime lifetime); + /// /// Registers the with a set of and /// ensures that service instance ordering matches the ordering of the . @@ -885,7 +940,7 @@ public interface ITypeConstructionInfoBuilder } /// - /// Represents a class that maps the generic arguments/parameters from a generic servicetype + /// Represents a class that maps the generic arguments/parameters from a generic service type /// to a open generic implementing type. /// public interface IGenericArgumentMapper @@ -1163,6 +1218,8 @@ public interface IScopeManagerProvider /// public static class RuntimeArgumentsLoader { + internal static MethodInfo LoadMethod = typeof(RuntimeArgumentsLoader).GetTypeInfo().GetDeclaredMethod("Load"); + /// /// Loads the runtime arguments onto the evaluation stack. /// @@ -1248,16 +1305,7 @@ public static IServiceRegistry Register(this IServiceRegistry serviceRegistry, T /// The used to control the lifetime of the service. /// The , for chaining calls. public static IServiceRegistry Register(this IServiceRegistry serviceRegistry, Type serviceType, Func factory, string serviceName, ILifetime lifetime) - { - var serviceRegistration = new ServiceRegistration - { - FactoryExpression = factory, - ServiceType = serviceType, - ServiceName = serviceName, - Lifetime = lifetime, - }; - return serviceRegistry.Register(serviceRegistration); - } + => serviceRegistry.RegisterServiceFromLambdaExpression(factory, lifetime, serviceType, serviceName, FactoryType.Service); /// /// Registers a singleton using the non-generic to resolve the instance. @@ -1319,8 +1367,8 @@ public static IServiceRegistry RegisterTransient(this IServiceRegistry serviceRe /// The factory used to resolve the instance. /// The name the service to register. /// The , for chaining calls. - public static IServiceRegistry RegisterTransient(this IServiceRegistry serviceRegistry, Type serviceType, Func factory, string serviceName) => - serviceRegistry.Register(serviceType, factory, serviceName); + public static IServiceRegistry RegisterTransient(this IServiceRegistry serviceRegistry, Type serviceType, Func factory, string serviceName) + => serviceRegistry.RegisterServiceFromLambdaExpression(factory, null, serviceType, serviceName, FactoryType.Service); /// /// Registers a singleton service of type with an implementing type of . @@ -1620,6 +1668,20 @@ public static IServiceRegistry Override(this IService /// The , for chaining calls. public static IServiceRegistry Initialize(this IServiceRegistry serviceRegistry, Action processor) => serviceRegistry.Initialize(sr => sr.ServiceType == typeof(TService), (factory, instance) => processor(factory, (TService)instance)); + + internal static IServiceRegistry RegisterServiceFromLambdaExpression(this IServiceRegistry serviceRegistry, Delegate factory, ILifetime lifetime, Type serviceType, string serviceName, FactoryType factoryType) + { + var serviceRegistration = new ServiceRegistration + { + ServiceType = serviceType, + FactoryExpression = factory, + ServiceName = serviceName, + FactoryType = factoryType, + Lifetime = lifetime, + }; + serviceRegistry.Register(serviceRegistration); + return serviceRegistry; + } } /// @@ -2420,6 +2482,7 @@ public ContainerOptions() EnableCurrentScope = true; EnableOptionalArguments = false; OptimizeForLargeObjectGraphs = false; + EnableMicrosoftCompatibility = false; } /// @@ -2490,9 +2553,40 @@ public ContainerOptions() /// public bool OptimizeForLargeObjectGraphs { get; set; } + /// + /// Gets or sets a value indicating whether to enable Microsoft compatibility. + /// This means that LightInject will operate in a way that is compatible with the Microsoft.Extensions.DependencyInjection. + /// + public bool EnableMicrosoftCompatibility { get; set; } + private static ContainerOptions CreateDefaultContainerOptions() => new ContainerOptions(); } + /// + /// A class that is capable converting the service name to a service key of a given type. + /// + public class ServiceKeyConverter : IServiceKeyConverter + { + /// + public TServiceKey Convert(string serviceName) + { + try + { + if (typeof(TServiceKey).IsEnum) + { + return (TServiceKey)Enum.Parse(typeof(TServiceKey), serviceName); + } + + return (TServiceKey)System.Convert.ChangeType(serviceName, typeof(TServiceKey)); + } + catch (Exception) + { + string errorMessage = $"Unable to convert the service name '{serviceName}' to the type '{typeof(TServiceKey).Name}'."; + throw new InvalidOperationException(errorMessage); + } + } + } + /// /// Represents a log entry. /// @@ -2533,6 +2627,8 @@ public class ServiceContainer : IServiceContainer private readonly ServiceRegistry constructorDependencyFactories = new ServiceRegistry(); private readonly ServiceRegistry propertyDependencyFactories = new ServiceRegistry(); private readonly ServiceRegistry availableServices = new ServiceRegistry(); + private readonly ConcurrentDictionary> allRegistrations = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> allEmitters = new ConcurrentDictionary>(); private readonly object lockObject = new object(); private readonly ContainerOptions options; @@ -2563,6 +2659,7 @@ public class ServiceContainer : IServiceContainer private bool isLocked; private Type defaultLifetimeType; + private int registrationOrder = 0; /// /// Initializes a new instance of the class. @@ -2596,6 +2693,7 @@ public ServiceContainer(Action configureOptions) methodSkeletonFactory = (returnType, parameterTypes) => new DynamicMethodSkeleton(returnType, parameterTypes); ScopeManagerProvider = new PerLogicalCallContextScopeManagerProvider(); AssemblyLoader = new AssemblyLoader(); + ServiceKeyConverter = new ServiceKeyConverter(); } /// @@ -2613,6 +2711,7 @@ public ServiceContainer(ContainerOptions options) o.VarianceFilter = options.VarianceFilter; o.EnableOptionalArguments = options.EnableOptionalArguments; o.OptimizeForLargeObjectGraphs = options.OptimizeForLargeObjectGraphs; + o.EnableMicrosoftCompatibility = options.EnableMicrosoftCompatibility; }) { } @@ -2690,6 +2789,12 @@ private ServiceContainer( /// public IServiceNameProvider ServiceNameProvider { get; set; } + /// + /// Gets or sets the that is responsible + /// for converting a service key from a string representation to a instance. + /// + public IServiceKeyConverter ServiceKeyConverter { get; set; } + /// /// Gets or sets the that is responsible /// for executing composition roots. @@ -2781,7 +2886,14 @@ public object InjectProperties(object instance) /// public IServiceRegistry Register(Func factory, string serviceName, ILifetime lifetime) { - RegisterServiceFromLambdaExpression(factory, lifetime, serviceName); + this.RegisterServiceFromLambdaExpression(factory, lifetime, typeof(TService), serviceName, FactoryType.Service); + return this; + } + + /// + public IServiceRegistry Register(Func factory, string serviceName, ILifetime lifetime) + { + this.RegisterServiceFromLambdaExpression(factory, lifetime, typeof(TService), serviceName, FactoryType.ServiceWithServiceKey); return this; } @@ -2799,12 +2911,23 @@ public IServiceRegistry RegisterFallback(Func predicate, Fun /// public IServiceRegistry Register(ServiceRegistration serviceRegistration) { + serviceRegistration.ServiceName ??= string.Empty; + if (serviceRegistration.Lifetime == null) + { + serviceRegistration.Lifetime = DefaultLifetime; + } + var services = GetAvailableServices(serviceRegistration.ServiceType); + registrationOrder++; + serviceRegistration.RegistrationOrder = registrationOrder; var sr = serviceRegistration; services.AddOrUpdate( serviceRegistration.ServiceName, s => AddServiceRegistration(sr), (k, existing) => UpdateServiceRegistration(existing, sr)); + var serviceKey = new ServiceKey(serviceRegistration.ServiceType, serviceRegistration.ServiceName); + var registrationList = allRegistrations.GetOrAdd(serviceKey, t => new List()); + registrationList.Add(serviceRegistration); return this; } @@ -3030,17 +3153,24 @@ public IServiceRegistry Register(string serviceName, /// public IServiceRegistry Register(Func factory, ILifetime lifetime) { - RegisterServiceFromLambdaExpression(factory, lifetime, string.Empty); + this.RegisterServiceFromLambdaExpression(factory, lifetime, typeof(TService), string.Empty, FactoryType.Service); return this; } /// public IServiceRegistry Register(Func factory, string serviceName) { - RegisterServiceFromLambdaExpression(factory, null, serviceName); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), serviceName, FactoryType.Service); return this; } + // /// + // public IServiceRegistry Register(Func factory, string serviceName) + // { + // this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), serviceName, FactoryType.Service); + // return this; + // } + /// public IServiceRegistry Register() { @@ -3095,7 +3225,7 @@ public IServiceRegistry RegisterInstance(Type serviceType, object instance, stri { Ensure.IsNotNull(instance, "instance"); Ensure.IsNotNull(serviceType, "type"); - Ensure.IsNotNull(serviceName, "serviceName"); + RegisterValue(serviceType, instance, serviceName); return this; } @@ -3103,63 +3233,63 @@ public IServiceRegistry RegisterInstance(Type serviceType, object instance, stri /// public IServiceRegistry Register(Func factory) { - RegisterServiceFromLambdaExpression(factory, null, string.Empty); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), string.Empty, FactoryType.Service); return this; } /// public IServiceRegistry Register(Func factory) { - RegisterServiceFromLambdaExpression(factory, null, string.Empty); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), string.Empty, FactoryType.ServiceWithRuntimeArguments); return this; } /// public IServiceRegistry Register(Func factory, string serviceName) { - RegisterServiceFromLambdaExpression(factory, null, serviceName); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), serviceName, FactoryType.ServiceWithRuntimeArguments); return this; } /// public IServiceRegistry Register(Func factory) { - RegisterServiceFromLambdaExpression(factory, null, string.Empty); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), string.Empty, FactoryType.ServiceWithRuntimeArguments); return this; } /// public IServiceRegistry Register(Func factory, string serviceName) { - RegisterServiceFromLambdaExpression(factory, null, serviceName); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), serviceName, FactoryType.ServiceWithRuntimeArguments); return this; } /// public IServiceRegistry Register(Func factory) { - RegisterServiceFromLambdaExpression(factory, null, string.Empty); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), string.Empty, FactoryType.ServiceWithRuntimeArguments); return this; } /// public IServiceRegistry Register(Func factory, string serviceName) { - RegisterServiceFromLambdaExpression(factory, null, serviceName); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), serviceName, FactoryType.ServiceWithRuntimeArguments); return this; } /// public IServiceRegistry Register(Func factory) { - RegisterServiceFromLambdaExpression(factory, null, string.Empty); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), string.Empty, FactoryType.ServiceWithRuntimeArguments); return this; } /// public IServiceRegistry Register(Func factory, string serviceName) { - RegisterServiceFromLambdaExpression(factory, null, serviceName); + this.RegisterServiceFromLambdaExpression(factory, null, typeof(TService), serviceName, FactoryType.ServiceWithRuntimeArguments); return this; } @@ -3359,11 +3489,18 @@ public void Dispose() disposableLifetimeInstance.Dispose(); } + List disposedObjects = new List(); var perContainerDisposables = disposableObjects.AsEnumerable().Reverse(); foreach (var perContainerDisposable in perContainerDisposables) { + disposedObjects.Add(perContainerDisposable); perContainerDisposable.Dispose(); } + + foreach (var disposed in disposedObjects) + { + disposableObjects.Remove(disposed); + } } /// @@ -3426,7 +3563,7 @@ internal object GetInstance(Type serviceType, Scope scope, string serviceName) instanceDelegate = CreateNamedDelegate(key, throwError: true); } - return instanceDelegate(constants.Items, scope); + return instanceDelegate(constants.Items.Concat(new object[] { serviceName }).ToArray(), scope); } internal IEnumerable GetAllInstances(Type serviceType, Scope scope) @@ -3538,7 +3675,7 @@ private static ConstructorDependency GetConstructorDependencyThatRepresentsDecor private static void PushRuntimeArguments(IEmitter emitter) { - MethodInfo loadMethod = typeof(RuntimeArgumentsLoader).GetTypeInfo().GetDeclaredMethod("Load"); + MethodInfo loadMethod = RuntimeArgumentsLoader.LoadMethod; emitter.Emit(OpCodes.Ldarg_0); emitter.Emit(OpCodes.Call, loadMethod); } @@ -3701,8 +3838,8 @@ private Action TryGetFallbackEmitMethod(Type serviceType, string servi if (rule != null) { emitMethod = CreateServiceEmitterBasedOnFactoryRule(rule, serviceType, serviceName); - - RegisterEmitMethod(serviceType, serviceName, emitMethod); + registrationOrder++; + RegisterEmitMethod(serviceType, serviceName, registrationOrder, emitMethod); } return emitMethod; @@ -3739,6 +3876,37 @@ private Action CreateEmitMethodWrapper(Action emitter, Type } private Action GetRegisteredEmitMethod(Type serviceType, string serviceName) + { + if (options.EnableMicrosoftCompatibility) + { + return GetRegisteredEmitMethodWithMicrosoftCompatibility(serviceType, serviceName); + } + else + { + return GetRegisteredEmitMethodWithoutMicrosoftCompatibility(serviceType, serviceName); + } + } + + private Action GetRegisteredEmitMethodWithMicrosoftCompatibility(Type serviceType, string serviceName) + { + serviceName ??= string.Empty; + + var emitters = allEmitters.GetOrAdd(new ServiceKey(serviceType, serviceName), _ => new List()); + + if (emitters.Count == 1) + { + return emitters[0].EmitMethod; + } + + if (emitters.Count > 1) + { + return emitters.Last().EmitMethod; + } + + return CreateEmitMethodForUnknownService(serviceType, serviceName); + } + + private Action GetRegisteredEmitMethodWithoutMicrosoftCompatibility(Type serviceType, string serviceName) { var registrations = GetEmitMethods(serviceType); @@ -3762,13 +3930,17 @@ private Action GetRegisteredEmitMethod(Type serviceType, string servic private ServiceRegistration AddServiceRegistration(ServiceRegistration serviceRegistration) { var emitMethod = ResolveEmitMethod(serviceRegistration); - RegisterEmitMethod(serviceRegistration.ServiceType, serviceRegistration.ServiceName, emitMethod); + RegisterEmitMethod(serviceRegistration.ServiceType, serviceRegistration.ServiceName, serviceRegistration.RegistrationOrder, emitMethod, serviceRegistration.IsCreatedFromWildcardService); return serviceRegistration; } - private void RegisterEmitMethod(Type serviceType, string serviceName, Action emitMethod) + private void RegisterEmitMethod(Type serviceType, string serviceName, int registrationOrder, Action emitMethod, bool createdFromWildcardService = false) { + // registrationOrder++; + var emitMethodInfo = new EmitMethodInfo(serviceType, emitMethod, registrationOrder, createdFromWildcardService); + allEmitters.GetOrAdd(new ServiceKey(serviceType, serviceName), _ => new List()).Add(emitMethodInfo); + GetEmitMethods(serviceType).TryAdd(serviceName, emitMethod); } @@ -3776,13 +3948,16 @@ private ServiceRegistration UpdateServiceRegistration(ServiceRegistration existi { if (isLocked) { - var message = $"Cannot overwrite existing serviceregistration {existingRegistration} after the first call to GetInstance."; + var message = $"Cannot overwrite existing service registration {existingRegistration} after the first call to GetInstance."; log.Warning(message); return existingRegistration; } Action emitMethod = ResolveEmitMethod(newRegistration); var serviceEmitters = GetEmitMethods(newRegistration.ServiceType); + registrationOrder++; + var emitMethodInfo = new EmitMethodInfo(existingRegistration.ServiceType, emitMethod, registrationOrder, false); + allEmitters.GetOrAdd(new ServiceKey(newRegistration.ServiceType, newRegistration.ServiceName), _ => new List()).Add(emitMethodInfo); serviceEmitters[newRegistration.ServiceName] = emitMethod; return newRegistration; } @@ -3941,6 +4116,20 @@ private void EmitNewInstanceUsingFactoryDelegate(ServiceRegistration serviceRegi MethodInfo invokeMethod = funcType.GetTypeInfo().GetDeclaredMethod("Invoke"); emitter.PushConstant(factoryDelegateIndex, funcType); var parameters = invokeMethod.GetParameters(); + + if (serviceRegistration.FactoryType == FactoryType.ServiceWithServiceKey) + { + var serviceFactoryIndex = constants.Add(this); + emitter.PushConstant(serviceFactoryIndex, typeof(IServiceFactory)); + var scopeManagerIndex = CreateScopeManagerIndex(); + emitter.PushConstant(scopeManagerIndex, typeof(IScopeManager)); + emitter.PushArgument(1); + emitter.Emit(OpCodes.Call, ServiceFactoryLoader.LoadServiceFactoryMethod); + emitter.Emit(OpCodes.Ldstr, serviceRegistration.ServiceName); + emitter.Call(invokeMethod); + return; + } + if (parameters.Length == 1 && parameters[0].ParameterType == typeof(ServiceRequest)) { var createServiceRequestMethod = ServiceRequestHelper.CreateServiceRequestMethod.MakeGenericMethod(serviceRegistration.ServiceType); @@ -4055,6 +4244,19 @@ private void EmitPropertyDependency(IEmitter emitter, PropertyDependency propert private Action GetEmitMethodForDependency(Dependency dependency) { + if (dependency.IsServiceKey) + { + return emitter => + { + var openGenericConvertServiceKeyMethod = typeof(IServiceKeyConverter).GetTypeInfo().GetDeclaredMethod(nameof(IServiceKeyConverter.Convert)); + var closedGenericConvertServiceKeyMethod = openGenericConvertServiceKeyMethod.MakeGenericMethod(dependency.ServiceType); + var serviceKeyConverterIndex = constants.Add(this.ServiceKeyConverter); + emitter.PushConstant(serviceKeyConverterIndex, typeof(IServiceKeyConverter)); + emitter.Emit(OpCodes.Ldstr, dependency.ConstructionInfo.ServiceName); + emitter.Emit(OpCodes.Callvirt, closedGenericConvertServiceKeyMethod); + }; + } + if (dependency.FactoryExpression != null) { return skeleton => EmitDependencyUsingFactoryExpression(skeleton, dependency); @@ -4256,9 +4458,39 @@ private void EmitPropertyDependencies(ConstructionInfo constructionInfo, IEmitte emitter.Push(instanceVariable); } + private Action CreateEmitMethodBasedOnWildcardService(Type serviceType, string serviceName) + { + var wildcardServices = allRegistrations.TryGetValue(new ServiceKey(serviceType, "*"), out var registrations) ? registrations : new List(); + if (wildcardServices.Count == 0) + { + return null; + } + + var wildcardRegistration = wildcardServices.Last(); + var serviceRegistration = new ServiceRegistration + { + ServiceType = serviceType, + ServiceName = serviceName, + FactoryExpression = wildcardRegistration.FactoryExpression, + ImplementingType = wildcardRegistration.ImplementingType, + FactoryType = wildcardRegistration.FactoryType, + Lifetime = CloneLifeTime(wildcardRegistration.Lifetime) ?? DefaultLifetime, + IsCreatedFromWildcardService = true, + }; + + Register(serviceRegistration); + return GetEmitMethod(serviceRegistration.ServiceType, serviceRegistration.ServiceName); + } + private Action CreateEmitMethodForUnknownService(Type serviceType, string serviceName) { Action emitter = null; + + if (CanUseWildcardRegistration(serviceType, serviceName)) + { + return CreateEmitMethodBasedOnWildcardService(serviceType, serviceName); + } + if (CanRedirectRequestForDefaultServiceToSingleNamedService(serviceType, serviceName)) { emitter = CreateServiceEmitterBasedOnSingleNamedInstance(serviceType); @@ -4280,19 +4512,19 @@ private Action CreateEmitMethodForUnknownService(Type serviceType, str emitter = CreateEmitMethodBasedOnClosedGenericServiceRequest(serviceType, serviceName); if (emitter == null) { - emitter = CreateEmitMethodForEnumerableServiceServiceRequest(serviceType); + emitter = CreateEmitMethodForEnumerableServiceServiceRequest(serviceType, serviceName); } } else if (serviceType.IsArray) { - emitter = CreateEmitMethodForArrayServiceRequest(serviceType); + emitter = CreateEmitMethodForArrayServiceRequest(serviceType, serviceName); } else if (serviceType.IsReadOnlyCollectionOfT() || serviceType.IsReadOnlyListOfT()) { emitter = CreateEmitMethodBasedOnClosedGenericServiceRequest(serviceType, serviceName); if (emitter == null) { - emitter = CreateEmitMethodForReadOnlyCollectionServiceRequest(serviceType); + emitter = CreateEmitMethodForReadOnlyCollectionServiceRequest(serviceType, serviceName); } } else if (serviceType.IsListOfT()) @@ -4300,7 +4532,7 @@ private Action CreateEmitMethodForUnknownService(Type serviceType, str emitter = CreateEmitMethodBasedOnClosedGenericServiceRequest(serviceType, serviceName); if (emitter == null) { - emitter = CreateEmitMethodForListServiceRequest(serviceType); + emitter = CreateEmitMethodForListServiceRequest(serviceType, serviceName); } } else if (serviceType.IsCollectionOfT()) @@ -4308,7 +4540,7 @@ private Action CreateEmitMethodForUnknownService(Type serviceType, str emitter = CreateEmitMethodBasedOnClosedGenericServiceRequest(serviceType, serviceName); if (emitter == null) { - emitter = CreateEmitMethodForListServiceRequest(serviceType); + emitter = CreateEmitMethodForListServiceRequest(serviceType, serviceName); } } else if (serviceType.IsClosedGeneric()) @@ -4408,16 +4640,16 @@ private Action CreateServiceEmitterBasedOnFactoryRule(FactoryRule rule return emitter => EmitNewInstanceWithDecorators(serviceRegistration, emitter); } - private Action CreateEmitMethodForArrayServiceRequest(Type serviceType) + private Action CreateEmitMethodForArrayServiceRequest(Type serviceType, string serviceName) { - Action enumerableEmitter = CreateEmitMethodForEnumerableServiceServiceRequest(serviceType); + Action enumerableEmitter = CreateEmitMethodForEnumerableServiceServiceRequest(serviceType, serviceName); return enumerableEmitter; } - private Action CreateEmitMethodForListServiceRequest(Type serviceType) + private Action CreateEmitMethodForListServiceRequest(Type serviceType, string serviceName) { // Note replace this with getEmitMethod(); - Action enumerableEmitter = CreateEmitMethodForEnumerableServiceServiceRequest(serviceType); + Action enumerableEmitter = CreateEmitMethodForEnumerableServiceServiceRequest(serviceType, serviceName); MethodInfo openGenericToArrayMethod = typeof(Enumerable).GetTypeInfo().GetDeclaredMethod("ToList"); MethodInfo closedGenericToListMethod = openGenericToArrayMethod.MakeGenericMethod(TypeHelper.GetElementType(serviceType)); @@ -4428,14 +4660,14 @@ private Action CreateEmitMethodForListServiceRequest(Type serviceType) }; } - private Action CreateEmitMethodForReadOnlyCollectionServiceRequest(Type serviceType) + private Action CreateEmitMethodForReadOnlyCollectionServiceRequest(Type serviceType, string serviceName) { Type elementType = TypeHelper.GetElementType(serviceType); Type closedGenericReadOnlyCollectionType = typeof(ReadOnlyCollection<>).MakeGenericType(elementType); ConstructorInfo constructorInfo = - closedGenericReadOnlyCollectionType.GetTypeInfo().DeclaredConstructors.Single(); + closedGenericReadOnlyCollectionType.GetTypeInfo().DeclaredConstructors.Where(c => c.GetParameters().Length == 1).Single(); - Action listEmitMethod = CreateEmitMethodForListServiceRequest(serviceType); + Action listEmitMethod = CreateEmitMethodForListServiceRequest(serviceType, serviceName); return emitter => { @@ -4546,16 +4778,24 @@ Action RegisterAndGetEmitMethod() } } - private Action CreateEmitMethodForEnumerableServiceServiceRequest(Type serviceType) + private Action CreateEmitMethodForEnumerableServiceServiceRequest(Type serviceType, string serviceName) { Type actualServiceType = TypeHelper.GetElementType(serviceType); if (actualServiceType.GetTypeInfo().IsGenericType) { Type openGenericServiceType = actualServiceType.GetGenericTypeDefinition(); - var openGenericServiceRegistrations = GetOpenGenericServiceRegistrations(openGenericServiceType); + ICollection openGenericServiceRegistrations; + if (options.EnableMicrosoftCompatibility) + { + openGenericServiceRegistrations = allRegistrations.SelectMany(r => r.Value).Where(r => r.ServiceType == openGenericServiceType).ToList(); + } + else + { + openGenericServiceRegistrations = GetOpenGenericServiceRegistrations(openGenericServiceType).Values; + } - var constructableOpenGenericServices = openGenericServiceRegistrations.Values.Select(r => new { r.Lifetime, r.ServiceName, closedGenericImplementingType = GenericArgumentMapper.TryMakeGenericType(actualServiceType, r.ImplementingType) }) + var constructableOpenGenericServices = openGenericServiceRegistrations.Select(r => new { r.RegistrationOrder, r.Lifetime, r.ServiceName, closedGenericImplementingType = GenericArgumentMapper.TryMakeGenericType(actualServiceType, r.ImplementingType) }) .Where(t => t.closedGenericImplementingType != null); foreach (var constructableOpenGenericService in constructableOpenGenericServices) @@ -4566,8 +4806,10 @@ private Action CreateEmitMethodForEnumerableServiceServiceRequest(Type ImplementingType = constructableOpenGenericService.closedGenericImplementingType, ServiceName = constructableOpenGenericService.ServiceName, Lifetime = CloneLifeTime(constructableOpenGenericService.Lifetime) ?? DefaultLifetime, + RegistrationOrder = constructableOpenGenericService.RegistrationOrder, }; - Register(serviceRegistration); + + AddServiceRegistration(serviceRegistration); } } @@ -4575,14 +4817,54 @@ private Action CreateEmitMethodForEnumerableServiceServiceRequest(Type if (options.EnableVariance && options.VarianceFilter(serviceType)) { - emitMethods = emitters + if (options.EnableMicrosoftCompatibility) + { + if (serviceName == "*") + { + emitMethods = allEmitters.Keys.Where(k => actualServiceType.IsAssignableFrom(k.ServiceType) && k.ServiceName.Length > 0).SelectMany(k => allEmitters[k]).Where(emi => !emi.CreatedFromWildcardService).OrderBy(emi => emi.RegistrationOrder).Select(emi => emi.EmitMethod).ToList(); + } + else + if (serviceName.Length > 0) + { + emitMethods = allEmitters.Keys.Where(k => actualServiceType.IsAssignableFrom(k.ServiceType) && (k.ServiceName == serviceName || k.ServiceName == "*")).SelectMany(k => allEmitters[k]).Where(emi => !emi.CreatedFromWildcardService).OrderBy(emi => emi.RegistrationOrder).Select(emi => emi.EmitMethod).ToList(); + } + else + { + var serviceKeys = allEmitters.Keys.Where(k => actualServiceType.IsAssignableFrom(k.ServiceType) && k.ServiceName == serviceName).ToList(); + emitMethods = serviceKeys.SelectMany(k => allEmitters[k]).OrderBy(emi => emi.RegistrationOrder).Select(emi => emi.EmitMethod).ToList(); + } + } + else + { + emitMethods = emitters .Where(kv => actualServiceType.GetTypeInfo().IsAssignableFrom(kv.Key.GetTypeInfo())) .SelectMany(kv => kv.Value).OrderBy(kv => kv.Key).Select(kv => kv.Value) .ToList(); + } } else { - emitMethods = GetEmitMethods(actualServiceType).OrderBy(kv => kv.Key).Select(kv => kv.Value).ToList(); + if (options.EnableMicrosoftCompatibility) + { + if (serviceName == "*") + { + emitMethods = allEmitters.Keys.Where(k => actualServiceType == k.ServiceType && k.ServiceName.Length > 0).SelectMany(k => allEmitters[k]).Where(emi => !emi.CreatedFromWildcardService).OrderBy(emi => emi.RegistrationOrder).Select(emi => emi.EmitMethod).ToList(); + } + else + if (serviceName.Length > 0) + { + emitMethods = allEmitters.Keys.Where(k => actualServiceType == k.ServiceType && (k.ServiceName == serviceName || k.ServiceName == "*")).SelectMany(k => allEmitters[k]).Where(emi => !emi.CreatedFromWildcardService).OrderBy(emi => emi.RegistrationOrder).Select(emi => emi.EmitMethod).ToList(); + } + else + { + var serviceKeys = allEmitters.Keys.Where(k => actualServiceType == k.ServiceType && k.ServiceName == serviceName).ToList(); + emitMethods = serviceKeys.SelectMany(k => allEmitters[k]).OrderBy(emi => emi.RegistrationOrder).Select(emi => emi.EmitMethod).ToList(); + } + } + else + { + emitMethods = GetEmitMethods(actualServiceType).OrderBy(kv => kv.Key).Select(kv => kv.Value).ToList(); + } } if (dependencyStack.Count > 0 && emitMethods.Contains(dependencyStack.Peek())) @@ -4600,7 +4882,17 @@ private Action CreateServiceEmitterBasedOnSingleNamedInstance(Type ser private bool CanRedirectRequestForDefaultServiceToSingleNamedService(Type serviceType, string serviceName) { - return string.IsNullOrEmpty(serviceName) && GetEmitMethods(serviceType).Count == 1; + return string.IsNullOrEmpty(serviceName) && GetEmitMethods(serviceType).Count == 1 && options.EnableMicrosoftCompatibility == false; + } + + private bool CanUseWildcardRegistration(Type serviceType, string serviceName) + { + if (string.IsNullOrWhiteSpace(serviceName) || !options.EnableMicrosoftCompatibility || serviceType.IsEnumerableOfT()) + { + return false; + } + + return allEmitters.Keys.Any(k => k.ServiceType == serviceType && k.ServiceName == "*"); } private ConstructionInfo GetConstructionInfo(Registration registration) @@ -4702,7 +4994,7 @@ private void EmitNewInstanceWithDecorators(ServiceRegistration serviceRegistrati EmitNewInstance(serviceRegistration, emitter); } - if (serviceRegistration.Lifetime is PerContainerLifetime && IsNotServiceFactory(serviceRegistration.ServiceType)) + if (serviceRegistration.Lifetime is PerContainerLifetime && IsNotServiceFactory(serviceRegistration.ServiceType) && serviceRegistration.Value == null) { var closedGenericTrackInstanceMethod = OpenGenericTrackInstanceMethod.MakeGenericMethod(emitter.StackType); var containerIndex = constants.Add(this); @@ -4710,7 +5002,6 @@ private void EmitNewInstanceWithDecorators(ServiceRegistration serviceRegistrati emitter.Emit(OpCodes.Call, closedGenericTrackInstanceMethod); } - var processors = initializers.Items.Where(i => i.Predicate(serviceRegistration)).ToArray(); if (processors.Length == 0) { @@ -4738,7 +5029,6 @@ private void EmitNewInstanceWithDecorators(ServiceRegistration serviceRegistrati emitter.Push(instanceVariable); - bool IsNotServiceFactory(Type serviceType) { return !typeof(IServiceFactory).GetTypeInfo().IsAssignableFrom(serviceType.GetTypeInfo()); @@ -4756,7 +5046,6 @@ private int GetInstanceDelegateIndex(ServiceRegistration serviceRegistration, Ac return servicesToDelegatesIndex.GetOrAdd(serviceRegistration, _ => CreateInstanceDelegateIndex(emitMethod)); } - private void EmitLifetime(ServiceRegistration serviceRegistration, Action emitMethod, IEmitter emitter) { if (serviceRegistration.Lifetime == null) @@ -4770,8 +5059,6 @@ private void EmitLifetime(ServiceRegistration serviceRegistration, Action CreateInstanceDelegateIndex(emitMethod)); @@ -4779,7 +5066,7 @@ private void EmitLifetime(ServiceRegistration serviceRegistration, Action k.ServiceType == serviceType && k.ServiceName == serviceName); + if (key != null) + { + servicesToDelegatesIndex.Remove(key); + } + throw new InvalidOperationException( string.Format("Unable to resolve type: {0}, service name: {1}", serviceType, serviceName), ex); @@ -4970,18 +5261,6 @@ private void RegisterValue(Type serviceType, object value, string serviceName) Register(serviceRegistration); } - private void RegisterServiceFromLambdaExpression(Delegate factory, ILifetime lifetime, string serviceName) - { - var serviceRegistration = new ServiceRegistration - { - ServiceType = typeof(TService), - FactoryExpression = factory, - ServiceName = serviceName, - Lifetime = lifetime ?? DefaultLifetime, - }; - Register(serviceRegistration); - } - private struct ClosedGenericCandidate { public ClosedGenericCandidate(Type closedGenericImplementingType, ILifetime lifetime) @@ -5269,6 +5548,11 @@ public LazyConcurrentDictionary() this.concurrentDictionary = new ConcurrentDictionary>(); } + /// + /// Gets the keys in the dictionary. + /// + public IEnumerable Keys => concurrentDictionary.Keys; + /// /// Adds a key/value pair to the /// by using the specified function if the key does not already exist, or returns @@ -5284,7 +5568,7 @@ public TValue GetOrAdd(TKey key, Func valueFactory) } /// - /// Determines if the dictionary contains an uninitialized value with the given . + /// Determines if the dictionary contains an uninitialized value with the given . /// /// The key for which to check for an uninitialized value. /// true if the dictionary contains an uninitialized value at the given . @@ -5294,11 +5578,12 @@ public bool ContainsUninitializedValue(TKey key) { return !value.IsValueCreated; } + return false; } /// - /// Removes an entry with the given if it exists it the dictionary. + /// Removes an entry with the given if it exists it the dictionary. /// /// The key for which to remove an entry. public void Remove(TKey key) @@ -5875,6 +6160,7 @@ public MostResolvableConstructorSelector(Func canGetInstance public ConstructorInfo Execute(Type implementingType) { ConstructorInfo[] constructorCandidates = implementingType.GetTypeInfo().DeclaredConstructors.Where(c => c.IsPublic && !c.IsStatic).ToArray(); + if (constructorCandidates.Length == 0) { throw new InvalidOperationException("Missing public constructor for Type: " + implementingType.FullName); @@ -5914,7 +6200,9 @@ private bool CanCreateParameterDependencies(IEnumerable parameter private bool CanCreateParameterDependency(ParameterInfo parameterInfo) { - return canGetInstance(parameterInfo.ParameterType, string.Empty) || canGetInstance(parameterInfo.ParameterType, GetServiceName(parameterInfo)) || (parameterInfo.HasDefaultValue && enableOptionalArguments); + var test = parameterInfo.GetCustomAttributes().Any(a => a.GetType().Name == "ServiceKeyAttribute"); + var result = canGetInstance(parameterInfo.ParameterType, string.Empty) || canGetInstance(parameterInfo.ParameterType, GetServiceName(parameterInfo)) || (parameterInfo.HasDefaultValue && enableOptionalArguments) || parameterInfo.GetCustomAttributes().Any(a => a.GetType().Name == "ServiceKeyAttribute"); + return result; } } @@ -5935,6 +6223,7 @@ public virtual IEnumerable Execute(ConstructorInfo constr { ServiceName = string.Empty, ServiceType = p.ParameterType, + IsServiceKey = p.GetCustomAttributes().Any(a => a.GetType().Name == "ServiceKeyAttribute"), Parameter = p, IsRequired = true, }); @@ -6027,6 +6316,20 @@ public ConstructionInfo Execute(Registration registration) constructionInfo.PropertyDependencies.AddRange(GetPropertyDependencies(implementingType)); constructionInfo.Constructor = constructorSelector.Execute(implementingType); constructionInfo.ConstructorDependencies.AddRange(GetConstructorDependencies(constructionInfo.Constructor)); + foreach (var constructorDependency in constructionInfo.ConstructorDependencies) + { + constructorDependency.ConstructionInfo = constructionInfo; + } + + foreach (var propertyDependency in constructionInfo.PropertyDependencies) + { + propertyDependency.ConstructionInfo = constructionInfo; + } + + if (registration is ServiceRegistration serviceRegistration) + { + constructionInfo.ServiceName = serviceRegistration.ServiceName; + } return constructionInfo; } @@ -6193,6 +6496,21 @@ public class ServiceRegistration : Registration /// public object Value { get; set; } + /// + /// Gets or sets the service registration order. + /// + public int RegistrationOrder { get; set; } + + /// + /// Gets or sets the that represents the type of factory used to create the service. + /// + public FactoryType FactoryType { get; set; } + + /// + /// Gets or sets a value indicating whether the service is created from a wildcard registration. + /// + internal bool IsCreatedFromWildcardService { get; set; } + /// /// Serves as a hash function for a particular type. /// @@ -6218,7 +6536,24 @@ public override bool Equals(object obj) return false; } - var result = ServiceName == other.ServiceName && ServiceType == other.ServiceType; + // Need to include ImplementingType, FactoryExpression and Lifetime in the comparison + var result = ServiceName == other.ServiceName && ServiceType == other.ServiceType && ImplementingType == other.ImplementingType; + if (result && Lifetime != null && other.Lifetime != null) + { + result = Lifetime.Equals(other.Lifetime); + } + + if (result && ImplementingType != null && other.ImplementingType != null) + { + result = ImplementingType == other.ImplementingType; + } + + // Cannot compare delegates like this. + if (result && FactoryExpression != null && other.FactoryExpression != null) + { + result = FactoryExpression.Equals(other.FactoryExpression); + } + return result; } @@ -6335,6 +6670,11 @@ public ConstructionInfo() /// Gets or sets the function delegate to be used to create the service instance. /// public Delegate FactoryDelegate { get; set; } + + /// + /// Gets or sets the service name the service that this represents. + /// + public string ServiceName { get; set; } } /// @@ -6367,6 +6707,16 @@ public abstract class Dependency /// public bool IsRequired { get; set; } + /// + /// Gets or sets a value indicating whether this parameter represents the service key/name. + /// + public bool IsServiceKey { get; set; } + + /// + /// Gets or sets the that represents the construction information. + /// + public ConstructionInfo ConstructionInfo { get; set; } + /// /// Returns textual information about the dependency. /// @@ -6690,9 +7040,14 @@ public Scope(ServiceContainer serviceFactory) /// /// Registers the so that it is disposed when the scope is completed. /// - /// The or object to register. + /// The to register. public void TrackInstance(object disposable) { + if (disposable is Scope) + { + return; + } + lock (lockObject) { if (disposableObjects == null) @@ -6731,6 +7086,7 @@ public void Dispose() EndScope(); } + #if USE_ASYNCDISPOSABLE /// public ValueTask DisposeAsync() @@ -6792,6 +7148,7 @@ static async ValueTask Await(int i, ValueTask vt, List toDispose, HashSe { continue; } + if (objectToDispose is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync().ConfigureAwait(false); @@ -6804,15 +7161,8 @@ static async ValueTask Await(int i, ValueTask vt, List toDispose, HashSe } } #endif - private void EndScope() - { - scopeManager?.EndScope(this); - var completedHandler = Completed; - completedHandler?.Invoke(this, new EventArgs()); - IsDisposed = true; - } - /// + /// public Scope BeginScope() => serviceFactory.BeginScope(); /// @@ -6861,9 +7211,9 @@ internal object GetScopedInstance(GetInstanceDelegate getInstanceDelegate, objec if (createdInstance == null) { createdInstance = getInstanceDelegate(arguments, this); -#if USE_ASYNCDISPOSABLE +#if USE_ASYNCDISPOSABLE if (createdInstance is IDisposable || createdInstance is IAsyncDisposable) -#else +#else if (createdInstance is IDisposable) #endif { @@ -6877,6 +7227,14 @@ internal object GetScopedInstance(GetInstanceDelegate getInstanceDelegate, objec } } + private void EndScope() + { + scopeManager?.EndScope(this); + var completedHandler = Completed; + completedHandler?.Invoke(this, new EventArgs()); + IsDisposed = true; + } + private class ReferenceEqualityComparer : IEqualityComparer { public static readonly ReferenceEqualityComparer Default @@ -7162,7 +7520,7 @@ public void Execute(TCompositionRoot compositionRoot) } /// - /// A class that maps the generic arguments/parameters from a generic servicetype + /// A class that maps the generic arguments/parameters from a generic service type /// to a open generic implementing type. /// public class GenericArgumentMapper : IGenericArgumentMapper @@ -7176,9 +7534,6 @@ public class GenericArgumentMapper : IGenericArgumentMapper /// A . public GenericMappingResult Map(Type genericServiceType, Type openGenericImplementingType) { - // string[] genericParameterNames = GetGenericArgumentsOrParameters(genericServiceType).Select(t => t.Name).ToArray(); - - string[] genericParameterNames = openGenericImplementingType.GetTypeInfo().GenericTypeParameters.Select(t => t.Name).ToArray(); @@ -8870,4 +9225,52 @@ public static Type GetEnumerableType(this Type returnType) => private static Type CreateEnumerableType(Type type) => typeof(IEnumerable<>).MakeGenericType(type); } + + internal class EmitMethodInfo + { + public EmitMethodInfo(Type serviceType, Action emitMethod, int registrationOrder, bool createdFromWildCardService) + { + ServiceType = serviceType; + EmitMethod = emitMethod; + RegistrationOrder = registrationOrder; + CreatedFromWildcardService = createdFromWildCardService; + } + + public Type ServiceType { get; } + + public Action EmitMethod { get; private set; } + + public int RegistrationOrder { get; private set; } + + public bool CreatedFromWildcardService { get; private set; } + } + + internal class ServiceKey + { + public ServiceKey(Type serviceType, string serviceName) + { + ServiceType = serviceType; + ServiceName = serviceName; + } + + public string ServiceName { get; private set; } = string.Empty; + + public Type ServiceType { get; private set; } + + public override int GetHashCode() + { + return ServiceType.GetHashCode() ^ ServiceName.GetHashCode(); + } + + public override bool Equals(object obj) + { + var other = obj as ServiceKey; + return ServiceType == other.ServiceType && ServiceName == other.ServiceName; + } + + public override string ToString() + { + return $"{ServiceType} - {ServiceName}"; + } + } } diff --git a/src/LightInject/LightInject.csproj b/src/LightInject/LightInject.csproj index 0b54148d..7acd2816 100644 --- a/src/LightInject/LightInject.csproj +++ b/src/LightInject/LightInject.csproj @@ -1,7 +1,7 @@  - net6.0;netstandard2.0;netcoreapp3.1;net462 + net8.0;netstandard2.0;netcoreapp3.1;net462 6.6.4 Bernhard Richter https://www.lightinject.net @@ -34,17 +34,17 @@ - + USE_ASYNCDISPOSABLE - + all runtime; build; native; contentfiles; analyzers