z4kn4fein / stashbox

A lightweight, fast, and portable dependency injection framework for .NET-based solutions.
https://z4kn4fein.github.io/stashbox
MIT License
141 stars 10 forks source link

Question: How to work with dependency overrides from factory method? #105

Closed schuettecarsten closed 3 years ago

schuettecarsten commented 3 years ago

I can use RegistrationConfigurator.WithFactory to register a factory method. That's fine. But now I need to check for specific dependency overrides in that factory method. If a class is not available in the dependency overrides, I want to handle this myself in the factory (by calculating a specific default value that might change from time to time).

Is that possible?

z4kn4fein commented 3 years ago

Hi there, unfortunately, there is no way to access the resolution request's ResolutionContext from a factory. I'll investigate how much effort would it take to make the current context publicly available for the resolution tree.

In the meantime, could you explain your case a bit more? Is it a must to access the overrides?

schuettecarsten commented 3 years ago

I plan to use dependency overrides to pass parameters into the constructor and need to make sure that the parameters instance(s) is/are passed as dependency override and are not created as transient dependency by Stashbox itself.

z4kn4fein commented 3 years ago

I came up with an idea and pushed it into the pre-release channel, here you can check the test for the usage.

What do you think?

schuettecarsten commented 3 years ago

Thank you for the very good solution. During my tests, I ran info a new issue: It is not possible to tell the UnknownRegistrationConfigurator to not resolve an unknown type if the service type is a class type. In that case, the UnknownRegistrationConfigurator is already initialized correctly, so that if (!registrationConfigurator.TypeMapIsValid(out _)) in line 42 in UnknownTypeResolver is always false.

I did not find a working solution with the current implementation, as context.SetImplementationType(typeof(void)); and context.SetImplementationType(null); both fail.

I have added a method to UnknownRegistrationConfigurator:

         /// <summary>
        /// Resets the current registration's implementation type and factory settings.
        /// </summary>
        /// <returns>The configurator itself.</returns>
        public UnknownRegistrationConfigurator Reset()
        {
            base.ImplementationType = null;
            this.Context.Factory = null;
            this.Context.FactoryParameters = null;
            this.Context.IsFactoryDelegateACompiledLambda = false;

            return this;
        }

To make this work, a null check must be added to RegistrationConfiguration.TypeMapIsValid in line 39:

        internal bool TypeMapIsValid(out string error)
        {
            error = null;
            if (this.ImplementationType == null)
            {
                error = $"Implementation type not set.";
                return false;
            }

            if (this.Context.Factory != null)
                return true;

Unfortunately, this also breaks dependency overrides as Stashbox does the dependency override check after it checks for registration validity. So, dependency overrides currently cannot be used to pass values into types that are not yet registered.

z4kn4fein commented 3 years ago

So, you want to tell in the unknown type configurator to DO NOT resolve some types, right?

Then, I would approach this in a different way. What do you think about this syntax: Skip<TypeToSkip1>().Skip<TypeToSkip2>() and Skip(IEnumerable<Type> typesToSkip)?

This list would be checked in the resolver to skip the containing types from resolution.

Or just a general Skip() method that could be used in a conditional statement like:

if (ctx.ServiceType == typeof(TypeToSkip) || ...) 
{
    ctx.Skip();
}
schuettecarsten commented 3 years ago

Yes, but this makes it neccessary for me to process all types that I do not want to register, which might be thousands. At the moment, I do this check when StashBox tries to resolve an unknown type. I did not recognize yet that it will always resolve class types.

I have updated the pull request #106 - renamed the Reset() method to Skip(), which simply resets the factory and implementation type of the context, and added a check for dependency overrides.

z4kn4fein commented 3 years ago

Alright, after a second thought I also ended up with the second solution, it feels more granular for the context. I checked your PR and I rather introduce a new bool property where we store the info about skipping. Also, the dependency override check is not relevant at that point because the unknown type resolution is executed far after the override search. Or is there another case you wanted to cover with that?

schuettecarsten commented 3 years ago

I added the dependency override check there because I ran into an ResolutionFailedException when I tried to pass an instance as depencency override and Skip the unknown type resolution. So yes, the override search runs before, but resolution still fails.

That's why I am unhappy with my solution, as the whole resolving stuff is not neccessary for types that are already available as dependency override.

z4kn4fein commented 3 years ago

That sounds weird, it shouldn't be picked by the unknown resolution when it's available as override. Let me check this out. Do you have a quick failing example? Thanks!

schuettecarsten commented 3 years ago

Your solution with Skip works like a charm and has no errors with dependency overrides. My exception was caused by my implementation. Thank you!

schuettecarsten commented 3 years ago

Coming back to the original question, also your test code works fine. But unfortunately, there is no WithFactory method that accepts a generic parameter on RegistrationConfigurator.

Update - I have added the following methods to src/Registration/Fluent/RegistrationConfigurator.cs, see my pull request.

z4kn4fein commented 3 years ago

I merged your PR and made those methods available for decorators, also, gave them some tests. Could you please try the latest version? Thanks!

schuettecarsten commented 3 years ago

Thank you, now everything works as expected.

z4kn4fein commented 3 years ago

Changes are released in v3.6.4