Skip to content

Commit e6fe5d0

Browse files
introduce runtime parameter so that we can inject the servicekey used instead of the servicekey bound
1 parent cb44ae5 commit e6fe5d0

File tree

9 files changed

+87
-17
lines changed

9 files changed

+87
-17
lines changed

src/Ninject.Web.AspNetCore/AspNetCoreKernel.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using Ninject.Planning.Bindings.Resolvers;
88
using Ninject.Web.AspNetCore.Components;
99
using System;
10+
using System.Linq;
1011
using Ninject.Planning.Strategies;
12+
using Ninject.Web.AspNetCore.Parameters;
1113
using Ninject.Web.AspNetCore.Planning;
1214

1315
namespace Ninject.Web.AspNetCore
@@ -39,7 +41,16 @@ protected override Func<IBinding, bool> SatifiesRequest(IRequest request)
3941
// as we can't register constraints via microsoft.extensions.dependencyinjection,
4042
// we always check for the latest binding
4143
// Note that we have at least one constraint for the servicekey >= .NET 8.0
42-
latest = binding.Metadata.Get<BindingIndex.Item>(nameof(BindingIndex))?.IsLatest ?? true;
44+
object requestIndexKey = null;
45+
#if NET8_0_OR_GREATER
46+
var serviceKeyParameter = request.Parameters.LastOrDefault(x => x is ServiceKeyParameter)
47+
as ServiceKeyParameter;
48+
if (serviceKeyParameter != null)
49+
{
50+
requestIndexKey = serviceKeyParameter.ServiceKey;
51+
}
52+
#endif
53+
latest = binding.Metadata.Get<BindingIndex.Item>(nameof(BindingIndex))?.IsLatest(requestIndexKey) ?? true;
4354
}
4455
return binding.Matches(request) && request.Matches(binding) && latest;
4556
};

src/Ninject.Web.AspNetCore/BindingIndex.cs

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

29-
private bool IsLatest(Type serviceType, object indexKey, Item item)
29+
private bool IsLatest(Type serviceType, object registeredIndexKey, object requestIndexKey, Item item)
3030
{
31-
return _bindingIndexMap[new ServiceTypeKey(serviceType, indexKey)] == item;
31+
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
40+
return match;
3241
}
3342

3443
public class Item
@@ -39,8 +48,7 @@ public class Item
3948
public int TotalIndex { get; }
4049
public int TypeIndex { get; }
4150
public object IndexKey { get; }
42-
43-
public bool IsLatest => _root.IsLatest(_serviceType, IndexKey, this);
51+
4452
public int Precedence => _root.Count - TotalIndex;
4553

4654
public Item(BindingIndex root, Type serviceType, object indexKey, int totalIndex, int typeIndex)
@@ -51,6 +59,8 @@ public Item(BindingIndex root, Type serviceType, object indexKey, int totalIndex
5159
TypeIndex = typeIndex;
5260
IndexKey = indexKey;
5361
}
62+
63+
public bool IsLatest(object requestIndexKey) => _root.IsLatest(_serviceType, IndexKey, requestIndexKey, this);
5464
}
5565

5666
/// <summary>

src/Ninject.Web.AspNetCore/DefaultDescriptorAdapter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using Microsoft.Extensions.DependencyInjection;
3+
using Ninject.Activation;
34

45
namespace Ninject.Web.AspNetCore
56
{
@@ -19,7 +20,7 @@ public DefaultDescriptorAdapter(ServiceDescriptor descriptor)
1920
public Type ImplementationType => _descriptor.ImplementationType;
2021
public object ImplementationInstance => _descriptor.ImplementationInstance;
2122
public bool UseServiceFactory => _descriptor.ImplementationFactory != null;
22-
public object InstantiateFromServiceFactory(IServiceProvider provider)
23+
public object InstantiateFromServiceFactory(IServiceProvider provider, IContext context)
2324
{
2425
return _descriptor.ImplementationFactory(provider);
2526
}

src/Ninject.Web.AspNetCore/IDescriptorAdapter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using Microsoft.Extensions.DependencyInjection;
3+
using Ninject.Activation;
34

45
namespace Ninject.Web.AspNetCore
56
{
@@ -27,7 +28,7 @@ public interface IDescriptorAdapter
2728
/// <summary>
2829
/// If UseServiceFactory returns true, use this method to instantiate via factory.
2930
/// </summary>
30-
object InstantiateFromServiceFactory(IServiceProvider provider);
31+
object InstantiateFromServiceFactory(IServiceProvider provider, IContext context);
3132

3233
/// <summary>
3334
/// The lifetime coonfigured for the service descriptor

src/Ninject.Web.AspNetCore/KeyedDescriptorAdapter.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
2+
using System.Linq;
23
using Microsoft.Extensions.DependencyInjection;
4+
using Ninject.Activation;
5+
using Ninject.Web.AspNetCore.Parameters;
36

47
namespace Ninject.Web.AspNetCore
58
{
@@ -20,9 +23,15 @@ public KeyedDescriptorAdapter(ServiceDescriptor descriptor)
2023
public Type ImplementationType => _descriptor.KeyedImplementationType;
2124
public object ImplementationInstance => _descriptor.KeyedImplementationInstance;
2225
public bool UseServiceFactory => _descriptor.KeyedImplementationFactory != null;
23-
public object InstantiateFromServiceFactory(IServiceProvider provider)
26+
public object InstantiateFromServiceFactory(IServiceProvider provider, IContext context)
2427
{
25-
return _descriptor.KeyedImplementationFactory(provider, _descriptor.ServiceKey);
28+
object serviceKey = _descriptor.ServiceKey;
29+
var keyParameter = context.Parameters.LastOrDefault(x => x is ServiceKeyParameter) as ServiceKeyParameter;
30+
if (keyParameter != null)
31+
{
32+
serviceKey = keyParameter.ServiceKey;
33+
}
34+
return _descriptor.KeyedImplementationFactory(provider, serviceKey);
2635
}
2736

2837
public ServiceLifetime Lifetime => _descriptor.Lifetime;

src/Ninject.Web.AspNetCore/NinjectServiceProvider.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Reflection;
8+
using Ninject.Parameters;
89
using Ninject.Planning.Bindings;
10+
using Ninject.Web.AspNetCore.Parameters;
911
using Ninject.Web.AspNetCore.Planning;
1012

1113
namespace Ninject.Web.AspNetCore
@@ -91,14 +93,16 @@ public object GetKeyedService(Type serviceType, object serviceKey)
9193
{
9294
EnsureNotAnyKey(serviceKey, serviceType);
9395
result = _resolutionRoot.TryGet(serviceType,
94-
metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, true));
96+
metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, true),
97+
new ServiceKeyParameter(serviceKey));
9598
}
9699
else
97100
{
98101
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
99102
// Therefore, need to implement a workaround to not instantiate here bindings with a different servicekey value
100103
result = ConvertToTypedEnumerable(elementType,
101-
_resolutionRoot.GetAll(elementType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, false)));
104+
_resolutionRoot.GetAll(elementType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, false),
105+
new ServiceKeyParameter(serviceKey)));
102106
}
103107

