Skip to content

Commit 09ef1f9

Browse files
add support for ServiceKeyAttribute and FromKeyedService
1 parent a11c483 commit 09ef1f9

10 files changed

+267
-24
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace Ninject.Web.AspNetCore.Test.Fakes
4+
{
5+
#if NET8_0_OR_GREATER
6+
public class KeyedNinja : IWarrior
7+
{
8+
public object Key {get; private set;}
9+
10+
public KeyedNinja([ServiceKey] object key)
11+
{
12+
Key = key;
13+
}
14+
15+
public string Name => nameof(KeyedNinja);
16+
}
17+
#endif
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace Ninject.Web.AspNetCore.Test.Fakes
4+
{
5+
#if NET8_0_OR_GREATER
6+
7+
public class NinjaWithKeyedWaepon : IWarrior
8+
{
9+
public IWeapon Weapon { get; private set; }
10+
11+
public string Name => nameof(NinjaWithKeyedWaepon) + $" with weapon {Weapon.Type}";
12+
13+
public NinjaWithKeyedWaepon([FromKeyedServices("Longsword")] IWeapon weapon)
14+
{
15+
Weapon = weapon;
16+
}
17+
}
18+
19+
#endif
20+
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,30 @@ namespace Ninject.Web.AspNetCore.Test.Unit
1414

1515
public class ServiceProviderKeyedTest
1616
{
17+
[Fact]
18+
public void OptionalExising_SingleServiceInjectedServiceKeyResolved()
19+
{
20+
var collection = new ServiceCollection();
21+
collection.Add(new ServiceDescriptor(typeof(IWarrior), "Ninja", typeof(KeyedNinja), ServiceLifetime.Transient));
22+
var kernel = CreateTestKernel(collection);
23+
var provider = CreateServiceProvider(kernel);
24+
25+
var warrior = provider.GetKeyedService(typeof(IWarrior), "Ninja");
26+
warrior.Should().NotBeNull().And.BeOfType(typeof(KeyedNinja)).And.Match(x => ((KeyedNinja)x).Key.ToString() == "Ninja");
27+
}
28+
29+
[Fact]
30+
public void OptionalExisingWithKeyedChild_SingleServiceResolved()
31+
{
32+
var collection = new ServiceCollection();
33+
collection.Add(new ServiceDescriptor(typeof(IWarrior), "Ninja", typeof(NinjaWithKeyedWaepon), ServiceLifetime.Transient));
34+
collection.Add(new ServiceDescriptor(typeof(IWeapon), "Longsword", typeof(Longsword), ServiceLifetime.Transient));
35+
var kernel = CreateTestKernel(collection);
36+
var provider = CreateServiceProvider(kernel);
37+
38+
var warrior = provider.GetKeyedService(typeof(IWarrior), "Ninja");
39+
warrior.Should().NotBeNull().And.BeOfType(typeof(NinjaWithKeyedWaepon)).And.Match(x => ((NinjaWithKeyedWaepon)x).Weapon.Type == nameof(Longsword));
40+
}
1741

1842
[Fact]
1943
public void OptionalKeyedServiceCollectionExisting_CorrectServiceResolved()

src/Ninject.Web.AspNetCore/AspNetCoreKernel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using Ninject.Planning.Bindings.Resolvers;
88
using Ninject.Web.AspNetCore.Components;
99
using System;
10+
using Ninject.Planning.Strategies;
11+
using Ninject.Web.AspNetCore.Planning;
1012

1113
namespace Ninject.Web.AspNetCore
1214
{
@@ -49,6 +51,8 @@ protected override void AddComponents()
4951
Components.Add<IBindingResolver, ConstrainedGenericBindingResolver>();
5052
Components.Remove<IBindingPrecedenceComparer, BindingPrecedenceComparer>();
5153
Components.Add<IBindingPrecedenceComparer, IndexedBindingPrecedenceComparer>();
54+
Components.Remove<IPlanningStrategy, ConstructorReflectionStrategy>();
55+
Components.Add<IPlanningStrategy, ConstructorReflectionStrategyWithKeyedSupport>();
5256

5357
Components.Add<IDisposalManager, DisposalManager>();
5458
Components.Remove<IActivationStrategy, DisposableStrategy>();

src/Ninject.Web.AspNetCore/NinjectServiceProvider.cs

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Reflection;
88
using Ninject.Planning.Bindings;
9+
using Ninject.Web.AspNetCore.Planning;
910

1011
namespace Ninject.Web.AspNetCore
1112
{
@@ -42,14 +43,14 @@ public object GetRequiredService(Type serviceType)
4243
{
4344
if (!IsListType(serviceType, out var elementType))
4445
{
45-
return _resolutionRoot.Get(serviceType, metadata => !HasServiceKeyMetadata(metadata));
46+
return _resolutionRoot.Get(serviceType, metadata => !metadata.HasServiceKeyMetadata());
4647
}
4748
else
4849
{
4950
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
5051
// Therefore, need to implement a workaround to not instantiate here bindings with servicekey
5152
return ConvertToTypedEnumerable(elementType,
52-
_resolutionRoot.GetAll(elementType, metadata => !HasServiceKeyMetadata(metadata)));
53+
_resolutionRoot.GetAll(elementType, metadata => !metadata.HasServiceKeyMetadata()));
5354
}
5455
}
5556

@@ -58,14 +59,14 @@ public object GetService(Type serviceType)
5859
object result;
5960
if (!IsListType(serviceType, out var elementType))
6061
{
61-
result = _resolutionRoot.TryGet(serviceType, metadata => !HasServiceKeyMetadata(metadata));
62+
result = _resolutionRoot.TryGet(serviceType, metadata => !metadata.HasServiceKeyMetadata());
6263
}
6364
else
6465
{
6566
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
6667
// Therefore, need to implement a workaround to not instantiate here bindings with servicekey
6768
result = ConvertToTypedEnumerable(elementType,
68-
_resolutionRoot.GetAll(elementType, metadata => !HasServiceKeyMetadata(metadata)));
69+
_resolutionRoot.GetAll(elementType, metadata => !metadata.HasServiceKeyMetadata()));
6970
}
7071

7172
return result;
@@ -84,14 +85,14 @@ public object GetKeyedService(Type serviceType, object serviceKey)
8485
{
8586
EnsureNotAnyKey(serviceKey, serviceType);
8687
result = _resolutionRoot.TryGet(serviceType,
87-
metadata => DoesMetadataMatchServiceKey(serviceKey, metadata));
88+
metadata => metadata.DoesMetadataMatchServiceKey(serviceKey));
8889
}
8990
else
9091
{
9192
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
9293
// Therefore, need to implement a workaround to not instantiate here bindings with a different servicekey value
9394
result = ConvertToTypedEnumerable(elementType,
94-
_resolutionRoot.GetAll(elementType, metadata => DoesMetadataMatchServiceKey(serviceKey, metadata)));
95+
_resolutionRoot.GetAll(elementType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey)));
9596
}
9697

