Skip to content

Commit 6adaf28

Browse files
committed
Fixed IsDefault() usage in DependencyInjectionAdapter
If multiple concrete classes are registered in castle, when you resolve castle resolves the first one. With Microsoft DI is the oposite, you want the last registered. The resolution is now fixed to honor IsDefault() because previous code registered every component with IsDefault() if it is registered from the adapter.
1 parent 17d1010 commit 6adaf28

File tree

4 files changed

+110
-30
lines changed

4 files changed

+110
-30
lines changed

.vscode/tasks.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
{
2-
"version": "0.1.0",
2+
"version": "2.0.0",
33
"command": "dotnet",
4-
"isShellCommand": true,
54
"args": [],
65
"tasks": [
76
{
8-
"taskName": "build",
7+
"label": "build",
8+
"type": "shell",
9+
"command": "dotnet",
910
"args": [
11+
"build",
1012
"${workspaceRoot}/src/Castle.Windsor.Tests/Castle.Windsor.Tests.csproj"
1113
],
12-
"isBuildCommand": true,
13-
"problemMatcher": "$msCompile"
14+
"problemMatcher": "$msCompile",
15+
"group": {
16+
"_id": "build",
17+
"isDefault": false
18+
}
1419
}
1520
]
1621
}

src/Castle.Windsor.Extensions.DependencyInjection.Tests/CustomAssumptionTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if NET8_0_OR_GREATER
22
using Castle.MicroKernel;
3+
using Castle.MicroKernel.Registration;
34
using Microsoft.Extensions.DependencyInjection;
45
using System;
56
using System.Linq;
@@ -366,6 +367,52 @@ public void TryToResolveScopedInOtherThread()
366367
Assert.True(task.Result);
367368
}
368369

370+
[Fact]
371+
public void Resolve_order_in_castle()
372+
{
373+
var serviceCollection = GetServiceCollection();
374+
_factory = new WindsorServiceProviderFactory();
375+
_container = _factory.CreateBuilder(serviceCollection);
376+
377+
_container.Register(
378+
Component.For<ITestService>().ImplementedBy<TestService>()
379+
, Component.For<ITestService>().ImplementedBy<AnotherTestService>());
380+
381+
var provider = _factory.CreateServiceProvider(_container);
382+
383+
var resolvedWithCastle = _container.Resolve<ITestService>();
384+
var resolvedWithProvider = provider.GetRequiredService<ITestService>();
385+
386+
//SUper important: Assumption for resolve multiple services registerd with the same
387+
//interface is different: castle resolves the first, Microsoft DI require you to
388+
//resolve the latest.
389+
Assert.IsType<TestService>(resolvedWithCastle);
390+
Assert.IsType<AnotherTestService>(resolvedWithProvider);
391+
}
392+
393+
[Fact]
394+
public void Resolve_order_in_castle_with_is_default()
395+
{
396+
var serviceCollection = GetServiceCollection();
397+
_factory = new WindsorServiceProviderFactory();
398+
_container = _factory.CreateBuilder(serviceCollection);
399+
400+
_container.Register(
401+
Component.For<ITestService>().ImplementedBy<TestService>().IsDefault()
402+
, Component.For<ITestService>().ImplementedBy<AnotherTestService>());
403+
404+
var provider = _factory.CreateServiceProvider(_container);
405+
406+
var resolvedWithCastle = _container.Resolve<ITestService>();
407+
var resolvedWithProvider = provider.GetRequiredService<ITestService>();
408+
409+
//SUper important: Assumption for resolve multiple services registerd with the same
410+
//interface is different: castle resolves the first, Microsoft DI require you to
411+
//resolve the latest.
412+
Assert.IsType<TestService>(resolvedWithCastle);
413+
Assert.IsType<TestService>(resolvedWithProvider);
414+
}
415+
369416
protected override void Dispose(bool disposing)
370417
{
371418
base.Dispose(disposing);

src/Castle.Windsor.Extensions.DependencyInjection/RegistrationAdapter.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ public static IRegistration FromOpenGenericServiceDescriptor(
6666
throw new System.ArgumentException("Unsupported ServiceDescriptor");
6767
}
6868
#endif
69-
return ResolveLifestyle(registration, service)
70-
.IsDefault();
69+
return ResolveLifestyle(registration, service);
7170
}
7271

7372
public static IRegistration FromServiceDescriptor(
@@ -127,8 +126,7 @@ public static IRegistration FromServiceDescriptor(
127126
registration = UsingImplementation(registration, service);
128127
}
129128
#endif
130-
return ResolveLifestyle(registration, service)
131-
.IsDefault();
129+
return ResolveLifestyle(registration, service);
132130
}
133131

134132
public static string OriginalComponentName(string uniqueComponentName)

