JasperFx / lamar

Fast Inversion of Control Tool and Successor to StructureMap
https://jasperfx.github.io/lamar
MIT License
573 stars 119 forks source link

GenericFactory, get current requested type, like it was possible in StructureMap #318

Closed rizi closed 1 year ago

rizi commented 2 years ago

Hi, I know it's not the best solution, but we have exactly that use case:

_.For(typeof(IFoo<>))
.Use(x =>
        {
            var ParamType = x.BuildStack.Current.RequestedType
                             .GetGenericArguments()[0];
            return BuildUsingFooFactory(ParamType);
        });
    }

Is there a way to get the information of the current requested type, like it was possible with StructureMap?

This question is quite similar, but unfortunately it has no answer: https://stackoverflow.com/questions/68832353/how-can-i-get-the-requested-type-in-a-usectx-factory-func-in-lamar-5 Br

jeremydmiller commented 2 years ago

You can use a custom Instance type for the parent, open type, then override the CloseType() method to return a subtype of LambaInstance that then calls through to your factory.

So something like:

public class MyBuilderInstance<T> : LambdaInstance<IFoo<T>>
{
    // and call through to the base constructor with s => BuildUsingFooFactory(typeof(T));
}

// and a custom instance like:

public class OpenFooInstance : Instance
{
  // override the CloseType() method to return the right MyBuilderInstance<T> where T would be the single template type
  // the service type should be typeof(IFoo<>)
}
rizi commented 2 years ago

Hi, Thx for the hint, I was able to create a "generic" solution that should work for any type (not only for IFoo/IAsyncRetryPolicy<>) --> of course it's a first draft and I would like to get feedback if I can make things better.

If you like it I could also make a pull request so that this feature is available for everyone (if so I think we need better names for OpenBuilderFactory and OpenBuilderInstance ;) ).

Please see the sample below.

And I have another question when I derive from Instance I have to implement three methods

and I have no idea how this should be implemented correctly, atm I just throw an exception and it seems this methods get not called atm, is this the correct implementation or how should I implement these methods?

br

note: IFoo == IAsyncRetryPolicy and OpenFooInstance == OpenBuilderInstance

  IContainer container = new Container(configuration =>
                                             {
                                                 configuration
                                                     .For(typeof(IAsyncRetryPolicy<>))
                                                     .Use(new OpenBuilderInstance(typeof(IAsyncRetryPolicy<>), ServiceLifetime.Transient,
                                                         (context, types) =>
                                                         {
                                                             Type closedTypeOfIPolicies = typeof(AsyncRetryPolicyProxy<>).MakeGenericType(types);

                                                             object asyncRetryPolicyFactory = context.GetRequiredService(closedTypeOfIPolicies);

                                                             return asyncRetryPolicyFactory;
                                                         }));
                                             });

        IAsyncRetryPolicy<string> asyncRetryPolicy = container.GetInstance<IAsyncRetryPolicy<string>>();
public class OpenBuilderFactory<TResult> : LambdaInstance<IServiceContext, TResult>
{
    public OpenBuilderFactory(Type serviceType, Func<IServiceContext, Type[], object> creationFactory, ServiceLifetime lifetime)
        : base(serviceType, c => (TResult)creationFactory(c, typeof(TResult).GenericTypeArguments), lifetime)
    {
    }
}

public class OpenBuilderInstance : Instance
{
    private readonly Func<IServiceContext, Type[], object> _creationFactory;

    public OpenBuilderInstance(Type type, ServiceLifetime lifetime, Func<IServiceContext, Type[], object> creationFactory)
        : base(type, type, lifetime)
    {
        _creationFactory = creationFactory;
    }

    public override Instance CloseType(Type serviceType, Type[] templateTypes)
    {
        if (!ImplementationType.IsOpenGeneric())
            throw new NotSupportedException($"{nameof(OpenBuilderInstance)} must be used with a generic type.");

        Type builderInstance = typeof(OpenBuilderFactory<>).MakeGenericType(serviceType);

        return (Instance)Activator.CreateInstance(builderInstance, serviceType, _creationFactory, Lifetime)!;
    }

    public override Func<Scope, object> ToResolver(Scope topScope)
    {
        throw new NotSupportedException();
    }

    public override object Resolve(Scope scope)
    {
        throw new NotSupportedException();
    }

    public override Variable CreateVariable(BuildMode mode, ResolverVariables variables, bool isRoot)
    {
        throw new NotSupportedException();
    } Type builderInstance = typeof(OpenBuilderFactory<>).MakeGenericType(serviceType);

        return (Instance)Activator.CreateInstance(builderInstance, serviceType, _factory, Lifetime)!;
    }

    public override Func<Scope, object> ToResolver(Scope topScope)
    {
        throw new NotImplementedException();
    }

    public override object Resolve(Scope scope)
    {
        throw new NotSupportedException();
    }

    public override Variable CreateVariable(BuildMode mode, ResolverVariables variables, bool isRoot)
    {
        throw new NotSupportedException();
    }

    public override Variable CreateVariable(BuildMode mode, ResolverVariables variables, bool isRoot)
    {
        throw new NotSupportedException();
    }
}
rizi commented 2 years ago

@jeremydmiller I have another question when I derive from Instance I have to implement three methods

Variable CreateVariable(BuildMode mode, ResolverVariables variables, bool isRoot) object Resolve(Scope scope) Func<Scope, object> ToResolver(Scope topScope))

I have no idea how this should be implemented correctly, atm I just throw an exception and it seems this methods get not called atm, is this the correct implementation or how should I implement these methods?

Br

rizi commented 2 years ago

@jeremydmiller are you interested in a PR? If yes, please let me know and please answer my questions above?

If no, I will close the issue.

Br

jeremydmiller commented 2 years ago

@rizi Sorry for being slow here, yes, just throw new NotSupportedException() in those methods. If I get around to it, I can finally formalize this pattern a bit. It comes up a couple times a year.

jeremydmiller commented 1 year ago

No activity, closing.