diff --git a/src/Castle.Facilities.AspNet.Mvc.Tests/Castle.Facilities.AspNet.Mvc.Tests.csproj b/src/Castle.Facilities.AspNet.Mvc.Tests/Castle.Facilities.AspNet.Mvc.Tests.csproj index ccb068647..11cd2e890 100644 --- a/src/Castle.Facilities.AspNet.Mvc.Tests/Castle.Facilities.AspNet.Mvc.Tests.csproj +++ b/src/Castle.Facilities.AspNet.Mvc.Tests/Castle.Facilities.AspNet.Mvc.Tests.csproj @@ -15,11 +15,11 @@ - + - + diff --git a/src/Castle.Facilities.AspNet.SystemWeb.Tests/Castle.Facilities.AspNet.SystemWeb.Tests.csproj b/src/Castle.Facilities.AspNet.SystemWeb.Tests/Castle.Facilities.AspNet.SystemWeb.Tests.csproj index 76f9f3266..2667d9d4a 100644 --- a/src/Castle.Facilities.AspNet.SystemWeb.Tests/Castle.Facilities.AspNet.SystemWeb.Tests.csproj +++ b/src/Castle.Facilities.AspNet.SystemWeb.Tests/Castle.Facilities.AspNet.SystemWeb.Tests.csproj @@ -19,10 +19,10 @@ - + - + diff --git a/src/Castle.Facilities.AspNet.WebApi.Tests/Castle.Facilities.AspNet.WebApi.Tests.csproj b/src/Castle.Facilities.AspNet.WebApi.Tests/Castle.Facilities.AspNet.WebApi.Tests.csproj index af3796890..dbd7491c2 100644 --- a/src/Castle.Facilities.AspNet.WebApi.Tests/Castle.Facilities.AspNet.WebApi.Tests.csproj +++ b/src/Castle.Facilities.AspNet.WebApi.Tests/Castle.Facilities.AspNet.WebApi.Tests.csproj @@ -15,11 +15,11 @@ - + - + diff --git a/src/Castle.Facilities.AspNetCore.Tests/Castle.Facilities.AspNetCore.Tests.csproj b/src/Castle.Facilities.AspNetCore.Tests/Castle.Facilities.AspNetCore.Tests.csproj index 4efbaf31d..195b1176e 100644 --- a/src/Castle.Facilities.AspNetCore.Tests/Castle.Facilities.AspNetCore.Tests.csproj +++ b/src/Castle.Facilities.AspNetCore.Tests/Castle.Facilities.AspNetCore.Tests.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/Castle.Facilities.WcfIntegration.Tests/Castle.Facilities.WcfIntegration.Tests.csproj b/src/Castle.Facilities.WcfIntegration.Tests/Castle.Facilities.WcfIntegration.Tests.csproj index 11b5401dc..82b2bed24 100644 --- a/src/Castle.Facilities.WcfIntegration.Tests/Castle.Facilities.WcfIntegration.Tests.csproj +++ b/src/Castle.Facilities.WcfIntegration.Tests/Castle.Facilities.WcfIntegration.Tests.csproj @@ -12,9 +12,9 @@ - + - + diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/Castle.Windsor.Extensions.DependencyInjection.Tests.csproj b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/Castle.Windsor.Extensions.DependencyInjection.Tests.csproj index aa6c272d3..327125b31 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/Castle.Windsor.Extensions.DependencyInjection.Tests.csproj +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/Castle.Windsor.Extensions.DependencyInjection.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/CustomAssumptionTests.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/CustomAssumptionTests.cs index ac83c0f0a..df3456951 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/CustomAssumptionTests.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/CustomAssumptionTests.cs @@ -1,6 +1,6 @@ #if NET8_0_OR_GREATER +using Castle.MicroKernel; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Specification.Fakes; using System; using System.Linq; using System.Threading; @@ -110,6 +110,21 @@ public void Scoped_service_resolved_outside_scope() Assert.Equal(resolvedOutsideScope, resolvedAgainOutsideScope); } + [Fact] + public void Mix_of_keyed_and_not_keyed() + { + var serviceCollection = GetServiceCollection(); + serviceCollection.AddSingleton(); + serviceCollection.AddKeyedSingleton("bla"); + + _serviceProvider = BuildServiceProvider(serviceCollection); + + //can resolve the non-keyed + var nonKeyed = _serviceProvider.GetRequiredService(); + Assert.NotNull(nonKeyed); + Assert.IsType(nonKeyed); + } + [Fact] public void Scoped_service_resolved_outside_scope_in_another_thread() { @@ -167,7 +182,8 @@ public async void Simulate_async_timer_without_wait() ITestService resolvedInThread = null; async Task ExecuteAsync() { - while (!stop) + DateTime start = DateTime.UtcNow; + while (!stop && DateTime.UtcNow.Subtract(start).TotalSeconds < 10) { await Task.Delay(100); if (shouldResolve) @@ -178,10 +194,7 @@ async Task ExecuteAsync() } } //fire and forget -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed var task = ExecuteAsync(); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - await Task.Delay(500); var serviceCollection = GetServiceCollection(); @@ -353,7 +366,6 @@ public void TryToResolveScopedInOtherThread() Assert.True(task.Result); } - protected override void Dispose(bool disposing) { base.Dispose(disposing); @@ -364,16 +376,12 @@ protected override void Dispose(bool disposing) } } - internal class TestService : ITestService - { - } + internal class TestService : ITestService; - internal class AnotherTestService : ITestService - { - } + internal class AnotherTestService : ITestService; - internal interface ITestService - { - } + internal class ThirdTestService : ITestService; + + internal interface ITestService; } #endif \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorKeyedDependencyInjectionSpecificationTests.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorKeyedDependencyInjectionSpecificationTests.cs index 6fc401577..c4bcdb1da 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorKeyedDependencyInjectionSpecificationTests.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorKeyedDependencyInjectionSpecificationTests.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Specification; using System; -using Xunit; namespace Castle.Windsor.Extensions.DependencyInjection.Tests { diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorScopedServiceProviderCustomWindsorContainerTests.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorScopedServiceProviderCustomWindsorContainerTests.cs index 54f909b97..34bf9b424 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorScopedServiceProviderCustomWindsorContainerTests.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorScopedServiceProviderCustomWindsorContainerTests.cs @@ -53,6 +53,29 @@ public void Dispose() GC.SuppressFinalize(this); } + ///// + ///// To verify when a single test failed, open the corresponding test in dotnet/runtime repository, + ///// then copy the test here, change name and execute with debugging etc etc. + ///// This helps because source link support seems to be not to easy to use from the test runner + ///// and this tricks makes everything really simpler. + ///// + //[Fact] + //public void ClosedServicesPreferredOverOpenGenericServices_custom() + //{ + // // Arrange + // var collection = new TestServiceCollection(); + // collection.AddTransient(typeof(IFakeOpenGenericService), typeof(FakeService)); + // collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + // collection.AddSingleton(); + // var provider = CreateServiceProvider(collection); + + // // Act + // var service = provider.GetService>(); + + // // Assert + // Assert.IsType(service); + //} + #if NET6_0_OR_GREATER #endif diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs index 1ccb1935e..7e2c1a696 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs @@ -14,6 +14,7 @@ namespace Castle.Windsor.Extensions.DependencyInjection { + using Castle.MicroKernel; using Castle.MicroKernel.Handlers; using Castle.Windsor; using Castle.Windsor.Extensions.DependencyInjection.Scope; @@ -99,15 +100,53 @@ private object ResolveInstanceOrNull(Type serviceType, bool isOptional) //this is complicated by the concept of keyed service, because if you are about to resolve WITHOUTH KEY you do not //need to resolve keyed services. Now Keyed services are available only in version 8 but we register with an helper //all registered services so we can know if a service was really registered with keyed service or not. - var componentRegistration = container.Kernel.GetHandler(serviceType); - if (componentRegistration.ComponentModel.Name.StartsWith(KeyedRegistrationHelper.KeyedRegistrationPrefix)) + var componentRegistrations = container.Kernel.GetHandlers(serviceType); + + //now since the caller requested a NON Keyed component, we need to skip all keyed components. + var realRegistrations = componentRegistrations.Where(x => !x.ComponentModel.Name.StartsWith(KeyedRegistrationHelper.KeyedRegistrationPrefix)).ToList(); + string registrationName = null; + if (realRegistrations.Count == 1) + { + registrationName = realRegistrations[0].ComponentModel.Name; + } + else if (realRegistrations.Count == 0) + { + //No component is registered for the interface without key, resolution cannot be done. + registrationName = null; + } + else if (realRegistrations.Count > 1) + { + //more than one component is registered for the interface without key, we have some ambiguity that is resolved, based on test + //found in framework with this rule. + //1. Last component win. + //2. closed service are preferred over open generic. + + //take first non generic + for (int i = realRegistrations.Count - 1; i >= 0; i--) + { + if (!realRegistrations[i].ComponentModel.Implementation.IsGenericTypeDefinition) + { + registrationName = realRegistrations[i].ComponentModel.Name; + break; + } + } + + //if we did not find any non generic, take the last one. + if (registrationName == null) + { + registrationName = realRegistrations[realRegistrations.Count - 1].ComponentModel.Name; + } + } + + if (registrationName == null) { - //Component was registered as keyed component, so we really need to resolve with null because this is the old interface - //so no key is provided. return null; } -#endif + return container.Resolve(registrationName, serviceType); +#else + //no keyed component in previous framework, just resolve. return container.Resolve(serviceType); +#endif } if (serviceType.GetTypeInfo().IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) diff --git a/src/Castle.Windsor.Tests/Castle.Windsor.Tests.csproj b/src/Castle.Windsor.Tests/Castle.Windsor.Tests.csproj index 474f70187..1a30263e0 100644 --- a/src/Castle.Windsor.Tests/Castle.Windsor.Tests.csproj +++ b/src/Castle.Windsor.Tests/Castle.Windsor.Tests.csproj @@ -47,7 +47,7 @@ - +