Я просмотрел множество руководств и посмотрел несколько видеороликов, посвященных использованию внедрения зависимостей в консольных приложениях .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.
this works fine if the call to SubClass is not made in Runner.cs ни в коем случае, будет сгенерирована та же ошибка компиляции. SubClass(ILogger<Runner> log, IConfiguration config) требует два параметра. Если вы их не предоставите, вы получите ошибку компиляции.
Вместо того, чтобы вручную обновлять SubClass в методе Runner.Run, попробуйте предоставить Subclass в качестве аргумента конструктора конструктору Runner и сопоставление с ним внутри метода ConfigureServices: например. services.AddTransient<SubClass>().
Вы правы @PanagiotisKanavos, это не подкласс, он вложенный, извините за неправильную терминологию, но к вашему второму утверждению, если я закомментирую «var sub = new SubClass(); sub.GetMySetting();» тогда приложение работает нормально, и для бегуна создается правильный вывод. При создании экземпляров подкласса я получаю ошибку, да, потому что у него нет ILogger и IConfiguration, но это мой главный вопрос: как мне его настроить, чтобы я использовал конструктор с ILogger и IConfiguration с использованием DI, или у меня есть передать объекты _log и _config каждому вложенному классу внутри Runner.cs?
@Стивен, я понимаю, о чем ты говоришь, но не уверен, чего это даст. В платформе .net с помощью одной строки я мог получить доступ к log4net в любом вложенном классе и конфигурации, просто используя ConfigurationManager в ЛЮБОМ вложенном классе. Если у меня есть 3 вложенных класса, вызываемых из Runner, выполняющих разные функции, как мне внедрить Logger и Configuration в эти несколько вложенных классов или классы внутри этих классов?





Выбросьте все, что вы знаете о .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.
Вашему приложению действительно нужен DI, поскольку ничто в .NET (Core) не работает без DI. Статические экземпляры являются антипаттерном, потому что они препятствуют тестированию, а DI — решение этой проблемы. DI — это стандарт в современной разработке программного обеспечения, и от него никуда не деться, поэтому я настоятельно рекомендую вам ознакомиться с некоторыми учебными пособиями и видеороликами о его преимуществах.
Также я бы посоветовал вам скорректировать свой подход к переносу приложения .NET Framework на .NET. Возможно, это все еще C#, но фундаментальные концепции радикально изменились, и приведение вашего кода в соответствие с этими новыми концепциями вряд ли будет тривиальным делом. Это всегда так при работе с устаревшим кодом (да, код .NET Framework на данный момент во многом является устаревшим).
Это ошибка компиляции, не имеющая ничего общего с DI. Класс
Subclassне является подклассом какого-либо другого класса, и его конструктор требует 2 параметра.var sub = new SubClass();не предоставляет ни того, ни другого, что приводит к ошибке компиляции. Вам придется использоватьnew SubClass(_log,_config).SubClassна самом деле является вложенным классом. Это не означает, что он происходит от внешнего класса или наследует его члены.