5

I have a service that I want to share between other transient services. Right now it's not really a service, but in real life application it will. How would I share my service using dependency injection?

I added some demo code below. The SharedService should be the same object for MyTransientService1 and MyTransientService2 in the "Scope" of MyCreatorService.

The second assert fails, while this is what I'd like to accomplish.

    class Program
    {
        static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        private static IHostBuilder CreateHostBuilder(string[] args)
            => Host.CreateDefaultBuilder(args)
                .ConfigureServices((_, services) =>
                {
                    services.AddScoped<SharedService>();
                    services.AddTransient<MyTransientService1>();
                    services.AddTransient<MyTransientService2>();
                    services.AddTransient<MyCreatorService>();
                    services.AddHostedService<MyHostedService>();
                });
    }

    public class SharedService
    {
        public Guid Id { get; set; }
    }

    public class MyTransientService1
    {
        public SharedService Shared;

        public MyTransientService1(SharedService shared)
        {
            Shared = shared;
        }
    }

    public class MyTransientService2
    {
        public SharedService Shared;

        public MyTransientService2(SharedService shared)
        {
            Shared = shared;
        }
    }

    public class MyCreatorService
    {
        public MyTransientService1 Service1;
        public MyTransientService2 Service2;

        public MyCreatorService(MyTransientService1 s1, MyTransientService2 s2)
        {
            Service1 = s1;
            Service2 = s2;
        }
    }

    public class MyHostedService : BackgroundService
    {
        private readonly IServiceProvider _serviceProvider;

        public MyHostedService(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            var creator1 = _serviceProvider.GetRequiredService<MyCreatorService>();
            var creator2 = _serviceProvider.GetRequiredService<MyCreatorService>();
            
            Assert.That(creator1.Service1.Shared.Id, Is.EqualTo(creator1.Service2.Shared.Id));
            Assert.That(creator1.Service1.Shared.Id, Is.Not.EqualTo(creator2.Service1.Shared.Id));
            
            return Task.CompletedTask;
        }
    }
3
  • 3
    You ask for a solution, which already seems to be provided in your question. Can you explain what is not working with this setup? Commented Jun 7, 2021 at 7:43
  • @Silvermind: The second assert fails, and this is what I'd like to accomplish. I'd like to create services that share another service in some kind of scoped way. For example, MyHostedService creates two MyCreatorServices. MyCreatorService has two transient services which should share a common service. But this common service is unique per MyCreatorService. Hope this makes sence. Commented Jun 7, 2021 at 8:15
  • With MS.DI, if you resolve a Scoped dependency from the root scope, that Scoped will effectively be a Singleton. This is what happens in your hosted service. A hosted service is effectively a singleton (even though you might register it as transient, that doesn't matter) and its IServiceProvider is the root provider. That's what is causing your problem. The solution is to start a new scope using serviceProvider.CreateScope() inside the hosted service's methods. Commented Jun 7, 2021 at 8:39

2 Answers 2

1

If you use AddScoped, the instance will be the same within the request (for instance for a HTTP Request & Response). If you need your other services to be created everytime they are resolved, you can indeed use AddTransient, but otherwise you can also use AddScoped.

I would also suggest you bind MyHostedService in this manner (if it has anything to do with your described problem), since it seems to be providing a Singleton binding. If a scope of an outer service (one with injected dependencies) is narrower, it will hold hostage injected dependencies. A singleton service with transient dependencies will therefore make all its dependencies singleton, since the outer service will only be created once and its dependencies only resolved once.

UPDATE

After understanding the problem more clearly this should work for you (no other bindings needed):

services.AddTransient<MyCreatorService>(_ =>
            {
                var shared = new SharedService();
                shared.Id = Guid.NewGuid();
                return new MyCreatorService(
                    new MyTransientService1(shared),
                    new MyTransientService2(shared));
            });

Sign up to request clarification or add additional context in comments.

7 Comments

I need the other services (MyTransientService1 & MyTransientService2) to be created everytime they are resolved, and they need to share the SharedService. Can you give me more details on how to do so ? Thanks a lot in advance.
Like @silvermind asked in the comments, what is not working in your setup?
The second assert fails, and this is what I'd like to accomplish. I'd like to create services that share another service in some kind of scoped way. For example, MyHostedService creates two MyCreatorServices. MyCreatorService has two transient services which should share a common service. But this common service is unique per MyCreatorService. Hope this makes sence.
Your example should have two different instances of MyCreatorService using the same instance of SharedService. Is this not what you want? Your second Assert fails because they are the same Id.
Yes I know, but I want them to be different. See I want MyCreatorService having MyTransientService1 and MyTransientService2 sharing the same MySharedService. So for every "new" MyCreatorService, there should be a "new" MySharedService, which is shared between the newly created MyTransientService1 and MyTransientService2.
|
0
  1. Add a constructor to the SharedService class, otherwise the Id is always 000.

  2. Use the following code to create a different scope, so the SharedService will be initialized twice:

    MyCreatorService creator1;
    MyCreatorService creator2;
    using (var scope1 = _serviceProvider.CreateScope())
    {
      creator1 = scope1.ServiceProvider.GetRequiredService<MyCreatorService>();
    }
    using (var scope2 = _serviceProvider.CreateScope())
    {
      creator2 = scope2.ServiceProvider.GetRequiredService<MyCreatorService>();
    }
    

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.