[Question] Decoration of closed interface over generic parent
Closed this issue · 2 comments
Hello. Apologies for a rethread of what appears to be a beaten question, but i'm a bit lost and existing issues don't quite fit
Suppose we have a following hierarchy:
// Base handler interface
public interface IHandler<TReq, TRes> { /* snip */ }
// This one is supposed to be used as shorthand in controllers and other entry points
public interface ISomeHandler : IHandler<string, int> { /* snip */ }
// Implementation
public class SomeHandler : ISomeHandler { /* snip */ }
// Decorator
public class Decorator<TReq, TRes> : IHandler<TReq, TRes> { /* snip */ }
// Example controller signature
public Task<ActionResult<SomeResult>> Test(ISomeHandler handler, SomeRequest request) { /* snip */ }
Base configuration:
// For controllers both folloving version work, as expected
// - Test(ISomeHandler handler, string input) -> Works
// - Test(IHandler<string, int> handler, string input) -> Works
builder.Services.Scan(scan =>
{
scan.FromAssembliesOf(typeof(IHandler<,>))
.AddClasses(c => c.Where(t => t != typeof(Decorator<,>)))
.AsSelfWithInterfaces().WithScopedLifetime();
// AsImplementedInterfaces would work as well
});
Now for the main problem: how do i decorate intermediate closed interface (ISomeHandler)?
// Attempt 1
// - Test(ISomeHandler handler, string input) -> Does not work (yields SomeHandler directly)
// - Test(IHandler<string, int> handler, string input) -> Works (yields decorator instance)
builder.Services.Decorate(typeof(IHandler<,>), typeof(Decorator<,>));
// Attempt 2
// Blows up at runtime: A suitable constructor for type 'ScrutorTest.Decorator`2[TReq,TRes]' could not be located
//builder.Services.Decorate(typeof(ISomeHandler), typeof(Decorator<,>));
// Or
//builder.Services.Decorate(typeof(SomeHandler), typeof(Decorator<,>));
// Attempt 3
// Blows up at runtime: Unable to cast object of type 'ScrutorTest.Decorator`2[System.String,System.Int32]' to type 'ScrutorTest.ISomeHandler'
//builder.Services.Decorate(typeof(ISomeHandler), typeof(Decorator<string, int>));
// Or
//builder.Services.Decorate(typeof(SomeHandler), typeof(Decorator<string, int>));
Is what i'm trying to do even possible with C# generics?
If it's possible with Scrutor, what am i missing?
If not possible with Scrutor + base .NET DI, would it be possible with other container, like Autofac? (I'm going to try that anyway in meantime)
Minimal repro:
ScrutorTest.zip
Hello @schmellow! 👋🏻
It's definitely a case that makes my head hurt a bit 😅
For the decorator pattern to work, the decorator needs to implement the same interface as it's accepting in its constructor, e.g.
public class Decorator<TReq, TRes> : IHandler<TReq, TRes>
{
public Decorator(IHandler<TReq, TRes> inner) { /* snip */ }
}
or
public class Decorator : ISomeHandler
{
public Decorator(ISomeHandler inner) { /* snip */ }
}
I don't see how you could decorate ISomeHandler
using Decorator<string, int>
as Decorator<string, int>
doesn't implement the ISomeHandler
interface. Sure, they both have a common interface IHandler<string, int>
, but in order to get that to work, you'd need something like structural typing in C#.
This definitelly smells like a case where I'd take a step or two back and question what I'm really trying to accomplish. Maybe there's a simple(r) solution? 🤔
Turns out you actually test against this case in DecoratingOpenGenericTypeBasedOnGrandparentInterfaceDoesNotDecorateParentInterface :)
Anyway...
This definitelly smells like a case where I'd take a step or two back and question what I'm really trying to accomplish. Maybe there's a simple(r) solution?
I think you are right. After trying out different options with containers and whatnot - it's just not possible OOTB.
It's kinda like "colored functions" - there is an impasse betwen generic and non-generic context you can't cross without reflection trickery
I can think of a solution through factory + reflection, but it's just impractical at this point. Which points to the fact, that this does not belong on a DI container level.
What actually fits my specific case is external dispatcher + pipeline (like asp.net does with middleware chains or MediatR).
So closing