Понимание внедрения зависимостей ядра .NET в консольном приложении

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

(Обратите внимание, что приведенный ниже пример не компилируется)

Вот базовый пример того, как я думаю, это должно работать (пожалуйста, укажите на что-нибудь необычное или неправильное):

        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddUserSecrets<Settings>()
                .Build();

            var services = new ServiceCollection()
                .AddLogging(b => b
                    .AddConsole())
                .AddDbContext<UnderstandingDIContext>(options =>
                    options.UseSqlite(builder.GetConnectionString("DefaultConnection")))
                .BuildServiceProvider();

            var logger = services.GetService<ILoggerFactory>()
                .CreateLogger<Program>();

            logger.LogInformation("Starting Application");

            var worker = new Worker();

            logger.LogInformation("Closing Application");
        }

Но как мне использовать эти сервисы внутри моего класса Worker ?:

        public Worker(ILogger logger, IConfiguration configuration)
        {
            logger.LogInformation("Inside Worker Class");
            var settings = new Settings()
            {
                Secret1 = configuration["Settings:Secret1"],
                Secret2 = configuration["Settings:Secret2"]
            };
            logger.LogInformation($"Secret 1 is '{settings.Secret1}'");
            logger.LogInformation($"Secret 2 is '{settings.Secret2}'");

            using (var context = new UnderstandingDIContext())
            {
                context.Add(new UnderstandingDIModel()
                {
                    Message = "Adding a message to the database."
                });
            }
        }

Понимание DIContext

    public class UnderstandingDIContext : DbContext
    {
        public UnderstandingDIContext(DbContextOptions<UnderstandingDIContext> options)
            : base(options)
        { }

        public DbSet<UnderstandingDIModel> UnderstandingDITable { get; set; }
    }

Проблемы с этим кодом заключаются в следующем:

Worker () ожидает передачи параметров ILogger и IConfiguration, но я думал, что внедрение зависимостей должно покрыть это?

Я не могу запустить «dotnet ef migrations add Initial», потому что я неправильно передаю строку подключения (ошибка: «Невозможно создать объект типа« UnderstandingDIContext ».»)

'using (var context = new UnderstandingDIContext ())' не будет компилироваться, потому что я неправильно понимаю бит DbContext.

Я ОЧЕНЬ много искал, и есть много примеров для веб-приложений, но очень мало для консольных приложений. Я просто неправильно понимаю всю концепцию внедрения зависимостей?


person mwade    schedule 04.05.2019    source источник
comment
Не создавайте new Worker() себя. Вместо этого зарегистрируйте его в своем ServiceCollection и позвольте DI создать его для вас.   -  person Dai    schedule 04.05.2019
comment
Означает ли это, что вы ДОЛЖНЫ реализовать интерфейс (например, IWorker) или это рекомендуемая практика?   -  person mwade    schedule 04.05.2019
comment
Нет, вы можете зарегистрировать зависимость без интерфейса. Просто укажите конкретный тип как в вызове регистрации, так и в качестве параметра конструктора.   -  person Dai    schedule 04.05.2019


Ответы (1)


При использовании внедрения конструктора зависимости будут разрешены только тогда, когда создаваемый вами объект будет фактически создан посредством самого внедрения зависимостей. Таким образом, ключ к тому, чтобы заставить работать инъекцию зависимостей внутри вашего Worker, состоит в том, чтобы фактически разрешить Worker через контейнер инъекции зависимостей.

На самом деле это довольно просто:

var services = new ServiceCollection()
    .AddLogging(b => b.AddConsole())
    .AddDbContext<UnderstandingDIContext>(options =>
        options.UseSqlite(builder.GetConnectionString("DefaultConnection")));

// register `Worker` in the service collection
services.AddTransient<Worker>();

// build the service provider
var serviceProvider = services.BuildServiceProvider();

// resolve a `Worker` from the service provider
var worker = serviceProvider.GetService<Worker>();

var logger = serviceProvider.GetService<ILogger<Program>>();
logger.LogInformation("Starting Application");

worker.Run();

logger.LogInformation("Closing Application");

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

var serviceProvider = services.BuildServiceProvider();

using (var scope = serviceProvider.CreateScope())
{
    var worker = serviceProvider.GetService<Worker>();
    worker.Run();
}

Обратите внимание, что я также сделал явный метод Run для вашего воркера, чтобы у вас не было логики внутри конструктора.

public class Worker
{
    private readonly ILogger<Worker> _logger = logger;
    private readonly IConfiguration _configuration = configuration;
    private readonly UnderstandingDIContext _dbContext = dbContext;

    public Worker(ILogger<Worker> logger, IConfiguration configuration, UnderstandingDIContext dbContext)
    {
        _logger = logger;
        _configuration = configuration;
        _dbContext = dbContext;
    }

    public void Run()
    {
        _logger.LogInformation("Inside Worker Class");
        var settings = new Settings()
        {
            Secret1 = configuration["Settings:Secret1"],
            Secret2 = configuration["Settings:Secret2"]
        };

        _logger.LogInformation($"Secret 1 is '{settings.Secret1}'");
        _logger.LogInformation($"Secret 2 is '{settings.Secret2}'");

        _dbContext.Add(new UnderstandingDIModel()
        {
            Message = "Adding a message to the database."
        });
        _dbContext.SaveChanges();
    }
}
person poke    schedule 04.05.2019
comment
После дополнительных исследований, пробуя разные вещи и опираясь на советы @Dai в комментариях выше, я был почти там и очень близко к коду, который вы разместили здесь, но это объяснено и хорошо продемонстрировано, так что вы понимаете. Большое спасибо за уделенное время. - person mwade; 04.05.2019
comment
При попытке создать миграцию базы данных я получаю сообщение об ошибке «Невозможно создать объект типа« UnderstandingDIContext »». - Все компилируется правильно, и строка подключения успешно читается. Я упустил что-то простое? У меня такое чувство, что это как-то связано с Database.EnsureCreate () в Sqlite, но я не знаю, куда это поместить. - person mwade; 04.05.2019
comment
@mwade Как вы пытаетесь создать миграцию базы данных? Возможно, вам потребуется настроить фабрику контекста времени разработки, чтобы использовать инструменты EF. - person poke; 04.05.2019
comment
Используя команду CLI 'dotnet ef migrations add Create'. Почему это не работает? services.AddDbContext<UnderstandingDIContext>(options => options.UseSqlite(builder.GetConnectionString("DefaultConnection"))); Кажется, что инъекция выполняется неправильно при использовании: public UnderstandingDIContext(DbContextOptions<UnderstandingDIContext> options) : base(options) { } Единственный способ, который я могу найти, - это вручную ввести строку подключения в метод OnConfiguring вместо получения из appsettings.json. - person mwade; 04.05.2019