khteh
khteh

Reputation: 3958

How to access IServiceCollection and/or IServiceProvider outside of Startup class?

I am using Microsoft.Extensions.DependencyInjection in .Net Farmework 4.6.2 class libraries. How to access IServiceCollection and/or IServiceProvider from outside the source code where they are instantiated, Main(), for instance? I could create a static class exposing this property in a separate class library and referenced it but I am wondering if there is any other better way of achieving this objective. .Net Framework has ServiceLocator.Current.GetInstance. Is there any similar thing in Microsoft.Extensions.DependencyInjection? Any advice and insight is appreciated.

Upvotes: 19

Views: 42184

Answers (3)

GuiBGP
GuiBGP

Reputation: 196

Different from the IServiceProvider, the IServiceCollection can't be injected into a class constructor.

As many have already said, you should avoid using them directly. But if you really need to do so for the IServiceCollection, you can create a "Provider", such as:

    public interface IServiceCollectionProvider 
    {
        IServiceCollection ServiceCollection { get; }
    }

    public sealed class ServiceCollectionProvider: IServiceCollectionProvider
    {
        public ServiceCollectionProvider(IServiceCollection serviceCollection)
        {
            ServiceCollection = serviceCollection;
        }

        public IServiceCollection ServiceCollection { get; }
    }

Registering:

services.AddSingleton<IServiceCollectionProvider>(new ServiceCollectionProvider(services));

Using:

    public class YourController : Controller
    {
        private readonly IServiceProvider _provider;
        private readonly IServiceCollection _services;

        public YourController (IServiceProvider provider, IServiceCollectionProvider serviceCollectionProvider)
        {
            _provider = provider;
            _services = serviceCollectionProvider.ServiceCollection;
        }
    }

Upvotes: 16

MovGP0
MovGP0

Reputation: 7773

Most of the time, IServiceProvider should not be used directly, because it will result in the Service Locator antipattern instead of the Dependency Injection pattern.

However, there are cases where it does make sense. Especially in multithreaded WPF or Blazor applications, where you need multiple scopes for the database access. To to so, you simply inject the IServiceProvider into the constuctor of a class that gets created from it:

public sealed class ServiceProviderResolver
{
    public ServiceProviderResolver(IServiceProvider serviceProvider)
    {
        ServiceProvider = serviceProvider;
    }

    public IServiceProvider ServiceProvider { get; }
}

Than you can create new scoped services as needed:

public static IDisposable Scoped<T>(this IServiceProvider scopeFactory, out T context)
{
    var scope = scopeFactory.CreateScope();
    var services = scope.ServiceProvider;
    context = services.GetRequiredService<T>();
    return scope;
}

Example: WPF UserControl

Assuming we are using:

public partial class MyWPFControl : UserControl, IViewFor<MyWPFControlViewModel>
{
    private IServiceProvider ServiceProvider { get; }

    public MyWPFControl()
    {
        InitializeComponent();

        // Use dependency resolver to get the service provider
        ServiceProvider = Splat.Locator.Current
            .GetService<ServiceProviderResolver>()
            .ServiceProvider;

        this.WhenActivated(d => {
            ActivateCustomerIdSelected(d);
            // ...
        });
    }

    private void ActivateCustomerIdSelected(d)
    {
        this.WhenAnyValue(vm => vm.CustomerId)
            .Where(id => id != null)
            .Select(_ => this)
            .SelectMany(async (vm, ct) => {
                // Make a database query in a dedicated scope
                // This way we can isolate the context 
                // Such that other threads won't interfer with the context
                using(var scope = ServiceProvider.Scoped<IMyDbContext>(out var context))
                {
                    return await context.Customers
                        .Where(c => c.Id == vm.CustomerId)
                        .AsNoTracking()
                        .SingleOrDefaultAsync(ct)
                        .ConfigureAwait(false);
                }
            })
            .ObserveOn(RxApp.MainThreadScheduler)
            // execute the DB query on the TaskPool
            .SubscribeOn(RxApp.TaskPoolScheduler) 
            // Handle the result 
            // Note: we are still on the task pool here
            // you might need DynamicData.SourceCache 
            // or DynamicData.SourceList when handling
            // multiple results
            .Subscribe(customer => { /* ... */ })
            .DisposeWith(d);
        }
    }

    // ...

    [Reactive] public Guid? CustomerId { get; set; }

    // ...
}

Upvotes: 4

nvoigt
nvoigt

Reputation: 77324

There is no singleton "Instance". You can create as many different service providers as you like. You can pass it as a parameter normally.

You can inject the IServiceProvider into any class that gets instantiated by it. Simply add it as a constructor parameter.

Upvotes: 17

Related Questions