Scrutor
Scrutor copied to clipboard
Support for optional open-generic decorators
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)
It helps to move reflection operations from run-time to configure time.
Hi there! Are there any workaround to accomplish this?
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:
- 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;
}
- 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;
}
- 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;
}
- Maybe you could also register the services with AsSelfWithInterfaces and scan from IServiceCollection the implementation Types...
Thank you.