Skip to content

Commit 87ea24c

Browse files
refactor to support dynamic binding of services registred with anykey, but resolved with a specific key by using a missing binding resolver
1 parent ae84882 commit 87ea24c

File tree

9 files changed

+331
-27
lines changed

9 files changed

+331
-27
lines changed

src/Ninject.Web.AspNetCore/AspNetCoreKernel.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ protected override Func<IBinding, bool> SatifiesRequest(IRequest request)
5050
requestIndexKey = serviceKeyParameter.ServiceKey;
5151
}
5252
#endif
53-
latest = binding.Metadata.Get<BindingIndex.Item>(nameof(BindingIndex))?.IsLatest(requestIndexKey) ?? true;
53+
latest = binding.Metadata.Get<BindingIndex.Item>(nameof(BindingIndex))?.IsLatest ?? true;
5454
}
5555
return binding.Matches(request) && request.Matches(binding) && latest;
5656
};
@@ -71,6 +71,10 @@ protected override void AddComponents()
7171
Components.Add<IDisposalManager, DisposalManager>();
7272
Components.Remove<IActivationStrategy, DisposableStrategy>();
7373
Components.Add<IActivationStrategy, OrderedDisposalStrategy>();
74+
75+
#if NET8_0_OR_GREATER
76+
Components.Add<IMissingBindingResolver, AnyBindingResolver>();
77+
#endif
7478
}
7579

7680
public void DisableAutomaticSelfBinding()

src/Ninject.Web.AspNetCore/BindingIndex.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,9 @@ public Item Next(Type serviceType, object indexKey)
2626
return next;
2727
}
2828

29-
private bool IsLatest(Type serviceType, object registeredIndexKey, object requestIndexKey, Item item)
29+
private bool IsLatest(Type serviceType, object registeredIndexKey, Item item)
3030
{
3131
var match = _bindingIndexMap[new ServiceTypeKey(serviceType, registeredIndexKey)] == item;
32-
#if NET8_0_OR_GREATER
33-
if (registeredIndexKey == KeyedService.AnyKey)
34-
{
35-
// if the binding is registered with anykey, it should only be considered as latest if there is no
36-
// exact match with the requestIndexKey
37-
match = !_bindingIndexMap.TryGetValue(new ServiceTypeKey(serviceType, requestIndexKey), out _);
38-
}
39-
#endif
4032
return match;
4133
}
4234

@@ -60,7 +52,7 @@ public Item(BindingIndex root, Type serviceType, object indexKey, int totalIndex
6052
IndexKey = indexKey;
6153
}
6254

63-
public bool IsLatest(object requestIndexKey) => _root.IsLatest(_serviceType, IndexKey, requestIndexKey, this);
55+
public bool IsLatest => _root.IsLatest(_serviceType, IndexKey, this);
6456
}
6557

6658
/// <summary>

src/Ninject.Web.AspNetCore/NinjectServiceProvider.cs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Ninject.Planning.Bindings;
1010
using Ninject.Web.AspNetCore.Parameters;
1111
using Ninject.Web.AspNetCore.Planning;
12+
using Ninject.Web.AspNetCore.RequestActivation;
1213

1314
namespace Ninject.Web.AspNetCore
1415
{
@@ -92,17 +93,14 @@ public object GetKeyedService(Type serviceType, object serviceKey)
9293
if (!IsListType(serviceType, out var elementType))
9394
{
9495
EnsureNotAnyKey(serviceKey, serviceType);
95-
result = _resolutionRoot.TryGet(serviceType,
96-
metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, true),
97-
new ServiceKeyParameter(serviceKey));
96+
return ResolveKeyedService<object>(serviceType, serviceKey, true, true);
9897
}
9998
else
10099
{
101100
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
102101
// Therefore, need to implement a workaround to not instantiate here bindings with a different servicekey value
103102
result = ConvertToTypedEnumerable(elementType,
104-
_resolutionRoot.GetAll(elementType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, false),
105-
new ServiceKeyParameter(serviceKey)));
103+
ResolveKeyedService<IEnumerable<object>>(elementType, serviceKey, false, true));
106104
}
107105