src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
namespace Castle.Windsor.Extensions.DependencyInjection
1616
{
17-
using Castle.MicroKernel;
1817
using Castle.MicroKernel.Handlers;
1918
using Castle.Windsor;
2019
using Castle.Windsor.Extensions.DependencyInjection.Scope;
@@ -96,7 +95,6 @@ private object ResolveInstanceOrNull(Type serviceType, bool isOptional)
9695
{
9796
if (container.Kernel.HasComponent(serviceType))
9897
{
99-
#if NET8_0_OR_GREATER
10098
//this is complicated by the concept of keyed service, because if you are about to resolve WITHOUTH KEY you do not
10199
//need to resolve keyed services. Now Keyed services are available only in version 8 but we register with an helper
102100
//all registered services so we can know if a service was really registered with keyed service or not.
@@ -105,7 +103,7 @@ private object ResolveInstanceOrNull(Type serviceType, bool isOptional)
105103
//now since the caller requested a NON Keyed component, we need to skip all keyed components.
106104
var realRegistrations = componentRegistrations.Where(x => !x.ComponentModel.Name.StartsWith(KeyedRegistrationHelper.KeyedRegistrationPrefix)).ToList();
107105
string registrationName = null;
108-
if (realRegistrations.Count == 1)
106+
if (realRegistrations.Count == 1)
109107
{
110108
registrationName = realRegistrations[0].ComponentModel.Name;
111109
}
@@ -116,25 +114,39 @@ private object ResolveInstanceOrNull(Type serviceType, bool isOptional)
116114
}
117115
else if (realRegistrations.Count > 1)
118116
{
119-
//more than one component is registered for the interface without key, we have some ambiguity that is resolved, based on test
120-
//found in framework with this rule.
121-
//1. Last component win.
122-
//2. closed service are preferred over open generic.
117+
//Need to honor IsDefault for castle registrations.
118+
var isDefaultRegistration = realRegistrations
119+
.FirstOrDefault(dh => dh.ComponentModel.ExtendedProperties.Any(ComponentIsDefault));
123120

124-
//take first non generic
125-
for (int i = realRegistrations.Count - 1; i >= 0; i--)
121+
//Remember that castle has a specific order of resolution, if someone registered something in castle with
122+
//IsDefault() it Must be honored.
123+
if (isDefaultRegistration != null)
124+
{
125+
registrationName = isDefaultRegistration.ComponentModel.Name;
126+
}
127+
else
126128
{
127-
if (!realRegistrations[i].ComponentModel.Implementation.IsGenericTypeDefinition)
129+
//more than one component is registered for the interface without key, we have some ambiguity that is resolved, based on test
130+
//found in framework with this rule. In this situation we do not use the same rule of Castle where the first service win but
131+
//we use the framework rule that:
132+
//1. Last component win.
133+
//2. closed service are preferred over open generic.
134+
135+
//take first non generic
136+
for (int i = realRegistrations.Count - 1; i >= 0; i--)
128137
{
129-
registrationName = realRegistrations[i].ComponentModel.Name;
130-
break;
138+
if (!realRegistrations[i].ComponentModel.Implementation.IsGenericTypeDefinition)
139+
{
140+
registrationName = realRegistrations[i].ComponentModel.Name;
141+
break;
142+
}
131143
}
132-
}
133144

134-
//if we did not find any non generic, take the last one.
135-
if (registrationName == null)
136-
{
137-
registrationName = realRegistrations[realRegistrations.Count - 1].ComponentModel.Name;
145+
//if we did not find any non generic, take the last one.
146+
if (registrationName == null)
147+
{
148+
registrationName = realRegistrations[realRegistrations.Count - 1].ComponentModel.Name;
149+
}
138150
}
139151
}
140152

@@ -143,10 +155,6 @@ private object ResolveInstanceOrNull(Type serviceType, bool isOptional)
143155
return null;
144156
}
145157
return container.Resolve(registrationName, serviceType);
146-
#else
147-
//no keyed component in previous framework, just resolve.
148-
return container.Resolve(serviceType);
149-
#endif
150158
}
151159

152160
if (serviceType.GetTypeInfo().IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -197,6 +205,28 @@ private object ResolveInstanceOrNull(Type serviceType, bool isOptional)
197205
return container.Resolve(serviceType);
198206
}
199207

208+
private static bool ComponentIsDefault(KeyValuePair<object, object> property)
209+
{
210+
if (!Core.Internal.Constants.DefaultComponentForServiceFilter.Equals(property.Key))
211+
{
212+
//not the property we are looking for
213+
return false;
214+
}
215+
216+
if (property.Value is bool boolValue)
217+
{
218+
return boolValue;
219+
}
220+
221+
if (property.Value is Predicate<Type> predicate)
222+
{
223+
//this is a method info that we can invoke to get the value.
224+
return predicate(null);
225+
}
226+
227+
return false;
228+
}
229+
200230
#if NET6_0_OR_GREATER
201231

202232
public bool IsService(Type serviceType)

0 commit comments

Comments
 (0)