Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6ed4084
initial implementation of optional keyed services
DominicUllmann Dec 24, 2025
439a94a
add support for keyed required service
DominicUllmann Dec 24, 2025
dd9919e
add test for keyedimplementationfactory as well
DominicUllmann Dec 24, 2025
f30ada7
refactor to remove code duplication in ServiceCollectionAdapter
DominicUllmann Dec 24, 2025
6521615
ensure that transient factory works as well correct with keyed service
DominicUllmann Dec 24, 2025
e9fadc6
align with ienumerable resolution in the Microsoft DI world
DominicUllmann Dec 24, 2025
85902e7
simplify code
DominicUllmann Dec 24, 2025
20ceda7
Added the keyed service specification tests to the compliance test pr…
lord-executor Dec 26, 2025
b03a743
start to fix compliance tests
DominicUllmann Dec 26, 2025
5919021
add initial support for KeyedService.AnyKey
DominicUllmann Dec 26, 2025
a11c483
remove check for no contraint. We have always a constraint now with e…
DominicUllmann Dec 26, 2025
09ef1f9
add support for ServiceKeyAttribute and FromKeyedService
DominicUllmann Dec 28, 2025
baf79ef
remove SelfBIndingResolver as one tests check that no automatic self …
DominicUllmann Dec 28, 2025
13f268b
fix binding overriding with keyed services
DominicUllmann Dec 28, 2025
61c9c5a
handle special cases with AnyKey as well as with null key
DominicUllmann Dec 28, 2025
cb44ae5
ensure that we only match non-keyed if ServiceLookupMode is NullKey
DominicUllmann Dec 28, 2025
e6fe5d0
introduce runtime parameter so that we can inject the servicekey used…
DominicUllmann Dec 28, 2025
32d066f
fix open generics resolution
DominicUllmann Dec 28, 2025
ae84882
fix exception when trying unsupported case for servicekey
DominicUllmann Dec 28, 2025
87ea24c
refactor to support dynamic binding of services registred with anykey…
DominicUllmann Dec 30, 2025
fd04c75
add support for inheriting fromkeyedservices value for the constraint…
DominicUllmann Dec 31, 2025
4abe129
throw InvalidOperationException in case a required service or a fromk…
DominicUllmann Dec 31, 2025
2a5ef58
support default parameter values
DominicUllmann Dec 31, 2025
8e0802e
ignore tests which are not reasonable for a NInject based implementation
DominicUllmann Dec 31, 2025
3e6dfa9
simplify code, fix review
DominicUllmann Dec 31, 2025
e7f7c05
fix comments
DominicUllmann Dec 31, 2025
6bb6ccb
improve binding and comment
DominicUllmann Jan 2, 2026
299b11f
use a class instead of just a string constant for unkeyed index key
DominicUllmann Jan 2, 2026
baa6471
remove no longer needed code
DominicUllmann Jan 2, 2026
a99aa77
rename metadata
DominicUllmann Jan 2, 2026
765aaf3
add also FromKeyedServices which has a FromKeyServicesChild as well
DominicUllmann Jan 2, 2026
d605d0c
add same performance improvement as Ninject does for reading constraints
DominicUllmann Jan 2, 2026
20ef1d1
replace resolve of GetCustomAttributes but redirect to new lazy field
DominicUllmann Jan 2, 2026
f310f0a
correct way to restore wanring
DominicUllmann Jan 2, 2026
6d5c284
remove another warning
DominicUllmann Jan 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions src/Ninject.Web.AspNetCore.Test/Unit/ServiceProviderKeyedTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using AwesomeAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Ninject.Web.AspNetCore.Test.Fakes;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace Ninject.Web.AspNetCore.Test.Unit
{

#if NET8_0_OR_GREATER

public class ServiceProviderKeyedTest
{

[Fact]
public void OptionalKeyedServiceCollectionExisting_CorrectServiceResolved()
{
var collection = new ServiceCollection();
collection.Add(new ServiceDescriptor(typeof(IWarrior),"Samurai", typeof(Samurai), ServiceLifetime.Transient));
collection.Add(new ServiceDescriptor(typeof(IWarrior), "Ninja1", new Ninja("test")));
collection.Add(new ServiceDescriptor(typeof(IWarrior),"Ninja2",
(provider, key) => new Ninja("test:" + key.ToString()), ServiceLifetime.Transient));
var kernel = CreateTestKernel(collection);
var provider = CreateServiceProvider(kernel);

provider.GetKeyedService(typeof(IWarrior), "Samurai").Should().NotBeNull().And.BeOfType(typeof(Samurai));
provider.GetKeyedService(typeof(IWarrior), "Ninja1").Should().NotBeNull().And.BeOfType(typeof(Ninja)).
And.Match(x => ((Ninja)x).Name == "test");
var ninja2First = provider.GetKeyedService(typeof(IWarrior), "Ninja2");
var ninja2Second = provider.GetKeyedService(typeof(IWarrior), "Ninja2");
ninja2First.Should().NotBeNull().And.BeOfType(typeof(Ninja)).
And.Match(x => ((Ninja)x).Name == "test:Ninja2");
ninja2Second.Should().NotBeNull().And.BeOfType(typeof(Ninja)).
And.Match(x => ((Ninja)x).Name == "test:Ninja2");
ninja2First.Should().NotBeSameAs(ninja2Second);
}

[Fact]
public void OptionalKeyedNinjectDirectBindingExisting_CorrectServiceResolved()
{
var kernel = CreateTestKernel();
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
var provider = CreateServiceProvider(kernel);

provider.GetKeyedService(typeof(IWarrior), "Samurai").Should().NotBeNull().And.BeOfType(typeof(Samurai));
provider.GetKeyedService(typeof(IWarrior), "Ninja").Should().NotBeNull().And.BeOfType(typeof(Ninja));
}

[Fact]
public void OptionalKeyedNonExisting_SingleServiceResolvedToNull()
{
var kernel = CreateTestKernel();
var provider = CreateServiceProvider(kernel);

provider.GetKeyedService(typeof(IWarrior), "Samurai").Should().BeNull();
}

[Fact]
public void OptionalExistingMultipleKeydServices_ResolvedQueriedAsList()
{
var kernel = CreateTestKernel();
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Warrior"));;
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Warrior"));
var provider = CreateServiceProvider(kernel);

var result = provider.GetKeyedService(typeof(IList<IWarrior>), "Warrior") as IEnumerable<IWarrior>;

result.Should().NotBeNull();
var resultList = result.ToList();
resultList.Should().HaveCount(2);
resultList.Should().Contain(x => x is Samurai);
resultList.Should().Contain(x => x is Ninja);
}

[Fact]
public void OptionalExistingMultipleKeydServices_NotResolvedAsListNonKeyed()
{
var kernel = CreateTestKernel();
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));;
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
var provider = CreateServiceProvider(kernel);

var result = provider.GetService(typeof(IList<IWarrior>)) as IEnumerable<IWarrior>;

result.Should().NotBeNull();
var resultList = result.ToList();
resultList.Should().HaveCount(0);
}

[Fact]
public void ExistingMultipleServices_ResolvesNonKeyedToNull()
{
var kernel = CreateTestKernel();
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));;
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
var provider = CreateServiceProvider(kernel);

provider.GetService(typeof(IWarrior)).Should().BeNull();
}

[Fact]
public void RequiredKeyedServiceCollectionExisting_CorrectServiceResolved()
{
var collection = new ServiceCollection();
collection.Add(new ServiceDescriptor(typeof(IWarrior),"Samurai", typeof(Samurai), ServiceLifetime.Transient));
collection.Add(new ServiceDescriptor(typeof(IWarrior), "Ninja", new Ninja("test")));
var kernel = CreateTestKernel(collection);
var provider = CreateServiceProvider(kernel);

provider.GetRequiredKeyedService(typeof(IWarrior), "Ninja").Should().NotBeNull().And.BeOfType(typeof(Ninja));
provider.GetRequiredKeyedService(typeof(IWarrior), "Samurai").Should().NotBeNull().And.BeOfType(typeof(Samurai));

}

[Fact]
public void RequiredKeyedNinjectDirectBindingExisting_CorrectServiceResolved()
{
var kernel = CreateTestKernel();
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
var provider = CreateServiceProvider(kernel);

provider.GetRequiredKeyedService(typeof(IWarrior), "Samurai").Should().NotBeNull().And.BeOfType(typeof(Samurai));
provider.GetRequiredKeyedService(typeof(IWarrior), "Ninja").Should().NotBeNull().And.BeOfType(typeof(Ninja));
}

[Fact]
public void RequiredKeyedNonExisting_SingleServiceResolvedToException()
{
var kernel = CreateTestKernel();
var provider = CreateServiceProvider(kernel);

Action action = () => provider.GetRequiredKeyedService(typeof(IWarrior), "Samurai");
action.Should().Throw<ActivationException>().WithMessage("*No matching bindings are available*");
}

[Fact]
public void RequiredExistingMultipleKeydServices_ResolvedQueriedAsList()
{
var kernel = CreateTestKernel();
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Warrior"));;
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Warrior"));
var provider = CreateServiceProvider(kernel);

var result = provider.GetRequiredKeyedService(typeof(IList<IWarrior>), "Warrior") as IEnumerable<IWarrior>;

result.Should().NotBeNull();
var resultList = result.ToList();
resultList.Should().HaveCount(2);
resultList.Should().Contain(x => x is Samurai);
resultList.Should().Contain(x => x is Ninja);
}

[Fact]
public void RequiredExistingMultipleKeydServices_NotResolvedAsListNonKeyed()
{
var kernel = CreateTestKernel();
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));;
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
var provider = CreateServiceProvider(kernel);

var result = provider.GetRequiredService(typeof(IList<IWarrior>)) as IEnumerable<IWarrior>;

result.Should().NotBeNull();
var resultList = result.ToList();
resultList.Should().HaveCount(0);
}

