Skip to content

Commit e9fadc6

Browse files
align with ienumerable resolution in the Microsoft DI world
1 parent 6521615 commit e9fadc6

File tree

3 files changed

+138
-14
lines changed

3 files changed

+138
-14
lines changed

src/Ninject.Web.AspNetCore.Test/Unit/ServiceProviderKeyedTest.cs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ public void OptionalKeyedNonExisting_SingleServiceResolvedToNull()
6363
public void OptionalExistingMultipleKeydServices_ResolvedQueriedAsList()
6464
{
6565
var kernel = CreateTestKernel();
66-
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));;
67-
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
66+
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Warrior"));;
67+
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Warrior"));
6868
var provider = CreateServiceProvider(kernel);
6969

70-
var result = provider.GetService(typeof(IList<IWarrior>)) as IEnumerable<IWarrior>;
70+
var result = provider.GetKeyedService(typeof(IList<IWarrior>), "Warrior") as IEnumerable<IWarrior>;
7171

7272
result.Should().NotBeNull();
7373
var resultList = result.ToList();
@@ -76,6 +76,21 @@ public void OptionalExistingMultipleKeydServices_ResolvedQueriedAsList()
7676
resultList.Should().Contain(x => x is Ninja);
7777
}
7878

79+
[Fact]
80+
public void OptionalExistingMultipleKeydServices_NotResolvedAsListNonKeyed()
81+
{
82+
var kernel = CreateTestKernel();
83+
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));;
84+
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
85+
var provider = CreateServiceProvider(kernel);
86+
87+
var result = provider.GetService(typeof(IList<IWarrior>)) as IEnumerable<IWarrior>;
88+
89+
result.Should().NotBeNull();
90+
var resultList = result.ToList();
91+
resultList.Should().HaveCount(0);
92+
}
93+
7994
[Fact]
8095
public void ExistingMultipleServices_ResolvesNonKeyedToNull()
8196
{
@@ -127,11 +142,11 @@ public void RequiredKeyedNonExisting_SingleServiceResolvedToException()
127142
public void RequiredExistingMultipleKeydServices_ResolvedQueriedAsList()
128143
{
129144
var kernel = CreateTestKernel();
130-
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));;
131-
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
145+
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Warrior"));;
146+
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Warrior"));
132147
var provider = CreateServiceProvider(kernel);
133148

134-
var result = provider.GetRequiredService(typeof(IList<IWarrior>)) as IEnumerable<IWarrior>;
149+
var result = provider.GetRequiredKeyedService(typeof(IList<IWarrior>), "Warrior") as IEnumerable<IWarrior>;
135150

136151
result.Should().NotBeNull();
137152
var resultList = result.ToList();
@@ -140,6 +155,21 @@ public void RequiredExistingMultipleKeydServices_ResolvedQueriedAsList()
140155
resultList.Should().Contain(x => x is Ninja);
141156
}
142157

158+
[Fact]
159+
public void RequiredExistingMultipleKeydServices_NotResolvedAsListNonKeyed()
160+
{
161+
var kernel = CreateTestKernel();
162+
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));;
163+
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
164+
var provider = CreateServiceProvider(kernel);
165+
166+
var result = provider.GetRequiredService(typeof(IList<IWarrior>)) as IEnumerable<IWarrior>;
167+
168+
result.Should().NotBeNull();
169+
var resultList = result.ToList();
170+
resultList.Should().HaveCount(0);
171+
}
172+
143173
[Fact]
144174
public void ExistingMultipleServices_ResolvesNonKeyedToException()
145175
{

src/Ninject.Web.AspNetCore/NinjectServiceProvider.cs

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
using Microsoft.Extensions.DependencyInjection;
22
using Ninject.Syntax;
33
using System;
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Reflection;
48
using Ninject.Planning.Bindings;
59

610
namespace Ninject.Web.AspNetCore
@@ -24,6 +28,7 @@ public class NinjectServiceProvider : IServiceProvider, ISupportRequiredService,
2428
, IKeyedServiceProvider
2529
#endif
2630
{
31+
private static readonly MethodInfo EnumerableCastMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.Cast));
2732
private readonly IResolutionRoot _resolutionRoot;
2833
private readonly IServiceScope _scope;
2934

