Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make decorated classes directly referenceable #184

Open
adj123 opened this issue Nov 7, 2022 · 4 comments
Open

Make decorated classes directly referenceable #184

adj123 opened this issue Nov 7, 2022 · 4 comments

Comments

@adj123
Copy link

adj123 commented Nov 7, 2022

Hi, I have possibly a slightly unusual case where I have some class which needs to be decorated twice, with the inner one registered as a hosted service:

public interface IMyExternalService { }
public class MyExternalServiceHttpApi : IMyExternalService { }
public class CachingLayerOverMyExternalService : IMyExternalService, IHostedService { }
public class SomethingElseDecoratingMyExternalService : IMyExternalService { }

services.AddSingleton<IMyExternalService, MyExternalServiceHttpApi>();
services.Decorate<IMyExternalService, CachingLayerOverMyExternalService>();
services.AddHostedService(sp => (CachingLayerOverMyExternalService)sp.GetRequiredService<IMyExternalService>()); // <-- Doesn't work
services.Decorate<IMyExternalService, SomethingElseDecoratingMyExternalService>();

The AddHostedService line doesn't work because by the time the function executes, IMyExternalService is already decorated with SomethingElseDecoratingMyExternalService. My only workaround at the moment is to publicly expose the underlying IMyExternalService in this decorator, but this is a hack - it's not part of the decorator's functionality, it's only there to facilitate the DI mechanism which happens to be used to compose all the classes.

I need some handle to be able to reference that inner decorator somehow for my AddHostedService call - eg. perhaps if Decorate also registered a transient service pointer to each decorated type, like if I could do:

services.Decorate<IMyExternalService, CachingLayerOverMyExternalService>();
services.AddHostedService(sp => sp.GetRequiredService<Decorator<CachingLayerOverMyExternalService>>().Value); // <-- Added automatically by the preceding .Decorate

I imagine the general problem here isn't limited to hosted services and this solution would help for cases when the decorator implements arbitrary multiple interfaces which it also needs to be registered against.

@khellang
Copy link
Owner

Hey @adj123! 👋🏻

I think this might be solvable by using the new "keyed services" in .NET 8. I might introduce an overload that takes a key when decorating, which will let you refer to that specific decorator when resolving using the key.

Do you think this could work?

@adj123
Copy link
Author

adj123 commented Sep 21, 2023

Hey, I haven't had a chance to play around with these yet, but from their description it does sound like this approach would work well as a solution to this problem!

@hankovich
Copy link

@khellang do you have some estimation when this keyed services feature will be available? I'm eagerly waiting for it

@adj123
Copy link
Author

adj123 commented May 21, 2024

Alternatively, after looking at the implementation further, I've realised we actually already do register a handle to the decorated service, we just don't expose it - the DecoratedType service descriptor. So a clean way to do this would be to add a wrapper like

public sealed record DecoratedService<TService>(Type Type);

public static TService GetRequiredService<TService>(this IServiceProvider provider, DecoratedService<TService> serviceType) =>
    (TService)provider.GetRequiredService(serviceType.Type);

and new overloads like

public static IServiceCollection Decorate<TService, TDecorator>(this IServiceCollection services, out DecoratedService<TService> decorated)
{
    ... decorate ...
    DecoratedType decoratedType = ...;
    decorated = new(decoratedType);
}

and then we can call it with something like (to use my earlier example):

services.Decorate<IMyExternalService, CachingLayerOverMyExternalService>(out var cachingLayer);
services.AddHostedService(sp => sp.GetRequiredService(cachingLayer));
services.Decorate<IMyExternalService, SomethingElseDecoratingMyExternalService>();

How does that sound?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants