JasperFx / lamar

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

Are there extension points / events in lamar? #310

Closed rizi closed 2 years ago

rizi commented 2 years ago

Hi, I'm new to lamar (I previous worked with unity).

I have a few questions:

What I want to do:

  1. Register all types in lamar
  2. Go through all registered types --> create an Interceptor (Core.Proxy/Dynamic Proxy) for all registered types (which interceptor should be used will be retrieved by lamar (e.q ServiceContext.GetService().

I checked the documentation (https://jasperfx.github.io/lamar/), but wasn't able to find something, maybe I missed it.

Thx in advance

Edit @interceptor here is a possible solution (but I would like to know if there is a better way).

public class AopPolicy : IDecoratorPolicy
{
    private readonly IAopSettings _aopSettings;

    public AopPolicy()
    {
        //note rie: We can't inject something in a policy therefore we have to get this information from the app.config directly.
        _aopSettings = new AopSettings();
    }

    public bool TryWrap(Instance inner, out Instance wrapped)
    {
        Type currentTypeToIntercept = inner.ImplementationType;
        Type serviceType = inner.ServiceType;

        if (ShouldSkipInterception(currentTypeToIntercept, serviceType))
        {
            wrapped = null!;

            return false;
        }

        wrapped = new LambdaInstance(serviceType, provider => CreateProxy(inner, provider), inner.Lifetime);

        return true;
    }

    protected static bool IsDelegateType(Type serviceType)
    {
        return typeof(Delegate).IsAssignableFrom(serviceType);
    }

    protected static bool IsTypeFromLamarOrMicrosoftAssembly(Type? type)
    {
        string? name = type?.Assembly.GetName().Name;

        return name != null && (name.Equals("Lamar", StringComparison.OrdinalIgnoreCase) || name.Equals("Microsoft", StringComparison.OrdinalIgnoreCase));
    }

    private static object CreateProxy(Instance inner, IServiceProvider provider)
    {
        IServiceContext serviceContext = provider.GetRequiredService<IServiceContext>();
        IInterceptor[] interceptors = provider.GetRequiredService<IInterceptor[]>();

        ProxyGenerationOptions proxyGenerationOptions = new ProxyGenerationOptions();

        object proxy = inner.ImplementationType.IsInterface
            ? CreateInterfaceProxy(inner, inner.ServiceType, serviceContext, proxyGenerationOptions, interceptors)
            : CreateClassProxy(inner.ImplementationType, serviceContext, proxyGenerationOptions, interceptors);

        return proxy;
    }

    private static object CreateClassProxy(Type implementationType, IServiceContext serviceContext, ProxyGenerationOptions proxyGenerationOptions, IInterceptor[] interceptors)
    {
        ConstructorInfo[] constructors = implementationType.GetConstructors();
        ParameterInfo[] constructorParameters = constructors.First().GetParameters();

        List<object> resolvedConstructorParameter = new List<object>();

        if (constructors.Length > 0 && constructorParameters.Length > 0)
            resolvedConstructorParameter.AddRange(constructorParameters.Select(info => serviceContext.GetInstance(info.ParameterType)));

        return new ProxyGenerator().CreateClassProxy(implementationType, proxyGenerationOptions, resolvedConstructorParameter.ToArray(), interceptors);
    }

    private static object CreateInterfaceProxy(Instance inner, Type serviceType, IServiceProvider serviceContext, ProxyGenerationOptions proxyGenerationOptions, IInterceptor[] interceptors)
    {
        object target = inner.Resolve(serviceContext.GetRequiredService<Scope>());

        return new ProxyGenerator().CreateInterfaceProxyWithTarget(serviceType, target, proxyGenerationOptions, interceptors);
    }

    private bool ShouldSkipInterception(Type currentTypeToIntercept, Type serviceType)
    {
        return
            !_aopSettings.ShouldIntercept
            || IsTypeFromLamarOrMicrosoftAssembly(currentTypeToIntercept)
            || serviceType.IsClass && serviceType.IsSealed
            || serviceType.IsAssignableTo(typeof(ILamarConfigurationModlue))
            || serviceType == typeof(IAopSettings) || serviceType == typeof(IInterceptor)
            || IsDelegateType(currentTypeToIntercept)
            || typeof(IEnumerable).IsAssignableFrom(currentTypeToIntercept);
    }
jeremydmiller commented 2 years ago

Okay, so that's a lot. Let's see:

Just to get this out here, I think you're potentially setting yourself up for a non-idiomatic usage of Lamar that's going to be difficult to maintain and troubleshoot over time. What are you hoping to accomplish with all this dynamic proxy stuff?

During the resolving time I need to check if certain criteria match, like are we resolving a certain type from a nested container or not, I tried create a custom policy that derived from IInstancePolicy or ILamarPolicy and I was able to find the lifetime of the instance that is going to be resolved by lamar, but I wasn't able to find which container resolves that instance (the parent container or the nested container) --> is there a way to do it (with a policy or some other way)?

I'd strongly recommend against doing it that way. By and large, I recommend against having runtime logic within the Lamar registrations. You could use Lambda registrations where you'd have access to the IServiceContext (if done through Lamar) or IServiceProvider through .Net Core DI abstractions, and cast that to IContainer, which would be the Lamar container building your service.

Is it possible to resolve a type within a policy (e.q Instance inner contains at least two methods Resolve() and QuickResolve() but both require a scope and I don't know how to get the scope)?

To what end? Why would you need to do that?

I also wonder what's the difference between Resolve and QuickResolve in general?

QuickResolve() is done through pure Reflection, and that's what Lamar does for singletons that are only resolved once so you don't have the extra overhead of generating and compiling dynamic code for repeated resolves

I also want to add a interceptor for every registered type programmatically, is there a way to do that as well? --> it seems this is question is quite similar (Is it possible to intercept instances before they are returned so it is possible to wrap them without doing manual registry configurations for every type? #279). I tryed to create a custom AOPPolicy that derives from IDecoratorPolicy but I don't know how to create the "wrapped"-instance.

IDecoratorPolicy is what you want. I've never been very interested in AOP, so I don't know what to tell you about the dynamic proxy stuff. Are you trying to do runtime or compile time AOP though? You wouldn't need to know anything about the inner type if you were doing runtime AOP, so I'm not sure why your decorator policy up above would even care about constructors or whether it's a lambda instance.

Are there any events I can subscribe to, like container created, child container created, type resolved, type disposed, ....?

No, and I don't know why that would even be necessary. Autofac has that kind of thing, and it's not coincidentally very slow compared to more modern DI containers. By and large, the goal of Lamar for me was to have a simpler DI container that was very fast with much simpler internals. I consciously chose to make it less flexible at runtime than StructureMap was. The kind of dynamic, runtime selection stuff you're doing here isn't Lamar's strong spot -- and that's quite purposeful.

rizi commented 2 years ago

@jeremydmiller

@I'd strongly recommend against doing it that way. By and large, I recommend against having runtime logic within the Lamar registrations. You could use Lambda registrations where you'd have access to the IServiceContext (if done through Lamar) or IServiceProvider through .Net Core DI abstractions, and cast that to IContainer, which would be the Lamar container building your service.

We just would use this as a safety net to ensure that types that are registered as Scope are not resolved by the global container (in our use cases they need to be resolved via a nested container / child container).

So there is no way to do this, right?

@Is it possible to resolve a type within a policy (e.q Instance inner contains at least two methods Resolve() and QuickResolve() but both require a scope and I don't know how to get the scope)?

To what end? Why would you need to do that?

We need to inject a class that has been created from a xml/json file and registered within Lamar. (it contains the information if a proxy should be created or not (we decide it by namespace (this namespaces are stored in a xml/json file).

So is there a way to inject a type/class from Lamar within a policy?

@IDecoratorPolicy is what you want. I've never been very interested in AOP, so I don't know what to tell you about the dynamic proxy stuff. Are you trying to do runtime or compile time AOP though? You wouldn't need to know anything about the inner type if you were doing runtime AOP, so I'm not sure why your decorator policy up above would even care about constructors or whether it's a lambda instance.

We are using runtime proxies (proxy.core/dynamic proxy). I use the inner type to resolve the service from the container --> because I have to pass the instance to the proxy generator.

And the stuff with constructor is only needed if a concrete class is resolved because the proxy generator will create this class (and not the ioc container) and therefore we have to pass all constructor parameters to the proxy generator).

I have removed the "IsLambdaInstance(...)" check in the code sample above, @jeremydmiller has told me how to do it without the check (in a cleaner way).