Доступ к элементам внедрения зависимостей, таким как IConfiguration, из вложенного класса в консольном приложении Core

Я просмотрел множество руководств и посмотрел несколько видеороликов, посвященных использованию внедрения зависимостей в консольных приложениях .NET Core. Однако все они, похоже, немного не соответствуют тому, что я ищу. В большом консольном приложении у нас может быть несколько вызываемых/ссылающихся классов, и каждому из этих классов может потребоваться доступ к средству ведения журнала или настройкам конфигурации. Все учебные пособия и объяснения, с которыми я столкнулся, показывали только один уровень глубины и передавали экземпляры DI для ведения журнала и настройки на первый уровень.

Что, если класс 1 вызывает класс 2, который затем вызывает класс 3. Скажем, классу 3 необходимо получить значение конфигурации или классу 2 необходимо записать в журнал некоторые информационные или отладочные данные. Как получить доступ к ILogger и IConfiguration с помощью DI в классах 2 и 3?

Я создал здесь очень простой пример на основе видеоурока Тима Кори «Консольное приложение .NET Core с внедрением зависимостей, ведением журнала и настройками», где он настраивает конфигурацию и последовательный журнал для консольного приложения. но опять же, он идет только так глубоко, как первоначальный рабочий класс.

Приложение состоит из трех основных классов: Program.cs (используется для настройки конфигураций), Runner.cs (класс рабочей службы) и SubClass.cs (подкласс, вызываемый из Runner.cs). Мой код работает для регистрации и получения настроек в рабочем классе службы (Runner.cs), но когда я пытаюсь получить доступ к регистратору или настройкам конфигурации из подкласса, я сталкиваюсь с проблемами.

Программа.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;

namespace DiConsoleExample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder();
            BuildConfig(builder);

            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(builder.Build())
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .CreateLogger();

            Log.Logger.Information("Application Starting");

            var host = Host.CreateDefaultBuilder()
                .ConfigureServices((context, services) =>
                {
                    services.AddTransient<IRunner, Runner>();
                })
                .UseSerilog()
                .Build();

            var svc = ActivatorUtilities.CreateInstance<Runner>(host.Services);
            svc.Run();
        }

        static void BuildConfig(IConfigurationBuilder builder)
        {
            builder.SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables();
        }

    }
}

Бегун.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace DiConsoleExample
{
    public interface IRunner
    {
        void Run();
    }

    public class Runner : IRunner
    {
        private readonly ILogger<Runner> _log;
        private readonly IConfiguration _config;

        public Runner(ILogger<Runner> log, IConfiguration config)
        {
            _log = log;
            _config = config;
        }


        public void Run()
        {
            _log.LogInformation("Value for 'MyRunnerSetting': {settingValue}", _config.GetValue<string>("MyRunnerSetting"));

            var sub = new SubClass();
            sub.GetMySetting();
        }
    }
}

Подкласс.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace DiConsoleExample
{
    public class SubClass
    {
        private readonly ILogger<Runner> _log;
        private readonly IConfiguration _config;

        public SubClass(ILogger<Runner> log, IConfiguration config)
        {
            _log = log;
            _config = config;
        }

        public void GetMySetting()
        {
            _log.LogInformation("Value for 'MySubClassSetting': {settingValue}", _config.GetValue<string>("MySubClassSetting"));
        }
    }
}

appsettings.json

{
  "MyRunnerSetting": "Runner123",
  "MySubClassSetting":  "SubClass234"
}

Ошибка, которую я получаю, заключается в том, что когда я создаю новый SubClass(), он не может использовать службы журнала и конфигурации, внедренные DI: There is no argument given that corresponds to the required parameter 'log' of 'SubClass.SubClass(ILogger<Runner>, IConfiguration)

Опять же, это работает нормально, если вызов SubClass не выполняется в Runner.cs, но мне нужно знать, как получить доступ к регистратору и конфигурациям через DI. Я знаю, что могу поместить операторы сборки/настройки для каждого в каждый класс, но, похоже, это противоречит цели внедрения внедрения и дублирует код.

Да, и я использую .NET 8 в VS 2022.


РЕДАКТИРОВАТЬ. Мне не следовало использовать «Подкласс» в своем объяснении, поскольку он ни от чего не наследуется. Мне следовало использовать «NestedClass» (мне плохо)

Мой вопрос, поскольку мой Runner.cs может создать несколько вложенных классов в большом приложении, как мне поместить ILogger и IConfiguration в эти вложенные классы?

В платформе .net я мог бы поместить log4net в любой вложенный класс, чтобы начать ведение журнала, или вызвать ConfigurationManager в любом классе, чтобы получить настройку. В Core ASP.NET я могу использовать конструктор с ILogger и IConfiguration в каждом контроллере, но не могу понять, как получить доступ к ILogger и IConfiguration или ссылаться на них в любых вложенных классах, кроме службы Runner.cs.

Это ошибка компиляции, не имеющая ничего общего с DI. Класс Subclass не является подклассом какого-либо другого класса, и его конструктор требует 2 параметра. var sub = new SubClass(); не предоставляет ни того, ни другого, что приводит к ошибке компиляции. Вам придется использовать new SubClass(_log,_config). SubClass на самом деле является вложенным классом. Это не означает, что он происходит от внешнего класса или наследует его члены.