[Fact]
public void ExistingMultipleServices_ResolvesNonKeyedToException()
{
var kernel = CreateTestKernel();
kernel.Bind<IWarrior>().To<Samurai>().WithMetadata(nameof(ServiceKey), new ServiceKey("Samurai"));;
kernel.Bind<IWarrior>().ToConstant(new Ninja("test")).WithMetadata(nameof(ServiceKey), new ServiceKey("Ninja"));
var provider = CreateServiceProvider(kernel);

Action action = () => provider.GetRequiredService(typeof(IWarrior));
action.Should().Throw<ActivationException>().WithMessage("*More than one matching bindings are available*");
}

private IServiceProvider CreateServiceProvider(AspNetCoreKernel kernel)
{
NinjectServiceProviderBuilder builder = CreateServiceProviderBuilder(kernel);
var provider = builder.Build();
return provider;
}

private NinjectServiceProviderBuilder CreateServiceProviderBuilder(AspNetCoreKernel kernel)
{
var collection = new ServiceCollection();
var factory = new NinjectServiceProviderFactory(kernel);
var builder = factory.CreateBuilder(collection);
return builder;
}

private AspNetCoreKernel CreateTestKernel(IServiceCollection collection = null)
{
var kernel = new AspNetCoreKernel(new NinjectSettings() { LoadExtensions = false });
kernel.Load(typeof(AspNetCoreApplicationPlugin).Assembly);
if (collection != null)
{
new ServiceCollectionAdapter().Populate(kernel, collection);
}

return kernel;
}

}
#endif
}
28 changes: 28 additions & 0 deletions src/Ninject.Web.AspNetCore/DefaultDescriptorAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using Microsoft.Extensions.DependencyInjection;

namespace Ninject.Web.AspNetCore
{
/// <summary>
/// This ServiceDescriptorAdapter is used when ServiceDescriptor.IsKeyedService == false
/// This was always the case before .NET 8.0
/// </summary>
public class DefaultDescriptorAdapter : IDescriptorAdapter
{
private ServiceDescriptor _descriptor;

public DefaultDescriptorAdapter(ServiceDescriptor descriptor)
{
_descriptor = descriptor;
}

public Type ImplementationType => _descriptor.ImplementationType;
public object ImplementationInstance => _descriptor.ImplementationInstance;
public bool UseServiceFactory => _descriptor.ImplementationFactory != null;
public object InstantiateFromServiceFactory(IServiceProvider provider)
{
return _descriptor.ImplementationFactory(provider);
}
public ServiceLifetime Lifetime => _descriptor.Lifetime;
}
}
37 changes: 37 additions & 0 deletions src/Ninject.Web.AspNetCore/IDescriptorAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using Microsoft.Extensions.DependencyInjection;

namespace Ninject.Web.AspNetCore
{
/// <summary>
/// This interface allows to handle the differences between keyed and non keyed implementation instruction
/// on ServiceDescriptors
/// </summary>
public interface IDescriptorAdapter
{
/// <summary>
/// Returns the type to instantiate if instantiation should be done by type.
/// </summary>
Type ImplementationType { get; }

/// <summary>
/// Returns the instance if a specific instance is configured on the descriptor
/// </summary>
object ImplementationInstance { get; }

/// <summary>
/// Returns true, if a service factory is configured on the descriptor
/// </summary>
bool UseServiceFactory { get; }

/// <summary>
/// If UseServiceFactory returns true, use this method to instantiate via factory.
/// </summary>
object InstantiateFromServiceFactory(IServiceProvider provider);

/// <summary>
/// The lifetime coonfigured for the service descriptor
/// </summary>
ServiceLifetime Lifetime { get; }
}
}
31 changes: 31 additions & 0 deletions src/Ninject.Web.AspNetCore/KeyedDescriptorAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using Microsoft.Extensions.DependencyInjection;

namespace Ninject.Web.AspNetCore
{
#if NET8_0_OR_GREATER
/// <summary>
/// This ServiceDescriptorAdapter is used when ServiceDescriptor.IsKeyedService == true
/// </summary>
public class KeyedDescriptorAdapter : IDescriptorAdapter
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this one to prevent code duplications in ServiceCollectionAdapter. MS decided to use Keyed* properties on the service descriptor for keyed services.
Note that the implementation factory receives the ServiceKey => that's maybe one reason for the different set of properties for keyed services.

{

private ServiceDescriptor _descriptor;

public KeyedDescriptorAdapter(ServiceDescriptor descriptor)
{
_descriptor = descriptor;
}

public Type ImplementationType => _descriptor.KeyedImplementationType;
public object ImplementationInstance => _descriptor.KeyedImplementationInstance;
public bool UseServiceFactory => _descriptor.KeyedImplementationFactory != null;
public object InstantiateFromServiceFactory(IServiceProvider provider)
{
return _descriptor.KeyedImplementationFactory(provider, _descriptor.ServiceKey);
}

public ServiceLifetime Lifetime => _descriptor.Lifetime;
}
#endif
}
Loading
Loading