-
Notifications
You must be signed in to change notification settings - Fork 2
Add support for IKeyedServiceProvider. #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
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 439a94a
add support for keyed required service
DominicUllmann dd9919e
add test for keyedimplementationfactory as well
DominicUllmann f30ada7
refactor to remove code duplication in ServiceCollectionAdapter
DominicUllmann 6521615
ensure that transient factory works as well correct with keyed service
DominicUllmann e9fadc6
align with ienumerable resolution in the Microsoft DI world
DominicUllmann 85902e7
simplify code
DominicUllmann 20ceda7
Added the keyed service specification tests to the compliance test pr…
lord-executor b03a743
start to fix compliance tests
DominicUllmann 5919021
add initial support for KeyedService.AnyKey
DominicUllmann a11c483
remove check for no contraint. We have always a constraint now with e…
DominicUllmann 09ef1f9
add support for ServiceKeyAttribute and FromKeyedService
DominicUllmann baf79ef
remove SelfBIndingResolver as one tests check that no automatic self …
DominicUllmann 13f268b
fix binding overriding with keyed services
DominicUllmann 61c9c5a
handle special cases with AnyKey as well as with null key
DominicUllmann cb44ae5
ensure that we only match non-keyed if ServiceLookupMode is NullKey
DominicUllmann e6fe5d0
introduce runtime parameter so that we can inject the servicekey used…
DominicUllmann 32d066f
fix open generics resolution
DominicUllmann ae84882
fix exception when trying unsupported case for servicekey
DominicUllmann 87ea24c
refactor to support dynamic binding of services registred with anykey…
DominicUllmann fd04c75
add support for inheriting fromkeyedservices value for the constraint…
DominicUllmann 4abe129
throw InvalidOperationException in case a required service or a fromk…
DominicUllmann 2a5ef58
support default parameter values
DominicUllmann 8e0802e
ignore tests which are not reasonable for a NInject based implementation
DominicUllmann 3e6dfa9
simplify code, fix review
DominicUllmann e7f7c05
fix comments
DominicUllmann 6bb6ccb
improve binding and comment
DominicUllmann 299b11f
use a class instead of just a string constant for unkeyed index key
DominicUllmann baa6471
remove no longer needed code
DominicUllmann a99aa77
rename metadata
DominicUllmann 765aaf3
add also FromKeyedServices which has a FromKeyServicesChild as well
DominicUllmann d605d0c
add same performance improvement as Ninject does for reading constraints
DominicUllmann 20ef1d1
replace resolve of GetCustomAttributes but redirect to new lazy field
DominicUllmann f310f0a
correct way to restore wanring
DominicUllmann 6d5c284
remove another warning
DominicUllmann File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
214 changes: 214 additions & 0 deletions
214
src/Ninject.Web.AspNetCore.Test/Unit/ServiceProviderKeyedTest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| { | ||
|
|
||
| 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 | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.