4
\$\begingroup\$

I want to use IServiceScopeFactory in a way, which is not the Service Locator anti-pattern.

Therefore I thought about making a generic one, which supports explicitly only the given service.

I have made these interfaces:

public interface IServiceScopeFactory<T> where T : class
{
    IServiceScope<T> CreateScope();
}

public interface IServiceScope<T> : IDisposable where T : class
{
    T Service { get; }
}

And these implementations:

public class ServiceScopeFactory<T> : IServiceScopeFactory<T> where T:class
{
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public ServiceScopeFactory(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    public IServiceScope<T> CreateScope()
    {
        return new ServiceScope<T>(_serviceScopeFactory.CreateScope());
    }
}

public class ServiceScope<T> : IServiceScope<T> where T : class
{
    private readonly IServiceScope _scope;
    private T _service;

    public ServiceScope(IServiceScope scope)
    {
        _scope = scope;
    }

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

    public T Service => _service ?? (_service = _scope.ServiceProvider.GetRequiredService<T>());
}

Usage is for example:

public class Foo
{
    private readonly IServiceScopeFactory<ConmaniaDbContext> _dbContextFactory;

    public Foo(IServiceScopeFactory<ConmaniaDbContext> dbContextFactory)
    {
        _dbContextFactory = dbContextFactory;
    }

    public void Bar()
    {
        using (var scope = _dbContextFactory.CreateScope())
        {
            var dbContext = scope.Service
            //use service
        }
    }
}

For registering it, I have made these extension methods:

public static void AddServiceScopeFactory<T>(this IServiceCollection serviceCollection) where T : class
{
    serviceCollection.AddTransient<IServiceScopeFactory<T>, ServiceScopeFactory<T>>();
}

The registration is like that:

services.AddScoped<ConmaniaDbContext, ConmaniaDbContext>(); //Scoped Service
services.AddServiceScopeFactory<ConmaniaDbContext>();
services.AddSingleton<Foo, Foo>(); //Singleton Service

My base problem was that I need the Scoped Service DbContext in a Singleton service.

Is this a good way for dependency injection with a custom scope or is this just a wrapper around the Service Locator pattern? Is there anything you would improve? Or any obviously problems I might get with this implementation?

To clarify, this is where I am coming from:

public class Foo
{
    private readonly IServiceScopeFactory _scopeFactory;

    public Foo(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public void Bar()
    {
        using (var scope = _scopeFactory.CreateScope())
        {
            var dbContext = scope.ServiceProvider.GetRequiredService<ConmaniaDbContext>()
            //use service
        }
    }
}

But this feels like the Service Locator Pattern, or am I wrong? The problem is here, that the class constructor doesn't know which dependencies the class needs.

The problem I am facing is that IServiceScopeFactory feels like the Service Locator Pattern. Therefore I created a IServiceScopeFactory<T>.

Since the whole Dependency Injection Pattern is relative new to me, I want to know if this is a suitable way of doing it, or some misconception.

\$\endgroup\$
  • \$\begingroup\$ How much of this code is real? \$\endgroup\$ – t3chb0t Jan 19 '18 at 17:07
  • 1
    \$\begingroup\$ Everything but Foo class. The Bar method would for example query onetime the database to get some informations. @t3chb0t \$\endgroup\$ – Christian Gollhardt Jan 19 '18 at 17:09
  • 2
    \$\begingroup\$ Disposing the scope will automatically dispose any services acquired from it, so you don't need to put the dbContext in a using block. \$\endgroup\$ – Brad M Jan 19 '18 at 19:04
  • \$\begingroup\$ Injecting factories can sometimes be misused like a service locator. It is most times best to keep it simple and use Explicit dependency principle and just inject what is actually needed by the dependent class. In this case the factory is misleading as the class does not really need it. It really wants a ConmaniaDbContext which is what should have been injected. \$\endgroup\$ – Nkosi Jan 20 '18 at 12:55
  • \$\begingroup\$ The problem I am facing, is that ConmaniaDbContext is registered Scoped while the Service, that needs to use it, is registered as Singleton. This is the a part that I can not change. A Singleton can not consume Scoped or Transient services. @Nkosi \$\endgroup\$ – Christian Gollhardt Jan 20 '18 at 14:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.