Проекция конфигурации на классы

  • Михаил
  • 8 мин. на прочтение
  • 143
  • 31 May 2024
  • 31 May 2024

В .NET Core, проекция конфигурации на классы (Configuration Binding) - это механизм, который позволяет связывать структуру конфигурационных данных (например, из файлов appsettings.json или переменных среды) с классами C#. Это позволяет удобно работать с конфигурационными данными в приложении, избегая прямого обращения к разрозненным источникам конфигурации.

Вот основные шаги по проекции конфигурации на классы в .NET Core:

1. Определение классов для конфигурации:
Создайте классы C#, которые будут соответствовать структуре ваших конфигурационных данных. Например:

public class DatabaseSettings
{
    public string ConnectionString { get; set; }
    public int TimeoutSeconds { get; set; }
}

public class EmailSettings
{
    public string SmtpServer { get; set; }
    public int SmtpPort { get; set; }
    public string SenderEmail { get; set; }
}

2. Регистрация конфигурации в IConfigurationRoot:
В методе ConfigureServices() класса Startup, зарегистрируйте конфигурационные данные и связанные с ними классы:

public void ConfigureServices(IServiceCollection services)
{
    // Связываем конфигурацию с классами
    services.Configure<DatabaseSettings>(Configuration.GetSection("DatabaseSettings"));
    services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));

    // Другие регистрации сервисов
    services.AddControllers();
}

3. Использование связанных классов в приложении:
Теперь вы можете внедрять эти классы конфигурации в качестве зависимостей в контроллеры, сервисы или другие компоненты вашего приложения:

public class MyController : ControllerBase
{
    private readonly DatabaseSettings _databaseSettings;
    private readonly EmailSettings _emailSettings;

    public MyController(IOptions<DatabaseSettings> databaseOptions, IOptions<EmailSettings> emailOptions)
    {
        _databaseSettings = databaseOptions.Value;
        _emailSettings = emailOptions.Value;
    }

    public IActionResult Index()
    {
        // Использование настроек из связанных классов
        var connectionString = _databaseSettings.ConnectionString;
        var smtpServer = _emailSettings.SmtpServer;

        // Логика контроллера
        return View();
    }
}

В этом примере контроллер получает доступ к конфигурационным настройкам через внедрение зависимостей IOptions<DatabaseSettings> и IOptions<EmailSettings>.

Ключевые преимущества использования проекции конфигурации на классы:

1. Структурированный доступ к конфигурации: Классы C# обеспечивают четкую и структурированную модель для конфигурационных данных, облегчая их использование в приложении.
2. Валидация и типизация: Классы конфигурации позволяют применять валидацию и использовать типизированные значения вместо работы с разрозненными строковыми данными.
3. Injecting Dependencies: Благодаря механизму внедрения зависимостей, конфигурационные классы могут быть легко внедрены в другие компоненты, обеспечивая высокую степень модульности.
4. Переиспользование: Определенные классы конфигурации могут быть повторно использованы в различных частях приложения, улучшая управляемость и согласованность конфигурационных данных.

Второй способ от metanit.com.

Например, определим в проекте новый файл person.json, который будет хранить данные пользователя:

{
  "name": "Tom",
  "age": "22"
}

Под эти данные определим в проекте класс Person:

public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; } = 0;
}

Теперь свяжем конфигурацию из файла person.json с объектом класса Person:

var builder = WebApplication.CreateBuilder();
var app = builder.Build();
builder.Configuration.AddJsonFile("person.json");
var tom = new Person();
app.Configuration.Bind(tom);    // связываем конфигурацию с объектом tom
app.Run(async (context) => await context.Response.WriteAsync($"{tom.Name} - {tom.Age}"));
app.Run();
public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; } = 0;
}

Ключевой момент заключается в применении метода Bind:

var tom = new Person();
app.Configuration.Bind(tom);

Для объекта IConfiguration определен метод Bind(), который в качестве параметра принимает объект, который надо связать с данными. Стоит отметить, что между конфигурацией в json и классом Person имеется соответствие по названию свойств, благодаря чему может осуществляться связка (регистр в данном случае роли не играет).

В качестве альтернативы методу Bind мы могли бы использовать метод Get<T>(), который возвращает объект созданного класса:

Person tom = app.Configuration.Get<Person>();

В этом случае нам не надо предварительно создавать объект класса Person.

Подобным образом можно выполнять привязку при получении конфигурации через механизм Dependency Injection:

var builder = WebApplication.CreateBuilder();
var app = builder.Build();
builder.Configuration.AddJsonFile("person.json");
app.Map("/", (IConfiguration appConfig) =>
{
    var tom = appConfig.Get<Person>();  // связываем конфигурацию с объектом tom
    return $"{tom.Name} - {tom.Age}";
});
app.Run();
public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; } = 0;
}

Привязка сложных объектов
Рассмотрим привязку более сложных по структуре данных. Определим следующий файл person.json:

{
  "age": "28",
  "name": "Tom",
  "languages": [
    "English",
    "German",
    "Spanish"
  ],
  "company": {
    "title": "Microsoft",
    "country": "USA"
  }
}

Для представления этих данных в коде C# определим следующие классы:

public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public List<string> Languages { get; set; } = new();
    public Company? Company { get; set; }
}
public class Company
{
    public string Title { get; set; } = "";
    public string Country { get; set; } = "";
}

Теперь выполним в приложении привязку из конфигурации json в объекты классов C#:

var builder = WebApplication.CreateBuilder();
var app = builder.Build();
builder.Configuration.AddJsonFile("person.json");
var tom = new Person();
app.Configuration.Bind(tom);
app.Run(async (context) =>
{
    context.Response.ContentType = "text/html; charset=utf-8";
    string name = $"<p>Name: {tom.Name}</p>";
    string age = $"<p>Age: {tom.Age}</p>";
    string company = $"<p>Company: {tom.Company?.Title}</p>";
    string langs = "<p>Languages:</p><ul>";
    foreach (var lang in tom.Languages)
    {
        langs += $"<li><p>{lang}</p></li>";
    }
    langs += "</ul>";
    await context.Response.WriteAsync($"{name}{age}{company}{langs}");
});
app.Run();
public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public List<string> Languages { get; set; } = new();
    public Company? Company { get; set; }
}
public class Company
{
    public string Title { get; set; } = "";
    public string Country { get; set; } = "";
}

Привязка конфигурации из xml

Возьмем выше определенные классы Person и Company. И добавим в проект файл person.xml, который будет содержать аналогичные данные:

<?xml version="1.0" encoding="utf-8" ?>
<person>
  <name>Tom</name>
  <age>35</age>
  <languages name="0">English</languages>
  <languages name="1">German</languages>
  <languages name="2">Chinese</languages>
  <company>
    <title>Microsoft</title>
    <country>USA</country>
  </company>
</person>

Обратите внимание на установку в файле xml массивов - они имеют атрибут name, который определяет условный индекс.

Применим конфигурацию из выше определенного файла xml в приложении:

var builder = WebApplication.CreateBuilder();
var app = builder.Build();
builder.Configuration.AddXmlFile("person.xml");
var tom = new Person();
app.Configuration.Bind(tom);
app.Run(async (context) =>
{
    context.Response.ContentType = "text/html; charset=utf-8";
    string name = $"<p>Name: {tom.Name}</p>";
    string age = $"<p>Age: {tom.Age}</p>";
    string company = $"<p>Company: {tom.Company?.Title}</p>";
    string langs = "<p>Languages:</p><ul>";
    foreach (var lang in tom.Languages)
    {
        langs += $"<li><p>{lang}</p></li>";
    }
    langs += "</ul>";
    await context.Response.WriteAsync($"{name}{age}{company}{langs}");
});
app.Run();
public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public List<string> Languages { get; set; } = new();
    public Company? Company { get; set; }
}
public class Company
{
    public string Title { get; set; } = "";
    public string Country { get; set; } = "";
}

Как можно заметить из кода, меняется только подключение файла с json на xml, а весь остальной код остается прежним.

Привязка секций конфигурации

В примерах выше выполнялась привязка корневого объекта конфигурации, однако также можно осуществлять привязку отдельных секций. Например, выше в файле json и xml была определена секция company, которая хранит компанию пользователя. Выполним привязку отдельно этой секции к объекту класса Company:

var builder = WebApplication.CreateBuilder();
var app = builder.Build();
builder.Configuration.AddJsonFile("person.json");
Company company = app.Configuration.GetSection("company").Get<Company>();
app.Run(async (context) =>
{
    await context.Response.WriteAsync($"{company.Title} - {company.Country}");
});
app.Run();
public class Company
{
    public string Title { get; set; } = "";
    public string Country { get; set; } = "";
}

С помощью метода GetSection() получаем нужную нам секцию конфигурации и затем также можно вызвать методы Bind или Get и выполнить привязку.
Таким образом, проекция конфигурации на классы является мощным инструментом в .NET Core, позволяющим эффективно работать с конфигурационными данными в рамках приложения.