108106
return result;
@@ -119,16 +117,29 @@ public object GetRequiredKeyedService(Type serviceType, object serviceKey)
119117
if (!IsListType(serviceType, out var elementType))
120118
{
121119
EnsureNotAnyKey(serviceKey, serviceType);
122-
return _resolutionRoot.Get(serviceType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, true),
123-
new ServiceKeyParameter(serviceKey));
120+
return ResolveKeyedService<object>(serviceType, serviceKey, true, false);
124121
}
125122
else
126123
{
127124
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
128125
// Therefore, need to implement a workaround to not instantiate here bindings with a different servicekey value
129126
return ConvertToTypedEnumerable(elementType,
130-
_resolutionRoot.GetAll(elementType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, false),
131-
new ServiceKeyParameter(serviceKey)));
127+
ResolveKeyedService<IEnumerable<object>>(elementType, serviceKey, false, true));
128+
}
129+
}
130+
131+
private T ResolveKeyedService<T>(Type serviceType, object serviceKey, bool isUnique, bool isOptional) where T : class
132+
{
133+
var standardRequest = _resolutionRoot.CreateRequest(serviceType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey), Array.Empty<Parameter>(), isOptional, isUnique);
134+
var keyedRequest = standardRequest.ToKeyedRequest(serviceKey);
135+
var result = _resolutionRoot.Resolve(keyedRequest);
136+
if (isUnique)
137+
{
138+
return (result as object[]).FirstOrDefault() as T;
139+
}
140+
else
141+
{
142+
return result as T;
132143
}
133144
}
134145

@@ -180,6 +191,7 @@ private static object ConvertToTypedEnumerable(Type elementType, IEnumerable<obj
180191
{
181192
var castMethod = EnumerableCastMethod.MakeGenericMethod(elementType);
182193
var result = (IEnumerable)castMethod.Invoke(null, new object[] { objectList });
194+
183195
return result;
184196
}
185197

src/Ninject.Web.AspNetCore/NinjectServiceProviderIsService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public bool IsKeyedService(Type serviceType, object serviceKey)
5252
}
5353

5454
return _kernel.CanResolve(serviceType, metadata =>
55-
metadata.DoesMetadataMatchServiceKey(serviceKey, true));
55+
metadata.DoesMetadataMatchServiceKey(serviceKey));
5656
}
5757
#endif
5858
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Ninject.Activation;
6+
using Ninject.Activation.Providers;
7+
using Ninject.Components;
8+
using Ninject.Infrastructure;
9+
using Ninject.Planning.Bindings;
10+
using Ninject.Planning.Bindings.Resolvers;
11+
using Ninject.Web.AspNetCore.RequestActivation;
12+
13+
namespace Ninject.Web.AspNetCore.Planning
14+
{
15+
#if NET8_0_OR_GREATER
16+
public class AnyBindingResolver : NinjectComponent, IMissingBindingResolver
17+
{
18+
public IEnumerable<IBinding> Resolve(Multimap<Type, IBinding> bindings, IRequest request)
19+
{
20+
// we resolve here request with a specific service key, but only having a binding with anykey.
21+
// this ensures that we e.g. can have a singleton binding with anykey, but instantiate one singleton
22+
// per servicekey.
23+
var keyedRequest = request as KeyedRequest;
24+
if (keyedRequest != null && keyedRequest.ServiceKey != null && keyedRequest.ServiceKey != KeyedService.AnyKey
25+
&& keyedRequest.IsUnique)
26+
{
27+
var service = request.Service;
28+
var matchingBindings = bindings.Where(x => x.Key == service);
29+
if (!matchingBindings.Any())
30+
{
31+
return Array.Empty<IBinding>();
32+
}
33+
34+
IBinding matchingAnyBinding = null;
35+
foreach (var bindingGroup in matchingBindings)
36+
{
37+
foreach (var binding in bindingGroup.Value)
38+
{
39+
if (binding.Metadata.HasServiceKeyMetadata() && Object.Equals(binding.Metadata.GetServiceKey(), KeyedService.AnyKey)
40+
&& (binding.Metadata.Get<BindingIndex.Item>(nameof(BindingIndex))?.IsLatest ?? true)
41+
)
42+
{
43+
matchingAnyBinding = binding;
44+
break;
45+
}
46+
}
47+
}
48+
49+
if (matchingAnyBinding == null)
50+
{
51+
return Array.Empty<IBinding>();
52+
}
53+
54+
var resultBinding = new Binding(service)
55+
{
56+
IsImplicit = true,
57+
ProviderCallback = matchingAnyBinding.ProviderCallback,
58+
ScopeCallback = matchingAnyBinding.ScopeCallback,
59+
Target = matchingAnyBinding.Target
60+
};
61+
var bindingIndex = new BindingIndex();
62+
resultBinding.Metadata.Set(nameof(BindingIndex), bindingIndex.Next(service, keyedRequest.ServiceKey));
63+
resultBinding.Metadata.Set(nameof(ServiceKey), new ServiceKey(keyedRequest.ServiceKey));
64+
resultBinding.Metadata.Set(nameof(ServiceDescriptor), matchingAnyBinding.Metadata.Get<IDescriptorAdapter>(nameof(ServiceDescriptor)));
65+
66+
return new Binding[1]
67+
{
68+
resultBinding
69+
};
70+
}
71+
72+
return Array.Empty<IBinding>();
73+
}
74+
}
75+
#endif
76+
}