Panagiotis Kanavos 20.03.2024 18:01
this works fine if the call to SubClass is not made in Runner.cs ни в коем случае, будет сгенерирована та же ошибка компиляции. SubClass(ILogger<Runner> log, IConfiguration config) требует два параметра. Если вы их не предоставите, вы получите ошибку компиляции.
Panagiotis Kanavos 20.03.2024 18:02

Вместо того, чтобы вручную обновлять SubClass в методе Runner.Run, попробуйте предоставить Subclass в качестве аргумента конструктора конструктору Runner и сопоставление с ним внутри метода ConfigureServices: например. services.AddTransient<SubClass>().

Steven 20.03.2024 18:05

Вы правы @PanagiotisKanavos, это не подкласс, он вложенный, извините за неправильную терминологию, но к вашему второму утверждению, если я закомментирую «var sub = new SubClass(); sub.GetMySetting();» тогда приложение работает нормально, и для бегуна создается правильный вывод. При создании экземпляров подкласса я получаю ошибку, да, потому что у него нет ILogger и IConfiguration, но это мой главный вопрос: как мне его настроить, чтобы я использовал конструктор с ILogger и IConfiguration с использованием DI, или у меня есть передать объекты _log и _config каждому вложенному классу внутри Runner.cs?

BBrowsk6605 20.03.2024 18:33

@Стивен, я понимаю, о чем ты говоришь, но не уверен, чего это даст. В платформе .net с помощью одной строки я мог получить доступ к log4net в любом вложенном классе и конфигурации, просто используя ConfigurationManager в ЛЮБОМ вложенном классе. Если у меня есть 3 вложенных класса, вызываемых из Runner, выполняющих разные функции, как мне внедрить Logger и Configuration в эти несколько вложенных классов или классы внутри этих классов?

BBrowsk6605 20.03.2024 18:37
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
138
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Выбросьте все, что вы знаете о .NET Framework, касающееся ведения журналов и настройки, поскольку они не имеют значения в мире .NET (Core). Вы никогда не создаете классы вручную new, структура DI предоставляет их вам . И вы не привязываетесь напрямую к IConfiguration, вы используете шаблон опций. Итак, ваши занятия будут выглядеть примерно так:

public record SubClassOptions(
    string MySubClassSetting);

public class Runner
{
    private ILogger<Runner> _logger;
    private ISubClass _subClass;

    public Runner(ILogger<Runner> logger, ISubClass subClass)
    {
        _logger = logger;
        _subClass = subClass;
    }

    public void Run()
    {
        _subClass.GetMySetting();
    }
}

public interface ISubClass
{
    void GetMySetting();
}

public class SubClass : ISubClass
{
    private readonly ILogger<Runner> _logger;
    private readonly SubClassOptions _options;

    public SubClass(ILogger<Runner> log, SubClassOptions config)
    {
        _log = log;
        _config = config;
    }

    public void GetMySetting()
    {
        _log.LogInformation(
            "Value for 'MySubClassSetting': {SettingValue}",
            _config.MySubClassSetting);
    }
}

и регистрации ваших услуг:

services.AddSingleton<SubClassOptions>(
    configuration.GetRequiredSection("SubClassSection").Get<SubClassOptions>());
services.AddScoped<ISubClass, SubClass>();
services.AddScoped<Runner>();

Итак, я подправил свой образец, и это работает, однако кажется, что это ОГРОМНЫЕ накладные расходы, особенно когда мы говорим о приложении, которое может иметь много вложенных классов внутри многих вложенных классов. Все только для того, чтобы каждый класс мог писать в лог или получать настройки конфига. Раньше я писал «частный статический ILog Logger = LogManager.GetLogger(LogType.CMS.ToString());» в любом классе, и я мог бы войти в этот класс, но теперь для входа мне нужно добавить все классы в коллекцию сервисов? Моему приложению НЕ НУЖЕН Di, но я не вижу способа получить журнал и конфигурацию без него в ядре. Все примеры, которые я нашел, показывают использование DI.

BBrowsk6605 21.03.2024 15:04

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

Ian Kemp 21.03.2024 16:56

Также я бы посоветовал вам скорректировать свой подход к переносу приложения .NET Framework на .NET. Возможно, это все еще C#, но фундаментальные концепции радикально изменились, и приведение вашего кода в соответствие с этими новыми концепциями вряд ли будет тривиальным делом. Это всегда так при работе с устаревшим кодом (да, код .NET Framework на данный момент во многом является устаревшим).

Ian Kemp 21.03.2024 16:58

Другие вопросы по теме

Конвейер сборки AzureDevops отменяется из-за тайм-аута во время команды сборки dot
Проблемы с уровнем журнала функции Azure, изолированные от dotnet
Как изменения базы данных управляются и развертываются в Azure DevOps при разработке приложений Blazor?
Почему BackgroundService не игнорирует исключения в зависимости от того, как они обрабатываются
Как перенаправить обратно на страницу «Создать» после авторизации
Каков правильный синтаксис для привязки списка объектов, содержащих дополнительные (вложенные) списки, при отправке форм?
.NET Core: кажущаяся и реальная сборка типа — это не одно и то же
После обновления Visual Studio до версии 17.9.3 среда IDE закрывается, и команда dotnet.exe сообщает о фатальной ошибке. Внутренняя ошибка CLR. (0x80131506)
SmtpClient не работает в .NET Framework, но успешно работает в .NET Core
Доступ к классу настроек, специфичному для среды, внутри фабричного метода