如何在 Startup 类之外访问 IServiceCollection 和/或 IServiceProvider?

Kok*_*Teh 6 dependency-injection .net-4.0 .net-core

我在 .Net Farmework 4.6.2 类库中使用 Microsoft.Extensions.DependencyInjection。例如,如何从它们被实例化的源代码外部访问 IServiceCollection 和/或 IServiceProvider?我可以创建一个静态类,在一个单独的类库中公开这个属性并引用它,但我想知道是否有其他更好的方法来实现这个目标。.Net Framework 有 ServiceLocator.Current.GetInstance。Microsoft.Extensions.DependencyInjection 中是否有类似的东西?任何建议和见解表示赞赏。

小智 15

与 不同的是IServiceProviderIServiceCollection不能注入到类构造函数中。

正如许多人已经说过的,您应该避免直接使用它们。但如果你确实需要这样做IServiceCollection,你可以创建一个“Provider”,例如:

    public interface IServiceCollectionProvider 
    {
        IServiceCollection ServiceCollection { get; }
    }

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

        public IServiceCollection ServiceCollection { get; }
    }

Run Code Online (Sandbox Code Playgroud)

注册:

services.AddSingleton<IServiceCollectionProvider>(new ServiceCollectionProvider(services));
Run Code Online (Sandbox Code Playgroud)

使用:

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

        public YourController (IServiceProvider provider, IServiceCollectionProvider serviceCollectionProvider)
        {
            _provider = provider;
            _services = serviceCollectionProvider.ServiceCollection;
        }
    }
Run Code Online (Sandbox Code Playgroud)


nvo*_*igt 6

没有单例“实例”。您可以根据需要创建任意数量的不同服务提供商。您可以正常将其作为参数传递。

您可以注入IServiceProvider成得到由它实例化任何类。只需将其添加为构造函数参数即可。

  • 这是一个*反*模式。IServiceProvider 是*创建*实例的那个。如果您将 *it* 作为依赖项传递,您就打破了模式 (4认同)
  • @PanagiotisKanavos 那么,您如何处理创建更多在创建对象时无法确定的类(或接口)实例的需求?假设您有一个 ClassRoom 将检查数据库并需要创建 Desk 对象?将它传递给 DeskObjectFactory?还有什么是 IServiceProvider? (4认同)
  • @KokHowTeh 你不应该*需要*所有这些类中的`IServiceProvider`。也许您应该针对您的用例提出更具体的问题? (3认同)
  • 那么如何解析接口/获取应用程序其他需要它的部分的实例呢? (2认同)

Mov*_*GP0 5

大多数时候,IServiceProvider 不应该直接使用,因为它将导致服务定位器反模式而不是依赖注入模式。

然而,在某些情况下它确实有意义。特别是在多线程 WPF 或 Blazor 应用程序中,您需要多个范围来进行数据库访问。为此,您只需将 注入IServiceProvider到从中创建的类的构造函数中:

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

    public IServiceProvider ServiceProvider { get; }
}
Run Code Online (Sandbox Code Playgroud)

您可以根据需要创建新的范围服务:

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;
}
Run Code Online (Sandbox Code Playgroud)

示例:WPF 用户控件

假设我们正在使用:

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; }

    // ...
}
Run Code Online (Sandbox Code Playgroud)