5

I am creating a ASP.NET Core web application. I am using a Repository through a library project. I reference it in the web application project.

The repository interface is as below:

public interface IPushNotificationRepository
{
    IQueryable<PushNotification> Notifications
    {
        get;
    }
    IQueryable<Client> Clients
    {
        get;
    }

    void Add(PushNotification notification);
    void Add(Client client);
    void AddRange(IList<PushNotification> notifications);
    bool AddIfNotAlreadySent(PushNotification notification);
    void UpdateDelivery(PushNotification notification);
    bool CheckIfClientExists(string client);
    Client FindClient(int? id);
    void Update(Client client);
    void Delete(Client client);
}

Within the repository I inject the db context

    public class PushNotificationRepository : IPushNotificationRepository
    {
        private readonly PushNotificationsContext _context;

        public PushNotificationRepository(PushNotificationsContext context)
        {
            _context = context;
        }
}

The configure services of the start up class is as below:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddSingleton<IPushNotificationRepository, PushNotificationRepository>();
    services.AddDbContextPool<PushNotificationsContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("PushNotificationsConnection")));
}

In the controller class I consume the repository:

    public class ClientsController : Controller
    {
        //private readonly PushNotificationsContext _context;
        private readonly IPushNotificationRepository _pushNotificationRepository;

        public ClientsController(IPushNotificationRepository pushNotificationRepository)
        {
            _pushNotificationRepository = pushNotificationRepository;
        }
}

The repository classes are in a separate library project which is referenced by the web application project. The error I receive is:

System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Services.Messaging.Data.Abstract.IPushNotificationRepository Lifetime: Singleton ImplementationType: Services.Messaging.Data.PushNotificationRepository': Cannot consume scoped service 'Services.Messaging.Data.PushNotificationsContext' from singleton 'Services.Messaging.Data.Abstract.IPushNotificationRepository'.)'

Would really appreciate some advise on this

sm101
  • 562
  • 3
  • 10
  • 28

2 Answers2

10

A singleton cannot reference a Scoped instance. The error message is clear.

Cannot consume scoped service 'Services.Messaging.Data.PushNotificationsContext' from singleton

PushNotificationsContext is considered as a scoped service. You should almost never consume scoped service or transient service from a singleton. You should also avoid consuming transient service from a scoped service. Consuming scoped services it's a good practice to inject what you need, it gets cleaned up after the request automatically.

Either

services.AddTransient < IPushNotificationRepository, PushNotificationRepository>();

or

services.AddScoped< IPushNotificationRepository, PushNotificationRepository>();

will work fine, but check your design. Maybe this is not the behaviour you are looking for.

David Donari
  • 599
  • 7
  • 17
  • 2
    Thank you for the answer and explanation. I did not know that the DbContext is always going to be injected as a scope. This fixed the issue as I complete all required work within the Controller request lifetime. – sm101 Feb 21 '20 at 09:30
2

services.AddDbContext<PushNotificationsContext>() registers the PushNotificationsContext as a service with ServiceLifetime.Scoped which means that your PushNotificationsContext is created per web request. It is disposed when request is completed.

You could inject IServiceScopeFactory which is singleton into your repository, then create a new scope using CreateScope() and request the PushNotificationsContext service from that scope

public class PushNotificationRepository : IPushNotificationRepository
{
    IServiceScopeFactory _serviceScopeFactory;
    public PushNotificationRepository(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    public void Add(PushNotification notification);
    {
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<PushNotificationsContext>();
            //other logic
        }


    }
}

Refer to c# - DataContext disposed in ASP.NET Core scheduler

Ryan
  • 19,118
  • 10
  • 37
  • 53
  • thank you for the detailed explanation and for the link explaining the long running worker implementation. Got a idea on how it should be implemented as well. In my scenario, I am completing all the tasks within the controller lifetime. Thanks again ! – sm101 Feb 21 '20 at 06:02
  • @sm101 You are welcome.Could you accept the answer if you have resolved the problem?If you have any other question, you could post a new thread. – Ryan Feb 21 '20 at 07:45