Расширения для обработки конфигурационных файлов¶
Обработка конфигурационных файлов app.json
выполняется по алгоритму, описанному в разделе Файлы app.json. Результирующая конфигурация доступна через объект IConfigurationManager
из DI-контейнера.
Ниже рассмотрены примеры по расширению алгоритма обработки конфигурационных файлов.
Tip
Для того, чтобы вывести результирующий объект конфигурации после его загрузки со всеми расширениями, воспользуйтесь консольной командой tadmin PrintJson.
Убедитесь, что в папке extensions
расположены актуальные версии сборок с расширениями Tessa.Extensions.*.dll
.
Загрузчик с методом Invoke для переопределения включаемых файлов¶
Посредством свойства ".loader.type"
возможно указать квалифицированное имя типа для директивы .include или массив, содержащий имя типа с пространством имён и имя сборки.
Возможны следующие варианты указания типа:
"ExampleNamespace.Loaders.ExampleConfigurationLoader, ExampleLoadersLib"
- квалифицированное имя типаExampleConfigurationLoader
, указанное для сборкиExampleLoadersLib
, которая может быть загружена по имени сборки (на которую ссылается приложение).[ "ExampleNamespace.Loaders.ExampleConfigurationLoader", "ExampleLoadersLib" ]
- имя типа с пространством имён и имя сборки, аналогично п.1.[ "ExampleNamespace.Loaders.ExampleConfigurationLoader", "extensions/Tessa.Extensions.Shared.dll" ]
- имя типа с пространством имён и имя файла со сборкой. Если указан относительный путь, то он рассчитывается от папки с конфигурационными файлами, которая определяется переменной окруженияTESSA_CONFIG_ROOT
(по умолчанию соответствует папке с приложением). При этом приложение может не ссылаться на сборку с этим именем.
В этом случае, при наличии в нём метода Invoke
, класс будет инстанциирован конструктором по умолчанию, а метод будет вызван. Если указанный тип не может быть загружен, то добавляется ошибка конфигурации.
Tip
Использование такого загрузчика позволяет создать библиотеку, содержащую логику загрузки, которая не ссылается на Tessa.dll
, т.е. не использует никаких зависимых от платформы типов.
Метод Invoke
получает следующие параметры:
- Первый параметр типа
Dictionary<string, object?>
- это содержимое json-объекта в директиве.include
, включая свойство".loader.type"
. - Второй параметр типа
Dictionary<string, object?>
- это целиком содержание конфигурации на момент обработки (но без директивы.include
). - Параметр типа
CancellationToken
получает токен отмены операции. - Все прочие параметры при их наличии получает значение
null
. Если они не допускаютnull
(например, типы-значения), то вызов метода приведёт к ошибке.
Important
Если какие-либо из перечисленных параметров отсутствуют, то они не передаются. Например, если метод имеет единственный параметр Dictionary<string, object?>
, то параметры из п.2 не передаются.
Метод Invoke
возвращает одно из следующих значений:
Dictionary<string, object?>?
- результирующий json-объект для объединения с текущим конфигурационным файлом, илиnull
, если обработка не требуется. Метод вызывается синхронно.ValueTask<Dictionary<string, object?>?>
- асинхронная задача, возвращающая json-объект для объединения с текущим конфигурационным файлом, илиnull
, если обработка не требуется. Метод вызывается асинхронно.Task<Dictionary<string, object?>?>
- асинхронная задача, аналогичнаяValueTask
в п.2.- Все прочие возвращаемые значения игнорируются, т.е. аналогичны возврату
null
. Метод вызывается синхронно.
Пример такого класса:
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ExampleNamespace.Loaders
{
public sealed class ExampleConfigurationLoader
{
public async ValueTask<Dictionary<string, object?>?> Invoke(
Dictionary<string, object?> parameters,
Dictionary<string, object?> storage,
CancellationToken cancellationToken)
{
if (parameters.TryGetValue("uri", out var value) && value is string uri)
{
// зависимости необходимо создать вручную, DI недоступен
string text;
using (var httpClient = new HttpClient())
{
text = await httpClient.GetStringAsync(uri, cancellationToken).ConfigureAwait(false);
}
if (!string.IsNullOrEmpty(text))
{
// в примере не ссылаемся на Tessa.dll,
// поэтому напрямую используем библиотеку Newtonsoft.Json
var serializer = new JsonSerializer();
await using var reader = new JsonTextReader(new StringReader(text));
var settings = serializer.Deserialize<Dictionary<string, object?>>(reader);
return new Dictionary<string, object?> { { "Settings", settings } };
}
}
return null;
}
}
}
Пример упрощённого класса, который получает только параметры, т.е. значение json-объекта в директиве .include
, и синхронно возвращает хеш-таблицу.
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Newtonsoft.Json;
namespace ExampleNamespace.Loaders
{
public sealed class ExampleConfigurationLoader
{
public Dictionary<string, object?>? Invoke(Dictionary<string, object?> parameters)
{
if (parameters.TryGetValue("uri", out var value) && value is string uri)
{
string text;
using (var httpClient = new HttpClient())
{
// для сетевых запросов нежелательно выполнять синхронную загрузку
// если бы метод изначально выполнял только синхронные действия,
// то его имело бы смысл сделать синхронным
// здесь, для примера, мы асинхронный вызов преобразуем в синхронный
text = httpClient.GetStringAsync(uri).GetAwaiter().GetResult();
}
if (!string.IsNullOrEmpty(text))
{
var serializer = new JsonSerializer();
using var reader = new JsonTextReader(new StringReader(text));
var settings = serializer.Deserialize<Dictionary<string, object?>>(reader);
return new Dictionary<string, object?> { { "Settings", settings } };
}
}
return null;
}
}
}
Если класс расположен в сборке ExampleLoadersLib.dll
, которая размещена в папке приложения, то его возможно использовать в файле app.json
следующим образом:
{
".include": [
{
".loader.type": [ "ExampleNamespace.Loaders.ExampleConfigurationLoader", "ExampleLoadersLib.dll" ],
"uri": "https://my.configuration.server/files/app1.json"
}
]
}