0

Context

I have 5 services:

Service1 (no depends.)
Service2 (no depends.)
Service3 (Service1, Service2, IServiceProvider) : ISpecialService
Service4 (Service1, Service2, Service3) : ISpecialService
Service5 (Service1, Service3, Service4) : ISpecialService

They are all one-instance-per-app-life Singleton services. Normally you just add them as a singletons and BuildServiceProvider. However, in my case all ISpecialService needs to be registered twice: as factory and as their own direct type, so direct injection via ctor:

public SomeOtherService(Service3 s3, Service4 s4) { }

still works. The problem is that if I just register them normally:

.AddSingleton<ISpecialService, Service3>()
.AddSingleton<Service3, Service3>()

this will give me 2 different instances of Service3 which defeats the point of a singleton. To have one instance I need to make instance manually, before registering Service3 and provide the instance as a parameter to AddSingleton method.

 var s3 = new Service3(s1, s2, ?isp?);
.AddSingleton<ISpecialService, Service3>(s3) // same instance for contract work
.AddSingleton<Service3, Service3>(s3) // same instance
...
.BuildProvider()

And this is a problem because Service3 has IServiceProvider as dependency. Normally, it is being handled by DI but in this case where I need to register SINGLE instance of it twice I cant seems to find a way to provide complete ISP instance to Service3 instance. If I build ISP I no longer can add any services, which is a dead end because Servcie3 later will need to request as a workaround for circular dependency without 99 intermediate empty pointless services in-between:

private readonly Service1 service1;
private readonly Service2 service2;
private Service4 service4;
private readonly IServiceProvider ISP;

public Service3 (Service1 s1, Service2 s2, IServiceProvider isp)
{
    //assign depenencies
}

public Startup()
{
   //workaround for circular dependency
   service4 = ISP.GetServic<Service4>()
}

The reason why I need to register Service 3,4,5 as an interface type is some common contact work I want them to do later:

foreach (var srv in  ISP.GetService<ISpecialService>())
{
    serv.Startup();
}

Any ideas how to solve this?

KreonZZ
  • 175
  • 2
  • 10
  • 3
    Note that it is an antipattern to provide services with the service container/provider directly. Instead you should take a dependency on the actual services, or a service factory. Many service containers also support injecting a `Func` that you can do to construct instances at your will, without tying yourself directly to the service container, and some, like DryIoc, even create these factory funcs itself without you specifically registering them. – Lasse V. Karlsen Aug 17 '18 at 12:50
  • 2
    In other words, `Service3`, should be changed (in my opinion). – Lasse V. Karlsen Aug 17 '18 at 12:51
  • You can change the constructor of `Service3` to take a `Func<>` - preferably `Func` but worse case scenario `Func` – qujck Aug 17 '18 at 13:04
  • @LasseVågsætherKarlsen I can make Service4 inject itself into Service3 via glue code but isnt it "anti-pattern" and "bad too"? Right now I have 2 issues: Service3 and Service4 depend on each other and Services 3-to-5 needs to be registered both as self and factory type (each as same instance) so I can get all of them through service provider at once to run factory functionality/method in a loop. – KreonZZ Aug 17 '18 at 13:33
  • There seems to be some terminological "slippage" in this question. I mean, I don't think you mean the same thing by "singleton" and "factory" that is normally meant. I would need a lot more detail around a simpler example. Leave out services 1,2 and 5, just keep it to the circular dependency and we can go from there. The complexity you've introduced may be an X-Y problem, or maybe you need to "level up" to a DI container like Autofac. Because in any case, as @LasseVågsætherKarlsen mentioned, you should *not* be passing around `IServiceProvider` as a dependency. – Marc L. Aug 17 '18 at 16:45
  • Singleton means what it means - instance lives forever and there is only one instance. All my services needs to be singletons. Additionally, 3 of the services implement interface lets call it `MyInterface` which is just one contract method I want to run on each service prior full app startup (PreStartUp()) - this is the reason I want to register these services both as self and as that interface - so classic ctor DI will do its job and I can get them all later out of ISP as `MyInterface` to do contract thing. – KreonZZ Aug 17 '18 at 21:14
  • @MarcL. I've updated it to explain some stuff – KreonZZ Aug 18 '18 at 11:28
  • @KreonZZ You got "singleton" right, but don't think you need them by "factory" or to "register twice". I'll be honest, having five (5!) interlocking (tightly bound) singletons that also require a circular reference just reeks with code-smell. There can be reasons for this (adapting third-party components). My recommendations would be the same: level-up to Autofac or another more sophisticated DI system, or consider refactoring your design. If you still have questions after studying that, please come back so we can give you a hand. – Marc L. Aug 20 '18 at 15:29

1 Answers1

0

What you can do is this:

public class Answer
{
    public Answer()
    {
        // Have your service collection, register everything you need
        // before having to add your custom service.
        var collection = new ServiceCollection()
            .AddSingleton<ISpecialService, Service3>();
            //---

        // Build a disposable container without your custom service
        var provider = collection.BuildServiceProvider();

        // Use that container to call your instance of your custom service
        var serviceInstanceWithDI = provider.GetService<ISpecialService>();
        // This way the service is pulled from the DI container with all of its 
        // dependencies injected, assuming they are registered beforehand

        // Add it back to the collection with its dependencies already injected
        collection.AddSingleton<Service3, Service3>(serviceInstanceWithDI);

        // Now you can build the IServiceProvider and have your 2 instances registered.
        var serviceProvider = collection.BuildServiceProvider();
    }
}
Cristian Moraru
  • 265
  • 4
  • 15
  • But Service3 would be instantiated by first .BuildServiceProvider with reference to ORIGINAl ISP that only was first 3 services, not all 5. – KreonZZ Aug 17 '18 at 13:31