104108
return result;
@@ -115,14 +119,16 @@ public object GetRequiredKeyedService(Type serviceType, object serviceKey)
115119
if (!IsListType(serviceType, out var elementType))
116120
{
117121
EnsureNotAnyKey(serviceKey, serviceType);
118-
return _resolutionRoot.Get(serviceType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, true));
122+
return _resolutionRoot.Get(serviceType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, true),
123+
new ServiceKeyParameter(serviceKey));
119124
}
120125
else
121126
{
122127
// Ninject is not evaluating metadata constraint when resolving a IEnumerable<T>, see KernelBase.UpdateRequest
123128
// Therefore, need to implement a workaround to not instantiate here bindings with a different servicekey value
124129
return ConvertToTypedEnumerable(elementType,
125-
_resolutionRoot.GetAll(elementType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, false)));
130+
_resolutionRoot.GetAll(elementType, metadata => metadata.DoesMetadataMatchServiceKey(serviceKey, false),
131+
new ServiceKeyParameter(serviceKey)));
126132
}
127133
}
128134

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Ninject.Parameters;
2+
3+
4+
namespace Ninject.Web.AspNetCore.Parameters
5+
{
6+
public class ServiceKeyParameter : Parameter
7+
{
8+
public ServiceKeyParameter(object value) : base(nameof(ServiceKeyParameter), value, true)
9+
{
10+
ServiceKey = value;
11+
}
12+
13+
public object ServiceKey { get; }
14+
}
15+
}

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
2+
using System.Linq;
23
using System.Reflection;
34
using Microsoft.Extensions.DependencyInjection;
45
using Ninject.Activation;
56
using Ninject.Planning.Bindings;
67
using Ninject.Planning.Targets;
8+
using Ninject.Web.AspNetCore.Parameters;
79

810
namespace Ninject.Web.AspNetCore.Planning
911
{
@@ -81,10 +83,25 @@ object ITarget.ResolveWithin(IContext parent)
8183
if (serviceKeyAttributes?.Length > 0)
8284
{
8385
var result = parent.Binding.Metadata.GetServiceKey();
84-
if (result == KeyedService.AnyKey && this.Type == typeof(string))
86+
var serviceKeyParameter = parent.Parameters.LastOrDefault(x => x is ServiceKeyParameter) as ServiceKeyParameter;
87+
if (serviceKeyParameter != null)
8588
{
86-
// expected to automatically convert from AnyKey to string representation
87-
result = KeyedService.AnyKey.ToString();
89+
result = serviceKeyParameter.ServiceKey;
90+
}
91+
92+
var asConvertible = result as IConvertible;
93+
if (asConvertible != null)
94+
{
95+
try
96+
{
97+
result = Convert.ChangeType(asConvertible, this.Type);
98+
}
99+
catch (InvalidCastException)
100+
{
101+
// we have to throw and InvalidOperationException in this case, a InvalidCastException
102+
// is not passing the tests
103+
throw new InvalidOperationException("Cannot convert " + asConvertible + " to " + this.Type);
104+
}
88105
}
89106

90107
return result;

src/Ninject.Web.AspNetCore/ServiceCollectionAdapter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private IBindingNamedWithOrOnSyntax<T> ConfigureImplementationAndLifecycleWithAd
104104
// correct _scoped_ IServiceProvider is used. Fall back to root IServiceProvider when not created
105105
// through a NinjectServiceProvider (some tests do this to prove a point)
106106
var scopeProvider = context.GetServiceProviderScopeParameter()?.SourceServiceProvider ?? context.Kernel.Get<IServiceProvider>();
107-
return adapter.InstantiateFromServiceFactory(scopeProvider) as T;
107+
return adapter.InstantiateFromServiceFactory(scopeProvider, context) as T;
108108
}), adapter.Lifetime);
109109
}
110110
else

0 commit comments

Comments
 (0)