ASP.NET Core — внедрение зависимостей (Dependency Injection)

Внедрение зависимостей — это паттерн, который используется для разрешения зависимостей, в данном паттерне классы или объекты имеют свои зависимые классы введённые (переданные другим классом или объектом), а не созданные напрямую. Используется для того, чтобы максимально отделить объекты и их зависимости. В ASP.NET Core представлена базовая реализация контейнера внедрения зависимостей через конструктор. Сами сервисы добавляются в классе Startup в методе ConfigureServices.

Рассмотрим следующий упрощённый пример непосредственно в ASP.NET Core. У нас имеются классы:

public class UserService : IUserService
{
    public string ShowInfo()
    {
        //Get users' info
        return String.Empty;
    }
}

public class Services
{
    public Services(IUserService userService)
    {
        UserService = userService;
    }

    public IUserService UserService { get; }
}

Чтобы начать пользоваться сервисом Services если не использовать DI, то наш код будет примерно таким:

private readonly Services _services;

public HomeController()
{
    var userService = new UserService();
    _services = new Services(userService);
}

Мы объявляем объект типа Services, в конструкторе класса создаём новый UserService и при создании нового Services передаём его в конструктор. При таком подходе, у нас может быть много избыточного кода, ведь подобного использования Services может быть много в различных классах, а, например, сделать наследование от какого-то базового класса, где будет один раз это реализовано, не всегда возможно. Также при таком подходе увеличивается вероятность того, что появятся Services которые были проинициализированы разными данными.

При использовании DI мы можем объявить зависимости в ConfigureServices

services.AddTransient<IUserService, UserService>();
services.AddTransient<Services>();

И в требуемом классе использовать их как:

private readonly Services _services;

public HomeController(Services services)
{
    _services = services;
}

Результат использования будет аналогичный, но нам не придётся задумываться об инициализации класса Services вручную. При необходимости изменить зависимости, сделать это нужно будет в одном месте, а не во всех местах использования.

Время жизни зависимостей

Регистрация сервисов происходит во время запуска приложения. Жизненный цикл сервиса — это время его существования от первого создания контейнером, до момента освобождения всех его экземпляров. В ASP.Net Core DI мы можем определять следующие варианты времени жизни зависимостей:

  • Transient – создаётся каждый раз при каждом запросе к сервису.
  • Scoped – создаётся один раз для каждой области. В большинстве случаев область действия относится к веб-запросу.
  • Singleton – создаётся только один раз, при первом запросе к сервису.

Если у вас большой проект, и сервисов становится слишком много, имеет смысл использовать методы расширения, позволяющие упрощать их вызов. К примеру для ранее показанного примера, можно создать расширение, например, AddMyServices

public static class ServiceExtensions
{
    public static void AddMyServices(this IServiceCollection services)
    {
        services.AddTransient<Services>();
        services.AddTransient<IUserService, UserService>();
    }
}

Которое можно вызывать как services.AddMyServices();

Использование расширений для добавления зависимостей в ASP.NET Core

Появление ошибок

Если мы используем внедрение зависимостей, но при этом забываем добавить необходимый сервис в коллекцию сервисов, тогда мы получим ошибку, очень похожую на следующую:

Ошибка, возникающая если не добавить необходимые сервисы в DI коллекцию в ASP.NET Core

Как можно заметить, здесь не была определена зависимость для IUserService. При наличии подобных ошибок первым делом проверяйте, все ли необходимые сервисы были добавлены.

Сторонние IoC контейнеры

В контейнере, который содержит в себе ASP.Net Core есть только базовый функционал, но его будет достаточно для работы большинства приложений. Но существует много сторонних, более совершенных, контейнеров, которые использовали и совершенствовали не один год. Наиболее известны такие контейнеры как Autofac, Ninject, Unity. Было бы глупо, если бы не было возможности их использовать. И конечно же, такая возможность есть. В настоящее время наиболее функциональным и часто используемым в .NET Core является Autofac. Я не буду описывать здесь его функционал и какие у его есть преимущества, так как подобный материал займёт не одну статью. Покажу лишь как можно использовать Autofac вместо встроенного контейнера.

Сперва необходимо подключить пакет Autofac.Extensions.DependencyInjection

Подключение Autofac.Extensions.DependencyInjection через Nuget в ASP.NET Core

Теперь метод ConfigureServices должен возвращать IServiceProvider а не void. В самом методе добавить:

var builder = new ContainerBuilder();
builder.Populate(services);

var container = builder.Build();
return new AutofacServiceProvider(container);

Сервисы, добавленные ранее в services будут работать по-прежнему, заново их переопределять в данном случае нет необходимости. Если же вы хотите задать их непосредственно в Autofac, то код всего метода будет выглядеть следующим образом:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    var builder = new ContainerBuilder();
    builder.RegisterType<UserService>().As<IUserService>();
    builder.RegisterType<Services>();
    builder.Populate(services);

    var container = builder.Build();
    return new AutofacServiceProvider(container);
}

Autofac имеет отличную документацию, поддержку комьюнити, огромный функционал и он удобен в использовании. Даже начинающие разработчики легко и быстро осваиваются с этим контейнером. Но даже возможностей встроенного DI контейнера будет достаточно для большинства проектов, так что подключать его без необходимости так же не стоит.

Внедрение зависимостей в представления

Еще хочу обратить внимание на возможность внедрения зависимостей в файлы представлений. Для этого используется директива @inject Это будет выглядеть как:

@inject Services Services;

В дальнейшем можно использовать данный сервис непосредственно в разметке. Как, например, <h1>@Services.UserService.ShowInfo()</h1>

Внедрение зависимостей в представления в ASP.NET Core MVC

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

Итог:

Внедрение зависимостей в ASP.NET Core удобное и легко настраиваемое. То, что поддержка DI добавлена «из коробки» показывает, насколько широко они используются и всеобще признаны. Внедрение зависимостей упрощает создание экземпляров классов и обработку всех имеющихся их зависимостей. Внедрять зависимости можно как для контроллеров, как и для представлений и компонент представления (view components). Помимо использования встроенного DI контейнера, можно использовать сторонние, более мощные, если в этом есть необходимость.

Приятного программирования.

Добавить комментарий