src/Ninject.Web.AspNetCore/Planning/KeyedServicesMetaDataExtensions.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@ public static class KeyedServicesMetaDataExtensions
1212
{
1313

1414
#if NET8_0_OR_GREATER
15-
internal static bool DoesMetadataMatchServiceKey(this IBindingMetadata metadata, object serviceKey, bool isUniqueRequest)
15+
internal static bool DoesMetadataMatchServiceKey(this IBindingMetadata metadata, object serviceKey)
1616
{
1717
if (serviceKey == KeyedService.AnyKey)
1818
{
1919
// if the service is registered with KeyedService.AnyKey, it must not be returned when querying with AnyKey
2020
// see CombinationalRegistration compliancetest
2121
return HasServiceKeyMetadata(metadata) && !Object.Equals(metadata.GetServiceKey(), KeyedService.AnyKey);
2222
}
23-
return Object.Equals(metadata.GetServiceKey(), serviceKey)
24-
// if we query with a key different to KeyedService.AnyKey but registired with AnyKey, we have to return it as well
25-
// see ResolveKeyedServiceSingletonInstanceWithAnyKey compliancetest. But only if we resolve a unique instance
26-
|| (isUniqueRequest && Object.Equals(metadata.GetServiceKey(), KeyedService.AnyKey));
23+
24+
return Object.Equals(metadata.GetServiceKey(), serviceKey);
25+
// if we query with a key different to KeyedService.AnyKey but registired with AnyKey, we have to instantiate it in the end
26+
// but we do this with a missingbinding resolver, the AnyBindingResolver. But only if we resolve a unique instance
27+
// see ResolveKeyedServiceSingletonInstanceWithAnyKey compliancetest.
2728
}
2829

2930
internal static object GetServiceKey(this IBindingMetadata metadata)

src/Ninject.Web.AspNetCore/Planning/ParameterTargetWithKeyedSupport.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Ninject.Planning.Bindings;
77
using Ninject.Planning.Targets;
88
using Ninject.Web.AspNetCore.Parameters;
9+
using Ninject.Web.AspNetCore.RequestActivation;
910

1011
namespace Ninject.Web.AspNetCore.Planning
1112
{
@@ -55,7 +56,13 @@ protected override Func<IBindingMetadata, bool> ReadConstraintFromTarget()
5556

5657
if (metadata.HasServiceKeyMetadata())
5758
{
58-
result = result && metadata.DoesMetadataMatchServiceKey(keyedattributes[0].Key, true);
59+
object keyToCompareWith = keyedattributes[0].Key;
60+
#if NET10_0_OR_GREATER
61+
if (keyedattributes[0].LookupMode == ServiceKeyLookupMode.InheritKey) {
62+
// here we would need the request!
63+
}
64+
#endif
65+
result = result && metadata.DoesMetadataMatchServiceKey(keyToCompareWith);
5966
}
6067
else
6168
{
@@ -111,6 +118,14 @@ object ITarget.ResolveWithin(IContext parent)
111118

112119
return result;
113120
}
121+
122+
var keyedattributes = GetCustomAttributes(typeof (FromKeyedServicesAttribute), true) as FromKeyedServicesAttribute[];
123+
if (keyedattributes?.Length > 0)
124+
{
125+
var child = parent.Request.CreateKeyedChildRequest(Type, keyedattributes[0].Key, parent, this);
126+
child.IsUnique = true;
127+
return parent.Kernel.Resolve(child).SingleOrDefault();
128+
}
114129
#endif
115130
return base.ResolveWithin(parent);
116131
}

0 commit comments

Comments
 (0)