@@ -35,13 +40,35 @@ public NinjectServiceProvider(IResolutionRoot resolutionRoot, IServiceScope scop
3540

3641
public object GetRequiredService(Type serviceType)
3742
{
38-
var result = _resolutionRoot.Get(serviceType);
39-
return result;
43+
object result = null;
44+
if (!IsListType(serviceType, out var elementType))
45+
{
46+
return _resolutionRoot.Get(serviceType);
47+
}
48+
else
49+
{
50+
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
51+
// Therefore, need to implement a workaround to not instantiate here bindings with servicekey
52+
return ConvertToTypedEnumerable(elementType,
53+
_resolutionRoot.GetAll(elementType, metadata => !HasServiceKeyMetadata(metadata)));
54+
}
4055
}
4156

4257
public object GetService(Type serviceType)
4358
{
44-
var result = _resolutionRoot.TryGet(serviceType);
59+
object result = null;
60+
if (!IsListType(serviceType, out var elementType))
61+
{
62+
result = _resolutionRoot.TryGet(serviceType);
63+
}
64+
else
65+
{
66+
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
67+
// Therefore, need to implement a workaround to not instantiate here bindings with servicekey
68+
result = ConvertToTypedEnumerable(elementType,
69+
_resolutionRoot.GetAll(elementType, metadata => !HasServiceKeyMetadata(metadata)));
70+
}
71+
4572
return result;
4673
}
4774

@@ -53,19 +80,88 @@ public void Dispose()
5380
#if NET8_0_OR_GREATER
5481
public object GetKeyedService(Type serviceType, object serviceKey)
5582
{
56-
var result = _resolutionRoot.TryGet(serviceType, metadata => DoesMetadataMatchServiceKey(serviceKey, metadata));
83+
object result = null;
84+
if (!IsListType(serviceType, out var elementType))
85+
{
86+
result = _resolutionRoot.TryGet(serviceType,
87+
metadata => DoesMetadataMatchServiceKey(serviceKey, metadata));
88+
}
89+
else
90+
{
91+
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
92+
// Therefore, need to implement a workaround to not instantiate here bindings with a different servicekey value
93+
result = ConvertToTypedEnumerable(elementType,
94+
_resolutionRoot.GetAll(elementType, metadata => DoesMetadataMatchServiceKey(serviceKey, metadata)));
95+
}
96+
5797
return result;
5898
}
5999

60100
public object GetRequiredKeyedService(Type serviceType, object serviceKey)
61101
{
62-
return _resolutionRoot.Get(serviceType, metadata => DoesMetadataMatchServiceKey(serviceKey, metadata));
102+
if (!IsListType(serviceType, out var elementType))
103+
{
104+
return _resolutionRoot.Get(serviceType, metadata => DoesMetadataMatchServiceKey(serviceKey, metadata));
105+
}
106+
else
107+
{
108+
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
109+
// Therefore, need to implement a workaround to not instantiate here bindings with a different servicekey value
110+
return ConvertToTypedEnumerable(elementType,
111+
_resolutionRoot.GetAll(elementType, metadata => DoesMetadataMatchServiceKey(serviceKey, metadata)).ToList());
112+
}
63113
}
64114

65115
private static bool DoesMetadataMatchServiceKey(object serviceKey, IBindingMetadata metadata)
66116
{
67117
return metadata.Get<ServiceKey>(nameof(ServiceKey))?.Key == serviceKey;
68118
}
69119
#endif
120+
121+
private static bool HasServiceKeyMetadata(IBindingMetadata metadata)
122+
{
123+
return metadata.Has(nameof(ServiceKey));
124+
}
125+
126+
/// <summary>
127+
/// This method extracts the elementtype in the same way as Ninject does
128+
/// in KernelBase.Resolve
129+
/// </summary>
130+
private static bool IsListType(Type type, out Type elementType)
131+
{
132+
if (type.IsArray)
133+
{
134+
elementType = type.GetElementType();
135+
return true;
136+
}
137+
138+
if (type.IsGenericType)
139+
{
140+
Type genericTypeDefinition = type.GetGenericTypeDefinition();
141+
if (genericTypeDefinition == typeof(List<>) || genericTypeDefinition == typeof(IList<>) ||
142+
genericTypeDefinition == typeof(ICollection<>))
143+
{
144+
elementType = type.GenericTypeArguments[0];
145+
return true;
146+
}
147+
148+
if (genericTypeDefinition == typeof(IEnumerable<>))
149+
{
150+
elementType = type.GenericTypeArguments[0];
151+
return true;
152+
}
153+
}
154+
155+
elementType = null;
156+
return false;
157+
}
158+
159+
private static object ConvertToTypedEnumerable(Type elementType, IEnumerable<object> objectList)
160+
{
161+
var castMethod = EnumerableCastMethod.MakeGenericMethod(elementType);
162+
var result = (IEnumerable)castMethod.Invoke(null, new object[] { objectList });
163+
return result;
164+
}
165+
70166
}
71167
}

src/Ninject.Web.AspNetCore/ServiceKey.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
namespace Ninject.Web.AspNetCore
44
{
55

6-
#if NET8_0_OR_GREATER
7-
86
/// <summary>
97
/// Used to store ServiceDescriptor.ServiceKey as metadata of the Ninject binding.
8+
/// Only supported with .NET >= 8.0
109
/// </summary>
1110
public class ServiceKey
1211
{
@@ -17,6 +16,5 @@ public ServiceKey(object key)
1716
Key = key;
1817
}
1918
}
20-
#endif
2119

2220
}

0 commit comments

Comments
 (0)