9798
return result;
@@ -102,14 +103,14 @@ public object GetRequiredKeyedService(Type serviceType, object serviceKey)
102103
if (!IsListType(serviceType, out var elementType))
103104
{
104105
EnsureNotAnyKey(serviceKey, serviceType);
105-
return _resolutionRoot.Get(serviceType, metadata => DoesMetadataMatchServiceKey(serviceKey, metadata));
106+
return _resolutionRoot.Get(serviceType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey));
106107
}
107108
else
108109
{
109110
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
110111
// Therefore, need to implement a workaround to not instantiate here bindings with a different servicekey value
111112
return ConvertToTypedEnumerable(elementType,
112-
_resolutionRoot.GetAll(elementType, metadata => DoesMetadataMatchServiceKey(serviceKey, metadata)).ToList());
113+
_resolutionRoot.GetAll(elementType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey)).ToList());
113114
}
114115
}
115116

@@ -122,22 +123,8 @@ private void EnsureNotAnyKey(object serviceKey, Type serviceType)
122123
}
123124
}
124125

125-
126-
private static bool DoesMetadataMatchServiceKey(object serviceKey, IBindingMetadata metadata)
127-
{
128-
if (serviceKey == KeyedService.AnyKey)
129-
{
130-
return HasServiceKeyMetadata(metadata);
131-
}
132-
return metadata.Get<ServiceKey>(nameof(ServiceKey))?.Key == serviceKey;
133-
}
134126
#endif
135-
136-
private static bool HasServiceKeyMetadata(IBindingMetadata metadata)
137-
{
138-
return metadata.Has(nameof(ServiceKey));
139-
}
140-
127+
141128
/// <summary>
142129
/// This method extracts the elementtype in the same way as Ninject does
143130
/// in KernelBase.Resolve

src/Ninject.Web.AspNetCore/NinjectServiceProviderIsService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.Extensions.DependencyInjection;
22
using System;
33
using System.Collections.Generic;
4+
using Ninject.Web.AspNetCore.Planning;
45

56
namespace Ninject.Web.AspNetCore
67
{
@@ -51,7 +52,7 @@ public bool IsKeyedService(Type serviceType, object serviceKey)
5152
}
5253

