Using Scoped services within a hosted service in ASP.NET Core

Viewed 2055

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;
        }
    }
2 Answers

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));
            });

  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>();
    }
    
Related