Scrutor icon indicating copy to clipboard operation
Scrutor copied to clipboard

Support for optional open-generic decorators

Open vanbukin opened this issue 6 years ago • 3 comments

I thing it would be pretty nice to add .Decorate method overload that accepts Func<Type, bool> where Type is decorated type. If that function returns true than decorator will be applied, otherwise it should skip decorating for specific type. It could be helpful for cases where some decorated types has specific attributes, and if they (attributes on decorated type) exists, then decorator should be applied, otherwise - they should called "as is". Thanks. P.S. - sorry for my English)

vanbukin avatar May 23 '18 08:05 vanbukin

It helps to move reflection operations from run-time to configure time.

vanbukin avatar May 25 '18 05:05 vanbukin

Hi there! Are there any workaround to accomplish this?

rtellez91 avatar Jul 13 '21 22:07 rtellez91

One of the problems I see is that, if the service descriptor has an implementation type or implementation instance, to decorate, those 2 properties will be replaced by an implementationFactory for the decoration to work.

I tried to do this as well in 3 different ways but they all look ugly:

  1. Scanning again the types from parameterized assemblies to get the implementation types based on the open-generic service type to be decorated
 public static IServiceCollection Decorate(this IServiceCollection services, Type openGenericServiceType,
        Type openGenericDecoratorType, Func<DecoratorPredicateContext, bool> predicate, params Assembly[] assemblies)
    {
        if (!assemblies.Any())
        {
            assemblies = assemblies.Append(Assembly.GetCallingAssembly()).ToArray();
        }

       var contexts = assemblies
            .SelectMany(x => x.ExportedTypes)
            .AssignableTo(openGenericServiceType)
            .Where(x => !x.IsAbstract)
            .Select(x => new DecoratorPredicateContext(x.GetInterface(openGenericServiceType), x))
            .Where(predicate)
            .ToArray();
			
        foreach (var context in contexts)
        {
            services.Decorate(context.ServiceType,
                openGenericDecoratorType.MakeGenericType(context.ServiceType.GenericTypeArguments));
        }

        return services;
    }

  1. This requires an injected cached instance with the metadata about the services that you want to decorate(this service could also be used to get the info about attributes inside the decorators and so on)
public static IServiceCollection DecorateHandlersConditional(this IServiceCollection services, Type openGenericServiceType,
        Type openGenericDecoratorType, Func<DecoratorPredicateContext, bool> predicate)
    {
        var handlersContext = (IHandlersContext) services.Where(x => x.ServiceType  == typeof(IHandlersContext))
            .Select(x => x.ImplementationInstance!).First();
        
        var contexts = handlersContext
            .ToList(kv => kv.Key.IsBasedOn(openGenericServiceType))
            .Select(kv => new DecoratorPredicateContext(kv.Key, kv.Value.ImplementationType))
            .Where(predicate)
            .ToArray();
			
        foreach (var context in x)
        {
            services.Decorate(context.ServiceType,
                openGenericDecoratorType.MakeGenericType(context.ServiceType.GenericTypeArguments));
        }

        return services;
    }
  1. And the last one is trying to unwrap the implementation factories until we can get the the implementation type or instance(will only work if the first registration was with an implementation type or instance, will not work if was used an implementation factory):
public static IServiceCollection Decorate(this IServiceCollection services, Type openGenericServiceType,
        Type openGenericDecoratorType, Func<DecoratorPredicateContext, bool> predicate)
    {
       var contexts = services
            .Where(x => x.ServiceType.IsBasedOn(openGenericServiceType))
            .Select(x => new DecoratorPredicateContext(x.ServiceType, x.GetImplementationType()))
            .Where(predicate)
            .ToArray();
        
        foreach (var context in contexts)
        {
            services.Decorate(context.ServiceType,
                openGenericDecoratorType.MakeGenericType(context.ServiceType.GenericTypeArguments));
        }

        return services;
    }
public static Type GetImplementationType(this ServiceDescriptor? descriptor)
    {
        while (true)
        {
            if (descriptor?.ImplementationType is not null)
            {
                return descriptor.ImplementationType;
            }

            if (descriptor?.ImplementationInstance is not null)
            {
                return descriptor.ImplementationInstance.GetType();
            }

            if (descriptor?.ImplementationFactory is not null)
            {
                var serviceDescriptor = descriptor.ImplementationFactory
                    .GetCapturedVariablesValue<ServiceDescriptor>()
                    .FirstOrDefault();

                descriptor = serviceDescriptor;
                continue;
            }

            throw new InvalidOperationException("No service descriptor available to get implementation type");
        }
    }
public static IEnumerable<T> GetCapturedVariablesValue<T>(this Delegate del) where T : class
    {
        var target = del.Target;

        var values = target?.GetType().GetFields().Where(f => f.FieldType == typeof(T))
            .Select(f => f.GetValue(target)).Cast<T>().ToArray() ?? Array.Empty<T>();

        return values;
    }
  1. Maybe you could also register the services with AsSelfWithInterfaces and scan from IServiceCollection the implementation Types...

Thank you.

ggjnone avatar Sep 07 '22 11:09 ggjnone