Зачем нужен IServiceProvider

Существует такой тип в .NET как IServiceProvider. Его описание в официальной документации очень расплывчато:

This interface is implemented by a class or value type that provides a service to other objects.

The GetService method of this interface obtains the object that provides the service.

Я знаю, что этот тип используется в ASP.NET и при создании кастомного XAML расширения.

Но где ещё может использоваться этот тип? Можно ли ему найти свой применение в каких-либо других областях, как на продакшене, так и просто для себя? Да и какая вообще логика в использовании типа как аргумента метода GetService?


Ответы (1 шт):

Автор решения: Vyacheslav Benedichuk

IServiceProvider это интерфейс позволяющий вам использовать внедрение зависимостей в ваш код.

Как правильно написал @Mark Shevchenko для полного понимания хорошо почитать книжку " Внедрение зависимостей в .NET".

Но в целом это работает так.

Например у вас есть класс, который зависит от данных находящихся в каком-то хранилище, доступ к которому осуществляется через какие-то промежуточные классы. При обычном программировании он выглядит примерно так:

class MsSqlDataAccessor
{
  public DbDataAccessor() {/*тут логика инициализации*/}
  public IList<MyCoolEntity> GetAll()
  {
     //тут много кода специфичного для вашей бд.
  }
}

class Summarizer 
{
  public Summarizer() {/*тут логика инициализации*/}
  public GetSum()
  {
    var dataAccessor = new MsSqlDataAccessor();
    var result = 0;
    foreach(var item in dataAccessor.GetAll()){
      result += item.Sum;
    }
  }
}

Если у вас возникает необходимость протестировать такой код в юнит-тестах, то вам необходимо создать дополнительную базу данных и использовать ее в тестах. Если вам нужно использовать другую базу данных или web-сервис в качестве источника данных, в зависисмости от настроек, вам нужно будет добавлять if/else в каждое место где создается экземпляр MsSqlDataAccessor, что бы создать экземпляр правильного класса. Это очень сильно усложнит код.

Что бы упростить жизнь используется IServiceProvider и IServiceCollection. IServiceCollection используется для определения списка доступных сервисов. IServiceProvider используется для извлечения необходимых вам сервисов из коллекции. Пример с теми же классами.

public interface IDataAccessor
{
  IList<MyCoolEntity> GetAll();
}

class MsSqlDataAccessor : IDataAccessor
{
  public DbDataAccessor() {/*тут логика инициализации*/}
  public IList<MyCoolEntity> GetAll()
  {
     //тут много кода специфичного для вашей бд.
  }
}

class MockDataAccessor : IDataAccessor
{
  public DbDataAccessor() {/*тут логика инициализации*/}
  public IList<MyCoolEntity> GetAll()
  {
     var result = new List<MyCoolEntity>();
     result.Add(new MyCoolEntity()
     {
       Sum = 100;
     });
     result.Add(new MyCoolEntity()
     {
       Sum = 200;
     });
     return result;
  }
}

class Summarizer 
{
  private readonly IServiceProvider _serviceProvider;
  
  public Summarizer(IServiceProvider serviceProvider) 
  {
    _serviceProvider = serviceProvider;
    /*тут логика инициализации*/
  }

  public GetSum()
  {
    var dataAccessor = _serviceProvider.GetService<IDataAccessor>();
    var result = 0;
    foreach(var item in dataAccessor.GetAll()){
      result += item.Sum;
    }
  }
}

А где-то в инициализации приложения, скорее всего в Startup.cs или специально выделенном классе есть такой код: Services.AddScoped<IDataAccessor, MsSqlDataAccessor>(); // Здесь Services - экземпляр класса реализующего IServiceCollection

А в юнит-тесте есть такой код:
var services = new ServiceCollection();
var mockAccessor = new MockDataAccessor();
services.AddScoped<IServiceProvider, MsSqlDataAccessor>()
var summarizer = new Summarizer(services.BuildServiceProvider());
var result = summarizer.GetSum();
Assert.Equals(300, result);
→ Ссылка