Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to consume scoped service with dependency in a background task

Here is my scenerio. I want to use background task to send newsletter to subscribed users. This is done by MailService, which has UnitOfWork as dependency.

I tried the solution from learn.microsoft.com so in my case I use a method of IMailService instead of ILogger, but I'm getting an error:

System.InvalidOperationException: 'Cannot consume scoped service >'Fit4You.Core.Data.IUnitOfWork' from singleton >'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'.'

I don't want to make my UnitOfWork or DbContext with Singleton lifetime. Is it possible to consume somehow a dependency of MailService which is scoped UnitOfWork?

I know about IServiceScopeFactory but maybe don't know how to use it properly.

I'm using .NET Core 2.2 and build in interface IHostedService

ScopedMailService:

public class ScopedMailService : IScopedMailService
{
    private readonly IMailService mailService;

    public ScopedMailService(IMailService mailService)
    {
        this.mailService = mailService;
    }

    public void DoWork()
    {
        mailService.SendNewsletterToSubscribedUsers();
    }
}

ConsumeScopedMailService:

public class ConsumeScopedMailService : IHostedService
{
    private Timer timer;
    private readonly IMailService mailService;
    public IServiceProvider Services { get; }

    public ConsumeScopedMailService(IServiceProvider services, IMailService mailService)
    {
        Services = services;
        this.mailService = mailService;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        var startTimeSpan = GetStartTimeSpan();
        var periodTimeSpan = TimeSpan.FromSeconds(30);

        timer = new Timer(DoWork, null, startTimeSpan, periodTimeSpan);

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        using (var scope = Services.CreateScope())
        {
            var scopedMailService = scope.ServiceProvider.GetRequiredService<IScopedMailService>();
            scopedMailService.DoWork();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        timer?.Dispose();
    }

    private TimeSpan GetStartTimeSpan()
    {
        var currentTime = DateTime.Now.Ticks;
        var executeTime = DateTime.Today.AddHours(8)
                                        .AddMinutes(0)
                                        .Ticks;

        long ticks = executeTime - currentTime;

        if (ticks < 0)
        {
            ticks = ticks + TimeSpan.TicksPerDay;
        }

        var startTimeSpan = new TimeSpan(ticks);

        return startTimeSpan;
    }
}

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddDbContext<Fit4YouDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("MyConnectionString")));

    services.AddScoped<IUnitOfWork, UnitOfWork>();
    services.AddTransient<IMailService, MailService>();

    services.AddHostedService<ConsumeScopedMailService>();
    services.AddScoped<IScopedMailService, ScopedMailService>();

    ...
}

MailService:

    public class MailService : IMailService
    {
        private readonly IUnitOfWork unitOfWork;
        public MailService(IUnitOfWork unitOfWork)
        {
            this.unitOfWork = unitOfWork;
        }

        public void SendNewsletterToSubscribedUsers()
        {
            // Some Code
        }
    }
like image 245
Sadinus Avatar asked Mar 29 '19 08:03

Sadinus


People also ask

How do you consume scoped service from singleton?

The Solution. To be able to use scoped services within a singleton, you must create a scope manually. A new scope can be created by injecting an IServiceScopeFactory into your singleton service (the IServiceScopeFactory is itself a singleton, which is why this works).

What is scoped dependency?

Scoped (Once Per Application Request)The dependency instance is created at the beginning of the request, injected into all dependencies that need it during the request, and disposed of by the container at the end of the request.

What is the difference between AddScoped and AddTransient?

AddTransient() - This method creates a Transient service. A new instance of a Transient service is created each time it is requested. AddScoped() - This method creates a Scoped service. A new instance of a Scoped service is created once per request within the scope.

Should I use scoped or transient?

If you want an instance that lasts for the duration of a user request (that eg might hold details of the user), then use scoped. If you want a new instance every time the container is asked to provide one (a resource access request that needs disposing quickly for example), then use transient.


1 Answers

The singleton ConsumeScopedMailService depends on IMailService through its constructor :

public ConsumeScopedMailService(IServiceProvider services, IMailService mailService)

IMailService may be transient but the class that implements it depends on a scoped service, IUnitOfWork. Indirectly, ConsumeScopedMailService ends up depending on a scoped service.

To fix this IMailService mailService should be removed. It's not used in the posted code anyway.

like image 103
Panagiotis Kanavos Avatar answered Oct 18 '22 22:10

Panagiotis Kanavos