Home > database >  How can I add services after build IServiceProvider?
How can I add services after build IServiceProvider?

Time:01-04

These are some definitions:

public interface IClassA
{
    
}
public class ClassA : IClassA
{
    public ClassA()
    {
        Init();
    }

    private void Init()
    {
        Console.WriteLine("Hello!!!");
    }
}
public class ClassB
{
    private IClassA _classA;
    public ClassB(IClassA classA)
    {
        this._classA = classA;
    }
}

If I build the service provider, the singleton instance will be created twice or more after I add more serivces into the ioc container and get them.

IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.TryAddSingleton<IClassA,ClassA>();
ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetRequiredService<IClassA>();


serviceCollection.TryAddSingleton<ClassB>();
serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetRequiredService<ClassB>();

The output is:

Hello!!!
Hello!!!

It means the instance ClassA has been created twice. I want to use the ServiceProvider after I add some services into the ioc container at the stage one. I want to add some services into the container at the stage two and get some services. However, how can I make sure the service that is singleton will be created once still after I get my services at stage two.

CodePudding user response:

As each IServiceProvider keeps track of its own services separately, using the approach of registering the type will not work properly as each provider will end up instantiating the singleton, leading to 2 distinct instances as you saw.

If you really want to have 2 providers (a bad idea in general), you could workaround this problem by passing the instance to the container:

var classA = new ClassA();
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IClassA>(classA);
ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetRequiredService<IClassA>();


serviceCollection.TryAddSingleton<ClassB>();
serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetRequiredService<ClassB>();

When resolving anything from either provider, it will not attempt to instantiate the type since you are providing the instance explicitly.

This is however a fairly hacky approach and I would not recommend it unless it really was your last option.

CodePudding user response:

Disclaimer: I don't have experience with ServiceCollection; therefore, my application of it should be wrong. Plus, I haven't develop in C# for a while. If the answer I'm providing is not of your liking or doesn't help, please leave a comment so I can remove my answer. Thank you

Based on your question:

how can I make sure the service that is singleton will be created once still after I get my services at stage two.

While I can't help you to apply ServiceCollection correctly, due my ignorance of it, I might be able to help you to confirm that part of your question above.

To do so, I would use a different approach to confirm that.

Step by Step Approach

Lets check [ServiceCollection]:

The ServiceCollection class just defines an indexer to get and set registered services, to insert and remove services, and to check if a service is registered. source

and

GetRequiredService() returns a service object of type serviceType. Throws an InvalidOperationException if there is no service of type serviceType.

source

Now that we got that our of the way, lets use a different approach. Instead of using two classes (ClassA and ClassB), lets just use ClassA. We want to make sure we receive reference to one (and only one) instance.

Modify ClassA

To do so, I will modify ClassA to have a counter.

using System;

public interface IClassA
{
    int Count();
    void Inc();
}

public class ClassA : IClassA
{
    private int Counter = 0;
    
    //Accessor
    public int Count() {
      return Counter;
    }

    //Mutators
    public void Inc() {
      Counter  ;
    }
}

public class HelloWorld
{
    public static void Main(string[] args)
    {
        // Current instance have the counter as 0
        IClassA instance = new ClassA();
        
        // We increase the  counter by 1
        instance.Inc();
        
        // We display the result that should be 1
        Console.WriteLine (instance.Count());
    }
}

Check If We Get The Same Instance

If we request ServiceCollection to return returns the same instance then we could do something like this:

using System;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;

public interface IClassA
{
    int Count();
    void Inc();
}

public class ClassA : IClassA
{
    private int Counter = 0;
    
    // Accessor
    public int Count() {
      return Counter;
    }

    // Mutator
    public void Inc() {
      Counter  ;
    }
}

public class HelloWorld
{
    public static void Main(string[] args)
    {
        var classA = new ClassA();
        IServiceCollection serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<IClassA>(classA);
        ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
        var firstReference = serviceProvider.GetRequiredService<IClassA>();
        
        firstReference.Inc();
        firstReference.Inc();
        
        serviceCollection.TryAddSingleton<ClassA>();
        serviceProvider = serviceCollection.BuildServiceProvider();
        var secondReference = serviceProvider.GetRequiredService<ClassA>();

        System.Console.WriteLine(secondReference.Count());
        Debug.Assert(secondReference.Count() == 2, " Value should be 2.");
    }
}

If ServiceCollection does what you believe it does, then we should receive the same instance of ClassA, and the result of the counter should be 2.

What is Next?

Well, if you can confirm that it is returning the same instance, then you can start testing by introducing ClassB. The "catch" is that we will introduce a mutator that increase the counter by two:

public class ClassB
{
    private IClassA _classA;
    public ClassB(IClassA classA)
    {
        this._classA = classA;
    }
    
    public void IncBy2(){
        Counter  = 2;
    }
}

Then, you can:

// Online C# Editor for free
// Write, Edit and Run your C# code using C# Online Compiler

using System;
using System.Diagnostics;

public interface IClassA
{
    int Count();
    void Inc();
}

public class ClassA : IClassA
{
    private int Counter = 0;
    
    //Accessor
    public int Count() {
      return Counter;
    }

    //Mutators
    public void Inc() {
      Counter  ;
    }
}

public class ClassB
{
    private IClassA _classA;
    public ClassB(IClassA classA)
    {
        this._classA = classA;
    }
    
    public void IncBy2(){
        Counter  = 2;
    }
}

public class HelloWorld
{
    public static void Main(string[] args)
    {
        var classA = new ClassA();
        IServiceCollection serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<IClassA>(classA);
        ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
        var classAFirstReference = serviceProvider.GetRequiredService<IClassA>();
        
        classAFirstReference.Inc();
        classAFirstReference.Inc();
        
        serviceCollection.TryAddSingleton<ClassA>();
        serviceProvider = serviceCollection.BuildServiceProvider();
        var classASecondReference = serviceProvider.GetRequiredService<ClassA>();

        System.Console.WriteLine(classASecondReference.Count());
        Debug.Assert(classASecondReference.Count() == 2, " Value should be 2.");
        
        serviceCollection.TryAddSingleton<ClassB>();
        serviceProvider = serviceCollection.BuildServiceProvider();
        var classBFirstReference = serviceProvider.GetRequiredService<ClassB>();
        
        classBFirstReference.Inc(); // We inherit this mutator from ClassA
        classBFirstReference.IncBy2();
        
        serviceCollection.TryAddSingleton<ClassB>();
        serviceProvider = serviceCollection.BuildServiceProvider();
        var classBSecondReference = serviceProvider.GetRequiredService<ClassB>();
        
        System.Console.WriteLine(classBSecondReference.Count());
        Debug.Assert(classBSecondReference.Count() == 3, " Value should be 3.");
        
    }
}

So, this would we the approach I would take to confirm what you want to confirm.

I hope this was helpful and if not, leave a comment so I can remove this answer.

Also, if you don't mind, please let me know about your findings. I think it would be educational.

  • Related