5354
return _kernel.CanResolve(serviceType, metadata =>
54-
metadata.Get<ServiceKey>(nameof(ServiceKey))?.Key == serviceKey);
55+
metadata.DoesMetadataMatchServiceKey(serviceKey));
5556
}
5657
#endif
5758
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using Ninject.Injection;
6+
using Ninject.Planning.Directives;
7+
using Ninject.Planning.Targets;
8+
9+
namespace Ninject.Web.AspNetCore.Planning
10+
{
11+
public class ConstructorInjectionDirectiveWithKeyedSupport : ConstructorInjectionDirective
12+
{
13+
public ConstructorInjectionDirectiveWithKeyedSupport(ConstructorInfo constructor, ConstructorInjector injector) : base(constructor, injector)
14+
{
15+
}
16+
17+
protected override ITarget[] CreateTargetsFromParameters(ConstructorInfo method)
18+
{
19+
return method.GetParameters().
20+
Select((Func<ParameterInfo, ParameterTarget>) (parameter => new ParameterTargetWithKeyedSupport(method, parameter))).
21+
ToArray();
22+
}
23+
}
24+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Reflection;
3+
using Ninject.Components;
4+
using Ninject.Infrastructure.Language;
5+
using Ninject.Injection;
6+
using Ninject.Planning;
7+
using Ninject.Planning.Directives;
8+
using Ninject.Planning.Strategies;
9+
using Ninject.Selection;
10+
11+
namespace Ninject.Web.AspNetCore.Planning
12+
{
13+
/// <summary>
14+
/// Adds a directive to plans indicating which constructor should be injected during activation.
15+
/// Need a custom one to support FromKeyedServices attribute, which doesn't inherit from ConstraintAttribute
16+
/// </summary>
17+
public class ConstructorReflectionStrategyWithKeyedSupport : NinjectComponent,
18+
IPlanningStrategy
19+
{
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="ConstructorReflectionStrategy"/> class.
22+
/// </summary>
23+
/// <param name="selector">The selector component.</param>
24+
/// <param name="injectorFactory">The injector factory component.</param>
25+
public ConstructorReflectionStrategyWithKeyedSupport(ISelector selector, IInjectorFactory injectorFactory)
26+
{
27+
Selector = selector;
28+
InjectorFactory = injectorFactory;
29+
}
30+
31+
/// <summary>
32+
/// Gets the selector component.
33+
/// </summary>
34+
public ISelector Selector { get; }
35+
36+
/// <summary>
37+
/// Gets or sets the injector factory component.
38+
/// </summary>
39+
public IInjectorFactory InjectorFactory { get; }
40+
41+
/// <summary>
42+
/// Adds a <see cref="ConstructorInjectionDirective"/> to the plan for the constructor
43+
/// that should be injected.
44+
/// </summary>
45+
/// <param name="plan">The plan that is being generated.</param>
46+
public void Execute(IPlan plan)
47+
{
48+
var constructors = Selector.SelectConstructorsForInjection(plan.Type);
49+
if (constructors == null)
50+
{
51+
return;
52+
}
53+
54+
foreach (ConstructorInfo constructor in constructors)
55+
{
56+
var hasInjectAttribute = constructor.HasAttribute(Settings.InjectAttribute);
57+
var hasObsoleteAttribute = constructor.HasAttribute(typeof(ObsoleteAttribute));
58+
var directive = new ConstructorInjectionDirectiveWithKeyedSupport(constructor, InjectorFactory.Create(constructor))
59+
{
60+
HasInjectAttribute = hasInjectAttribute,
61+
HasObsoleteAttribute = hasObsoleteAttribute,
62+
};
63+
64+
plan.Add(directive);
65+
}
66+
}
67+
}
68+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Ninject.Planning.Bindings;
4+
5+
namespace Ninject.Web.AspNetCore.Planning
6+
{
7+
/// <summary>
8+
/// Extensions to handle ServiceKey.
9+
/// Only really relevant for >= .NET 8.0, as only there Microsoft DI supports keyed services.
10+
/// </summary>
11+
public static class KeyedServicesMetaDataExtensions
12+
{
13+
14+
#if NET8_0_OR_GREATER
15+
internal static bool DoesMetadataMatchServiceKey(this IBindingMetadata metadata, object serviceKey)
16+
{
17+
if (serviceKey == KeyedService.AnyKey)
18+
{
19+
return HasServiceKeyMetadata(metadata);
20+
}
21+
return Object.Equals(metadata.GetServiceKey(), serviceKey);
22+
}
23+
24+
internal static object GetServiceKey(this IBindingMetadata metadata)
25+
{
26+
return metadata.Get<ServiceKey>(nameof(ServiceKey))?.Key;
27+
}
28+
#endif
29+
internal static bool HasServiceKeyMetadata(this IBindingMetadata metadata)
30+
{
31+
return metadata.Has(nameof(ServiceKey));
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)