0

I'm trying to implement email background service based on IHostedService.

But I get this error:

System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Business.Abstract.ICustomerService Lifetime: Scoped ImplementationType: Business.Concrete.CustomerManager': Unable to resolve service for type 'DataAccess.Concrete.EntityFramework.SqlContext' while attempting to activate 'DataAccess.Concrete.EntityFramework.EfCustomerDal'.) (Error while validating the service descriptor 'ServiceType: DataAccess.Abstract.ICustomerDal Lifetime: Scoped ImplementationType: DataAccess.Concrete.EntityFramework.EfCustomerDal': Unable to resolve service for type 'DataAccess.Concrete.EntityFramework.SqlContext' while attempting to activate 'DataAccess.Concrete.EntityFramework.EfCustomerDal'.) (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: KodiksProject.API.Services.EmailBackgroundService': Unable to resolve service for type 'Business.Concrete.CustomerManager' while attempting to activate 'KodiksProject.API.Services.EmailBackgroundService'.)'

How can I fix this issue ?

ICustomerService:

public interface ICustomerService
{
    IResult Add(Customer customer);
    IResult Update(Customer customer);
    IResult Delete(int id);
    IDataResult<Customer> Get(int id);
    IDataResult<List<Customer>> GetAll();
    Task SendEmailsToCustomersAsync();
}

CustomerManager:

public class CustomerManager : ICustomerService
{
    ICustomerDal _customerDal;
    private readonly IEmailService _emailService;

    public CustomerManager(ICustomerDal customerDal, IEmailService emailService)  
    { 
        _customerDal = customerDal;
        _emailService = emailService;
    }

    public IResult Add(Customer customer)
    {
        _customerDal.Add(customer);
        return new SuccessResult();
    }

    public IResult Delete(int id)
    {
        _customerDal.Delete(id);
        return new SuccessResult();
    }

    public IDataResult<Customer> Get(int id)
    {
        return new SuccessDataResult<Customer>(_customerDal.Get(c => c.CustomerId == id));
    }

    public IDataResult<List<Customer>> GetAll()
    {
        return new SuccessDataResult<List<Customer>>(_customerDal.GetAll());
    }

    public IResult Update(Customer customer)
    {
        _customerDal.Update(customer);
        return new SuccessResult();
    }

    public async Task SendEmailsToCustomersAsync()
    {
        var customerEmails = await _customerDal.GetAllCustomerEmailsAsync();
        var tasks = new List<Task>();

        foreach (var email in customerEmails)
        {
            var task = _emailService.SendEmailAsync(email, "asdasas", "asasadasdasd");
            tasks.Add(task);
        }

        await Task.WhenAll(tasks);
    }
}

SqlContext:

public class SqlContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"my connection string");
    }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<Order> Orders { get; set; }
    public DbSet<Product> Products { get; set; }
}

EfCustomerDal:

public class EfCustomerDal : EfEntityRepositoryBase<Customer, SqlContext>, ICustomerDal
{
    private readonly SqlContext _context;

    public EfCustomerDal(SqlContext context)
    {
        _context = context;
    }

    public async Task<List<string>> GetAllCustomerEmailsAsync()
    {
        var emails = await _context.Customers
                                    .Select(c => c.Email)
                                    .ToListAsync();

        return emails;
    }
}

ICustomerDal:

public interface ICustomerDal : IEntityRepository<Customer>
{
    Task<List<string>> GetAllCustomerEmailsAsync();
}

EmailService:

public class EmailService : IEmailService
{
    private readonly SmtpClient _smtpClient;

    public EmailService()
    {
        _smtpClient = new SmtpClient("server")
        {
            Port = 465,
            Credentials = new NetworkCredential("username", "password"),
            EnableSsl = true,
        };
    }

    public async Task SendEmailAsync(string to, string subject, string body)
    {
        var mailMessage = new MailMessage("username", to, subject, body);
        await _smtpClient.SendMailAsync(mailMessage);
    }
}

I tried everything but can't solve this problem. This worked before I try to send emails with background worker. I can't see any problem with my dependency injection or service lives.

I'm stuck here and don't know what to do.

Can you help me with this situation?

1
  • How do you configure dependencies in program.cs? Commented Jun 28, 2024 at 9:22

1 Answer 1

3

This is covered in the Consuming a scoped service in a background task section of the docs. Hosted services are registered as singletons and they should not (can not if scope validation is enabled) consume the scoped dependencies. And by default EF Core context is added as a scoped one (i.e. by AddDbContext... call). The solution is to inject IServiceProvider and use it to resolve the dependency. For example:

public class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
    private readonly IServiceProvider Services;

    public ConsumeScopedServiceHostedService(IServiceProvider services, ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Consume Scoped Service Hosted Service running.");

        using var scope = Services.CreateScope();
        var scopedProcessingService = scope.ServiceProvider
            .GetRequiredService<IScopedProcessingService>();

        await scopedProcessingService.DoWork(stoppingToken);
    }
}

Read also:

  1. Service lifetimes
  2. Captive dependency - basically why singleton should not resolve scoped services (for example if you use EF without recycling the scope "per iteration" you can end up with memoryleakish behavior if you are using tracking and/or DML operations).
Sign up to request clarification or add additional context in comments.

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.