- Не правда ли, ужасное название?
- Что делает наши цепочки команд беспорядочными?
- Давайте заставим наши библиотеки легко работать в Azure Functions, а также в других проектах .NET
- Но как насчет Azure Functions?
- Методы расширения, которые работают вместе
- Как это выглядит для потребителя
- Что можно вынести:
- Alfetta159 / ConfigurationDotNetAzFunc
- Создание библиотек, легко конфигурируемых между Azure Functions и остальной частью .NET.
- Конфигурируемость для Azure Functions и .NET-проектов
- Что делает наши цепочки команд беспорядочными?
Не правда ли, ужасное название?
Инъекция зависимостей (DI) в .NET действительно мощна, если вы привыкнете думать так в начале проекта. Часто мы используем один из методов расширения AddScoped
или AddSingleton
для создания объектов на основе наших классов или объектов сторонних библиотек. Однако если сторонние библиотеки не позволяют нам передавать данные конфигурации через объекты, раскрывающие IConfiguration
или IOption
, наши цепочки команд HostBuilder
могут выйти из-под контроля. И если этого недостаточно, Azure Functions обычно передают данные конфигурации через переменные окружения.
Что делает наши цепочки команд беспорядочными?
Часто, когда нам нужно внедрить зависимость, мы должны передать функцию для создания объекта с параметрами, которые не могут быть внедрены ранее в IServicesCollection
:
// Here we have a constructor with two parameters neither of which is of type IConfiguration
AddScoped((sp) =>
{
var config = sp.GetService<IConfiguration>();
return new Client(param1:config["param1"], param2:config["param2"]);
});
Если нам повезет, то найдется конструктор или перегрузка, принимающая тип IConfiguration
, и, поскольку наша конфигурация была введена как тип IConfiguration
, нам не нужно использовать параметр для вызова другого конструктора.
// Here we have a constructor with one parameters which is of type IConfiguration
AddScoped<IClient, Client>();
Мне часто нравится передавать IConfigurationSection
, оставляя без внимания конкретную конфигурацию, относящуюся к инжектируемому объекту, из большого файла конфигурации в командную цепочку построения сервисов. Это позволит сделать инжектируемый класс более гибким при использовании в проектах Web API, консольных приложениях и Azure Functions.
Давайте заставим наши библиотеки легко работать в Azure Functions, а также в других проектах .NET
Допустим, у нас есть библиотека, и в этой библиотеке есть любое количество классов. Мы видим это в клиентских библиотеках API (они же SDK). Мы можем позволить пользователю вводить те объекты, которые ему нужны, или предложить возможность вводить все сразу в красивом чистом методе расширения. Это часто делается, когда многие объекты не являются необязательными. Это может выглядеть следующим образом:
AddMyClassLibrary();
И это вызовет все базовые методы расширения инъекции:
AddScoped<IClient, Client>()
.AddScoped<IClient1, Client1>()
.AddScoped<IClient2, Client2>()
// ...
.AddScoped<IClientN, ClientN>();
Поскольку мы инжектировали нашу конфигурацию, и по крайней мере один параметр во всех конструкторах всех наших классов в нашей библиотеке имеет тип IConfiguration
, мы позволяем DI выполнять работу по передаче конфигурации.
Но как насчет Azure Functions?
Azure Functions обычно используют переменные среды для хранения своих конфигураций, будь то из host.json, local.host.json или конфигурация в проекте функций на хосте Azure. Другими словами, Azure Functions не использует IConfiguration. Именно здесь нам следует задуматься о том, как мы позволяем нашим потребителям создавать наши библиотечные объекты с помощью инъекции зависимостей.
Методы расширения, которые работают вместе
Сначала давайте предложим базовый метод расширения, который инжектирует все наши классы. Что если потребителю не нужны все классы? Не волнуйтесь, они не будут инжектироваться до тех пор, пока не будут использованы где-нибудь в агрегирующем проекте. Может показаться странным, что мы выкапываем конфигурацию из вызывающего хост-билдера, но мы позволяем потребителю не думать об этих деталях:
/// <summary>Add all classes with the IConfiguration object</summary>
/// <remarks>Ironically, we dig out the configuration to find the default section in the settings file.
/// It might seem a bit clunky, but it's very reuseable and makes for an easy reference in your consumers' start up files.
/// </remarks>
public static IServiceCollection AddClassLibrary(this IServiceCollection services)
{
services
.AddScoped((sp) =>
{
var configuration = sp.GetService<IConfiguration>();
return new Class1(configuration!.GetSection(DefaultSectionName));
})
.AddScoped((sp) =>
{
var configuration = sp.GetService<IConfiguration>();
return new Class2(configuration!.GetSection(DefaultSectionName));
})
.AddScoped((sp) =>
{
var configuration = sp.GetService<IConfiguration>();
return new Class3(configuration!.GetSection(DefaultSectionName));
});
return services;
}
Во-вторых, давайте предложим аналогичную перегрузку, которая позволяет пользователю иметь именованный раздел конфигурации в своем файле настроек:
/// <summary>Add all classes with the IConfiguration object but with a named configuration section.</summary>
/// <remarks>Ironically, we dig out the configuration to find the default section in the settings file.
/// It might seem a bit clunky, but it's very reuseable and makes for an easy reference in your consumers' start up files.
/// </remarks>
public static IServiceCollection AddClassLibrary(this IServiceCollection services, string configurationSectionName)
{
services
.AddScoped((sp) =>
{
var configuration = sp.GetService<IConfiguration>();
return new Class1(configuration!.GetSection(configurationSectionName));
})
.AddScoped((sp) =>
{
var configuration = sp.GetService<IConfiguration>();
return new Class2(configuration!.GetSection(configurationSectionName));
})
.AddScoped((sp) =>
{
var configuration = sp.GetService<IConfiguration>();
return new Class3(configuration!.GetSection(configurationSectionName));
});
return services;
}
В-третьих, мы предлагаем простой способ поместить наши классы в проект Azure functions:
/// <summary>Add all classes with the IConfiguration object but with configuration that you find in Azure functions.</summary>
/// <remarks>We create a configuration as an in-memory collection, which is completely separate from any configuration already in the service collection),
/// This is really handy for Azure Functions that rely more on the environment variables collection and not an IConfiguration object.
/// </remarks>
public static IServiceCollection AddClassLibraryFromEnvironment(this IServiceCollection services)
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
["First"] = Environment.GetEnvironmentVariable("First")!,
["Second"] = Environment.GetEnvironmentVariable("Second")!,
["Third"] = Environment.GetEnvironmentVariable("Third")!
})
.Build();
services
.AddScoped(s => new Class1(configuration))
.AddScoped(s => new Class2(configuration))
.AddScoped(s => new Class3(configuration));
return services;
}
Как это выглядит для потребителя
В примере ниже показаны различные методы расширений.
Поймите, что вы не будете вызывать все эти методы вместе. Вы просто выберете те, которые подходят для вашего приложения.
Мы обмениваем трудоемкую работу по созданию конфигурации сервиса на многократно используемые методы расширения, чтобы иметь красивый чистый конструктор там, где это важно в коде приложений наших пользователей.
services
// Uncomment only the following line to add the class library with the 'Default' configuration:
.AddClassLibrary();
// Uncomment only the following line to add the class library with the named configuration:
.AddClassLibrary("SecondSection");
// Uncomment only the following two lines to add only two classes from the class library:
.AddScoped<Class1>()
.AddScoped<Class3>()
// Uncomment only the following command to add only the second class configured with the named configuration:
.AddScoped(sp =>
{
var configuration = sp.GetService<IConfiguration>();
return new Class2(configuration!.GetSection("SecondSection"));
})
// Uncomment only the following line to configure using the command-line arguments in the launch.json:
.AddClassLibraryFromCommandline()
;
В примере кода я даже сделал версию, которая получает параметры из аргументов командной строки, но это, вероятно, не так полезно, поскольку приложение, которое импортирует нашу библиотеку, будет разбирать аргументы командной строки.
Что можно вынести:
- Эти шаблоны отлично подходят для библиотек, в которых много классов и/или много параметров конфигурации.
- Если вы создаете многократно используемые библиотеки, особенно те, которые станут пакетами NuGet, то это отличный способ облегчить работу пользователей, даже если эти пользователи находятся в другом отделе вашей компании.
- Не стоит забывать, что все здесь добавлено как scoped. Вы, вероятно, обнаружите, что в вашей библиотеке некоторые классы лучше использовать как scoped, а другие как singleton или даже transitional, и вы лучше знаете, как следует использовать ваши классы.
- Но помните, что ваши потребители всегда могут внедрить ваши классы ala carte любым удобным для них способом, а поскольку вы позаботились об использовании по крайней мере одного параметра во всех ваших конструкторах типа
IConfiguration
, это будет легко сделать, используя формы параметров типа методов расширенийAddScoped/Singleton/Transitional
. - И это, надеюсь, только некоторые основополагающие идеи, которые могут быть адаптированы или заменены замечательными идеями, которые вы придумаете для своей библиотеки.
Дайте мне знать, что вы придумали в комментариях, и, пожалуйста, клонируйте репозиторий. Я горжусь тем, что никогда не показываю фрагменты кода без рабочего примера.
Alfetta159 / ConfigurationDotNetAzFunc
Создание библиотек, легко конфигурируемых между Azure Functions и остальной частью .NET.
Конфигурируемость для Azure Functions и .NET-проектов
от dev.to
Инъекция зависимостей (DI) в .NET становится по-настоящему мощной, когда вы привыкаете думать так с самого начала проекта. Часто мы используем один из методов расширения AddScoped
или AddSingleton
для создания объектов на основе наших классов или объектов сторонних библиотек. Однако если сторонние библиотеки не позволяют нам передавать данные конфигурации через объекты, раскрывающие IConfiguration
или IOption
, наши цепочки команд HostBuilder
могут выйти из-под контроля. И если этого недостаточно, Azure Functions обычно передают данные конфигурации через переменные окружения.
Что делает наши цепочки команд беспорядочными?
Часто, когда нам нужно ввести зависимость, мы должны передать функцию для создания объекта с параметрами, которые не могут быть введены ранее в IServicesCollection
:
// Here we have a constructor with two parameters neither of which is
…