Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 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
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
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
using System;

namespace Ninject.Web.AspNetCore.ComplianceTests
namespace Ninject.Web.AspNetCore.ComplianceTest;

/// <summary>
/// See https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src - the dotnet/runtime
/// project which contains the dependency injection library code also contains a set of "compliance tests" that can be run against a potential alternative
/// implementation to check if it is compliant. This class here is doing just that.
///
/// The project also contains a separate test project that includes these compliance tests for a set of compliant third party DI implementations like
/// Autofac and Lightinject under https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests.
///
/// All of this is part of the https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.DependencyInjection/Microsoft.Extensions.DependencyInjection.sln
/// solution of dotnet/runtime.
/// </summary>
public class DependencyInjectionComplianceTests : Microsoft.Extensions.DependencyInjection.Specification.DependencyInjectionSpecificationTests
{
/// <summary>
/// See https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src - the dotnet/runtime
/// project which contains the dependency injection library code also contains a set of "compliance tests" that can be run against a potential alternative
/// implementation to check if it is compliant. This class here is doing just that.
///
/// The project also contains a separate test project that includes these compliance tests for a set of compliant third party DI implementations like
/// Autofac and Lightinject under https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests.
///
/// All of this is part of the https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.DependencyInjection/Microsoft.Extensions.DependencyInjection.sln
/// solution of dotnet/runtime.
/// </summary>
public class DependencyInjectionComplianceTests : Microsoft.Extensions.DependencyInjection.Specification.DependencyInjectionSpecificationTests
protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
{
protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
{
var kernel = new AspNetCoreKernel();
var factory = new NinjectServiceProviderFactory(kernel);
var kernel = new AspNetCoreKernel();
var factory = new NinjectServiceProviderFactory(kernel);

return factory.CreateBuilder(serviceCollection).Build();
}
return factory.CreateBuilder(serviceCollection).Build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Ninject.Planning.Bindings.Resolvers;

namespace Ninject.Web.AspNetCore.ComplianceTest;

/// <summary>
/// See https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src - the dotnet/runtime
/// project which contains the dependency injection library code also contains a set of "compliance tests" that can be run against a potential alternative
/// implementation to check if it is compliant. This class is running the dedicated specification tests for KEYED services.
/// </summary>
public class KeyedDependencyInjectionComplianceTests : Microsoft.Extensions.DependencyInjection.Specification.KeyedDependencyInjectionSpecificationTests
{
protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
{
var kernel = new AspNetCoreKernel();
// remove autobinding as CreateServiceWithKeyedParameter e.g. tests that no autobinding happens.
kernel.Components.Remove<IMissingBindingResolver, SelfBindingResolver>();
var factory = new NinjectServiceProviderFactory(kernel);

return factory.CreateBuilder(serviceCollection).Build();
}
}
18 changes: 18 additions & 0 deletions src/Ninject.Web.AspNetCore.Test/Fakes/KeyedNinja.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.Extensions.DependencyInjection;

namespace Ninject.Web.AspNetCore.Test.Fakes
{
#if NET8_0_OR_GREATER
public class KeyedNinja : IWarrior
{
public object Key {get; private set;}

public KeyedNinja([ServiceKey] object key)
{
Key = key;
}

public string Name => nameof(KeyedNinja);
}
#endif
}
20 changes: 20 additions & 0 deletions src/Ninject.Web.AspNetCore.Test/Fakes/NinjaWithKeyedWaepon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;

namespace Ninject.Web.AspNetCore.Test.Fakes
{
#if NET8_0_OR_GREATER

public class NinjaWithKeyedWaepon : IWarrior
{
public IWeapon Weapon { get; private set; }

public string Name => nameof(NinjaWithKeyedWaepon) + $" with weapon {Weapon.Type}";

public NinjaWithKeyedWaepon([FromKeyedServices("Longsword")] IWeapon weapon)
{
Weapon = weapon;
}
}

#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public DummyBinding()

public DummyBinding WithIndex(BindingIndex index)
{
Metadata.Set(nameof(BindingIndex), index.Next(Service));
Metadata.Set(nameof(BindingIndex), index.Next(Service, BindingIndex.DefaultIndexKey));
return this;
}

Expand Down
251 changes: 251 additions & 0 deletions src/Ninject.Web.AspNetCore.Test/Unit/ServiceProviderKeyedTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
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 OptionalExising_ServiceKeyNullResolvedAsUnkeyed()
{
var collection = new ServiceCollection();
collection.Add(new ServiceDescriptor(typeof(IWarrior),null, typeof(Samurai), ServiceLifetime.Transient));
var kernel = CreateTestKernel(collection);
var provider = CreateServiceProvider(kernel);

var warrior = provider.GetKeyedService(typeof(Samurai), null);
warrior.Should().NotBeNull().And.BeOfType(typeof(Samurai));
}

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

var warrior = provider.GetKeyedService(typeof(IWarrior), "Ninja");
warrior.Should().NotBeNull().And.BeOfType(typeof(KeyedNinja)).And.Match(x => ((KeyedNinja)x).Key.ToString() == "Ninja");
}

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

var warrior = provider.GetKeyedService(typeof(IWarrior), "Ninja");
warrior.Should().NotBeNull().And.BeOfType(typeof(NinjaWithKeyedWaepon)).And.Match(x => ((NinjaWithKeyedWaepon)x).Weapon.Type == nameof(Longsword));
}

[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("*No matching bindings are available, and the type is not self-bindable.*");
}

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
}
Loading
Loading