-
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
base: main
Are you sure you want to change the base?
Conversation
| { | ||
| var result = _resolutionRoot.Get(serviceType); | ||
| return result; | ||
| if (!IsListType(serviceType, out var elementType)) |
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.
It is not nice that we have to distinguish here between request for IEnumerable and just T. Sadly, this is required due to a potential bug in Ninject.
I wonder if KernelBase.UpdateRequest should not pass the constraint instead of just ignoring it in case of requesting all of a certain type.
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.
Fun fact: Explicitly binding an IList<T> and then requesting exactly that IList<T> does not actually resolve that binding.
var kernel = new StandardKernel();
kernel.Bind<IWarrior>().To<Ninja>().WithConstructorArgument("name", "Nick");
kernel.Bind<IList<IWarrior>>().ToConstant(new List<IWarrior> { new Samurai() });
var warriors = kernel.Get<IList<IWarrior>>();
warriors.Count().Should().Be(1);
warriors.First().Should().BeOfType<Samurai>(); // FAIL: it's actually a NinjaThe code in Ninject.KernelBase.Resolve always treats collection type services as a special type of request. Constraints are not applied to collections, but they are also not applied to the collection items since as you mentioned the UpdateRequest just ignores them and passes null instead. If it were applied to collections, then I would call that a design decision, but since they are not applied to either of them, I'd call it a bug.
|
|
||
| private static object ConvertToTypedEnumerable(Type elementType, IEnumerable<object> objectList) | ||
| { | ||
| var castMethod = EnumerableCastMethod.MakeGenericMethod(elementType); |
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.
This is similar to how Ninject does it, but I can't reuse the Ninject version directly as it is internal. Here we should also handle the case if an array or list was requested similar to how Ninject does it.
| /// <summary> | ||
| /// This ServiceDescriptorAdapter is used when ServiceDescriptor.IsKeyedService == true | ||
| /// </summary> | ||
| public class KeyedDescriptorAdapter : IDescriptorAdapter |
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.
…oject - This is currently failing 45 out of the 54 keyed service specification tests
|
Not sure if you saw that, but there is actually a separate set of "specification tests" in the "Microsoft.Extensions.DependencyInjection.Specification" namespace just for testing keyed services. And of course, a lot of them revolve around generics which is why currently 45 out of 54 of these services fail. I think if we're trying to support keyed services, the result should somehow get all of these tests to green. This is why I abandoned that side-project when keyed services first arrived on the scene. I could not figure out a way to satisfy all of those requirements without essentially building two different Kernels, one for keyed services and one for unkeyed services. |
| return true; | ||
| } | ||
|
|
||
| if (genericTypeDefinition == typeof(IEnumerable<>)) |
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.
Hmm... why is IEnumerable<> on its own while the other list-ish types are grouped together above?
…ither filtering for keyed or non-keyed
Thanks a lot, I was wondering about that. Thanks for having added these. I have discovered a few features I have not implemented yet and also some implementation problems. |
I definitely didn't remember all of them, but I do remember that this was the best example I have ever seen that an interface on its own is just a horribly insufficient form of specification 😬. It appears that they simply made some design decisions for what would work best with their particular implementation and that then became the implicit specification. |
|
This is also definitely interesting... public class Service : IService
{
private readonly string _id;
public Service() => _id = Guid.NewGuid().ToString();
public Service([ServiceKey] string id) => _id = id; // oh yeah, btw: the ServiceKey can be injected into the service
public override string? ToString() => _id;
} |
…binding happens; ensure that ConstructorScorer doesn't discard a constructor with ServiceKey attribute in favour of another constructor
| /// This ensures that a binding with a different servicekey | ||
| /// can't override a binding with a non-matching servicekey | ||
| /// </summary> | ||
| public class ServiceTypeKey : IEquatable<ServiceTypeKey> |
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.
We have to split the binidngindex by servicekey as otherwise the overriding doesn't work correctly.
| { | ||
| var hasInjectAttribute = constructor.HasAttribute(Settings.InjectAttribute); | ||
| var hasObsoleteAttribute = constructor.HasAttribute(typeof(ObsoleteAttribute)); | ||
| var directive = new ConstructorInjectionDirectiveWithKeyedSupport(constructor, InjectorFactory.Create(constructor)) |
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.
We have to create a ConstructorInjectionDirectiveWithKeyedSupport instead of a ConstructorDirective. Therefore we have to use an own implementation here.
| protected override ITarget[] CreateTargetsFromParameters(ConstructorInfo method) | ||
| { | ||
| return method.GetParameters(). | ||
| Select((Func<ParameterInfo, ParameterTarget>) (parameter => new ParameterTargetWithKeyedSupport(method, parameter))). |
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.
Important here is to create a ParameterTargetWithKeyedSupport to support FromKeyedService as well as ServiceKey attribute.
| return binding => { | ||
| var latest = true; | ||
| if (request.IsUnique && request.Constraint == null) | ||
| if (request.IsUnique) |
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.
As we have now always constraints (either to exclude keyed bindings or only consider keyed bindings, we can't check for request.Constraint == null. But this should not be a big issue as Microsoft.Extension.DI bindings can't provide custom constraints. For normal ninject bindings we don't have BindingIndex, so we can always apply them here in my opinion. Do you see any reason why we have request.Constraint == null here in the first place @lord-executor ? All the existing tests still work when removing it.
Current state is that 20 of the new tests fail...
Tried to implement the keyed service provider support for >= .NET 8.0 without breaking < .NET 8.0
Sadly, there is one complication in this with resultion of IEnumerable. Ninject sadly doesn't consider metadata constraint when resolving an IEnumerable. This seems to be a potential bug in Ninject.
The following code snippet shows what Microsoft DI expects:
We can't resolve all the elements without considering the service key. The Microsoft DI always applies the constraint.
The PR should be extended with conversion to Array and List as well, that's not yet done. The current state is for disussion.