diff --git a/README.md b/README.md
index 9739ab5c4..aced57fdb 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,35 @@ Castle Windsor is a best of breed, mature Inversion of Control container availab
See the [documentation](docs/README.md).
+## Considerations
+
+Castle.Windsor.Extensions.DependencyInjection try to make Microsoft Dependency Injection works with Castle.Windsor. We have some
+really different rules in the two world, one is the order of resolution exposed by the test Resolve_order_in_castle that shows
+how the two have two different strategies.
+
+1. Microsof DI want to resolve the last registered service
+2. Castle.Windsor want to resolve the first registered service.
+
+This is one of the point where the integration become painful, because it can happen that the very same service got resolved
+in two distinct way, depending on who is resolving the service.
+
+ The preferred solution is to understand who is registering the service and resolve everything accordingly.
+
+## I want to try everything locally.
+
+If you want to easily try a local compiled version on your project you can use the following trick.
+
+1. Add the GenerateAssemblyInfo to false on the project file
+1. Add an assemblyinfo.cs in Properties folder and add the [assembly: AssemblyVersion("6.0.0")] attribute to force the correct version
+1. Compile the project
+1. Copy into the local nuget cache, from the output folder of this project run
+
+```
+copy * %Uer Profile%\.nuget\packages\castle.windsor.extensions.dependencyinjection\6.0.x\lib\net8.0
+```
+
+This usually works.
+
## Releases
See the [releases](https://github.com/castleproject/Windsor/releases).
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 327125b31..71767ccad 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
@@ -33,6 +33,7 @@
+
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/CustomAssumptionTests.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/CustomAssumptionTests.cs
index 5b2d6458d..aac248290 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/CustomAssumptionTests.cs
+++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/CustomAssumptionTests.cs
@@ -369,11 +369,38 @@ public void TryToResolveScopedInOtherThread()
[Fact]
public void Resolve_order_in_castle()
+ {
+ var serviceCollection = GetServiceCollection();
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
+ var provider = BuildServiceProvider(serviceCollection);
+
+
+ var castleContainer = new WindsorContainer();
+ castleContainer.Register(
+ Component.For().ImplementedBy()
+ , Component.For().ImplementedBy());
+
+ var resolvedWithCastle = castleContainer.Resolve();
+ var resolvedWithProvider = provider.GetRequiredService();
+
+ //SUper important: Assumption for resolve multiple services registerd with the same
+ //interface is different: castle resolves the first, Microsoft DI require you to
+ //resolve the latest.
+ Assert.IsType(resolvedWithCastle);
+ Assert.IsType(resolvedWithProvider);
+ }
+
+ [Fact]
+ public void If_we_register_through_container_resolution_is_castle()
{
var serviceCollection = GetServiceCollection();
_factory = new WindsorServiceProviderFactory();
_container = _factory.CreateBuilder(serviceCollection);
+ //We are recording component with castle, it is not important that we resolve
+ //with castle or with the adapter, we use castle rules because who registered
+ //the components wants probably castle semantic.
_container.Register(
Component.For().ImplementedBy()
, Component.For().ImplementedBy());
@@ -387,6 +414,26 @@ public void Resolve_order_in_castle()
//interface is different: castle resolves the first, Microsoft DI require you to
//resolve the latest.
Assert.IsType(resolvedWithCastle);
+ Assert.IsType(resolvedWithProvider);
+ }
+
+ [Fact]
+ public void If_we_register_through_adapter_resolution_is_microsoft()
+ {
+ var serviceCollection = GetServiceCollection();
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
+ _factory = new WindsorServiceProviderFactory();
+ _container = _factory.CreateBuilder(serviceCollection);
+ var provider = _factory.CreateServiceProvider(_container);
+
+ var resolvedWithCastle = _container.Resolve();
+ var resolvedWithProvider = provider.GetRequiredService();
+
+ //SUper important: Assumption for resolve multiple services registerd with the same
+ //interface is different: castle resolves the first, Microsoft DI require you to
+ //resolve the latest.
+ Assert.IsType(resolvedWithCastle);
Assert.IsType(resolvedWithProvider);
}
@@ -398,7 +445,9 @@ public void Resolve_order_in_castle_with_is_default()
_container = _factory.CreateBuilder(serviceCollection);
_container.Register(
- Component.For().ImplementedBy().IsDefault()
+ Component.For().ImplementedBy()
+ .IsDefault()
+ .ExtendedProperties(new Property("porcodio", "porcamadonna"))
, Component.For().ImplementedBy());
var provider = _factory.CreateServiceProvider(_container);
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Castle.Windsor.Extensions.DependencyInjection.csproj b/src/Castle.Windsor.Extensions.DependencyInjection/Castle.Windsor.Extensions.DependencyInjection.csproj
index 9884c8b7a..2ede9a6be 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection/Castle.Windsor.Extensions.DependencyInjection.csproj
+++ b/src/Castle.Windsor.Extensions.DependencyInjection/Castle.Windsor.Extensions.DependencyInjection.csproj
@@ -16,6 +16,7 @@
true
Castle.Windsor.Extensions.DependencyInjection
Castle.Windsor.Extensions.DependencyInjection
+ Castle.Windsor.Extensions.DependencyInjection
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/RegistrationAdapter.cs b/src/Castle.Windsor.Extensions.DependencyInjection/RegistrationAdapter.cs
index 0781d1b51..556dd837b 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection/RegistrationAdapter.cs
+++ b/src/Castle.Windsor.Extensions.DependencyInjection/RegistrationAdapter.cs
@@ -21,6 +21,13 @@ namespace Castle.Windsor.Extensions.DependencyInjection
internal static class RegistrationAdapter
{
+ ///
+ /// This is a constants that is used as key in the extended properties of a component
+ /// when it is registered through RegistrationAdapter. This allows to understand which
+ /// is the best semantic to use when resolving the component.
+ ///
+ internal static string RegistrationKeyExtendedPropertyKey = "microsoft-di-registered";
+
public static IRegistration FromOpenGenericServiceDescriptor(
Microsoft.Extensions.DependencyInjection.ServiceDescriptor service,
IWindsorContainer windsorContainer)
@@ -66,7 +73,11 @@ public static IRegistration FromOpenGenericServiceDescriptor(
throw new System.ArgumentException("Unsupported ServiceDescriptor");
}
#endif
- return ResolveLifestyle(registration, service);
+ //Extended properties allows to understand when the service was registered through the adapter
+ //and IsDefault is needed to change the semantic of the resolution, LAST registered service win.
+ return ResolveLifestyle(registration, service)
+ .ExtendedProperties(RegistrationKeyExtendedPropertyKey)
+ .IsDefault();
}
public static IRegistration FromServiceDescriptor(
@@ -126,7 +137,9 @@ public static IRegistration FromServiceDescriptor(
registration = UsingImplementation(registration, service);
}
#endif
- return ResolveLifestyle(registration, service);
+ return ResolveLifestyle(registration, service)
+ .ExtendedProperties(RegistrationKeyExtendedPropertyKey)
+ .IsDefault();
}
public static string OriginalComponentName(string uniqueComponentName)
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs
index 6a1e439df..83ab71884 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs
+++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs
@@ -17,7 +17,6 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope
{
using Castle.Windsor;
using Microsoft.Extensions.DependencyInjection;
- using System;
internal class WindsorScopeFactory : IServiceScopeFactory
{
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs
index 1b8b84643..3189f2e65 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.Core.Logging;
using Castle.MicroKernel.Handlers;
using Castle.Windsor;
using Castle.Windsor.Extensions.DependencyInjection.Scope;
@@ -28,18 +29,24 @@ internal class WindsorScopedServiceProvider : IServiceProvider, ISupportRequired
, IServiceProviderIsService
#endif
#if NET8_0_OR_GREATER
- , IKeyedServiceProvider, IServiceProviderIsKeyedService
+ , IKeyedServiceProvider, IServiceProviderIsKeyedService
#endif
{
private readonly ExtensionContainerScopeBase scope;
private bool disposing;
-
+ private ILogger _logger = NullLogger.Instance;
private readonly IWindsorContainer container;
public WindsorScopedServiceProvider(IWindsorContainer container)
{
this.container = container;
scope = ExtensionContainerScopeCache.Current;
+
+ if (container.Kernel.HasComponent(typeof(ILoggerFactory)))
+ {
+ var loggerFactory = container.Resolve();
+ _logger = loggerFactory.Create(typeof(WindsorScopedServiceProvider));
+ }
}
public object GetService(Type serviceType)
@@ -69,7 +76,6 @@ public object GetRequiredKeyedService(Type serviceType, object serviceKey)
}
#endif
-
public object GetRequiredService(Type serviceType)
{
using (_ = new ForcedScope(scope))
@@ -114,18 +120,32 @@ private object ResolveInstanceOrNull(Type serviceType, bool isOptional)
}
else if (realRegistrations.Count > 1)
{
- //Need to honor IsDefault for castle registrations.
- var isDefaultRegistration = realRegistrations
- .FirstOrDefault(dh => dh.ComponentModel.ExtendedProperties.Any(ComponentIsDefault));
+ //ok we have a big problem, we have multiple registration and different semantic, because
+ //Microsoft.DI wants the latest registered service to win
+ //Caste instead wants the first registered service to win.
+
+ //how can we live with this to have a MINIMUM (never zero) impact on everything that registers things?
+ //we need to determine who registered the components.
+ var registeredByMicrosoftDi = realRegistrations.Any(r => r.ComponentModel.ExtendedProperties.Any(ep => RegistrationAdapter.RegistrationKeyExtendedPropertyKey.Equals(ep.Key)));
- //Remember that castle has a specific order of resolution, if someone registered something in castle with
- //IsDefault() it Must be honored.
- if (isDefaultRegistration != null)
+ if (!registeredByMicrosoftDi)
{
- registrationName = isDefaultRegistration.ComponentModel.Name;
+ if (_logger.IsDebugEnabled)
+ {
+ _logger.Debug($@"Multiple components registered for service {serviceType.FullName} All services {string.Join(",", realRegistrations.Select(r => r.ComponentModel.Implementation.Name))}");
+ }
+
+ //ok we are in a situation where no component was registered through the adapter, this is the situatino of a component
+ //registered purely in castle (this should mean that the user want to use castle semantic).
+ //let the standard castle rules apply.
+ return container.Resolve(serviceType);
}
else
{
+ //If we are here at least one of the component was registered throuh Microsoft.DI, this means that the code that regiestered
+ //the component want to use the semantic of Microsoft.DI. This means that we need to use different set of rules.
+
+ //RULES:
//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. In this situation we do not use the same rule of Castle where the first service win but
//we use the framework rule that:
@@ -148,6 +168,12 @@ private object ResolveInstanceOrNull(Type serviceType, bool isOptional)
registrationName = realRegistrations[realRegistrations.Count - 1].ComponentModel.Name;
}
}
+
+ if (_logger.IsDebugEnabled)
+ {
+ _logger.Debug($@"Multiple components registered for service {serviceType.FullName}. Selected component {registrationName}
+all services {string.Join(",", realRegistrations.Select(r => r.ComponentModel.Implementation.Name))}");
+ }
}
if (registrationName == null)