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

Как мы уже знаем, при использование JSON-файлов для конфигурации приложения Blazor Hybrid мы можем удобно распределять необходимые настройки приложения по отдельным секциям.  Однако, каждый раз считывать секцию, искать в секции необходимую настройку бывает не совсем удобно. Для таких случаев, используя методы расширения IConfiguration, мы можем спроецировать необходимые настройки на обычные классы .NET

Пример файла конфигурации

Используя информацию из предыдущей части, создадим в папке wwwroot проекта Blazor Hybrid файл appsettings.json со следующим содержимым:

{
  "Greetings": {
    "Message": "Привет, Blazor",
    "Text": "Приложение для демонстрации проекции конфигурации на классы"
  },
  "Counter": {
    "Value": 10
  },
  "Weather": {
    "Max": 50,
    "Min": -20,
    "Count": 10
  }
}

В этом файле мы определяем три секции с настройками для трех компонентов шаблонного приложения Blazor HybridHome, Counter и Weather. Используем этот файл в нашем приложении. Для этого загрузим его в MauiProgram.cs как мы это делали ранее:

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

using System.Reflection;

namespace MauiApp3
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                });

            using var appsettingsStream = Assembly
                .GetExecutingAssembly()
                .GetManifestResourceStream("MauiApp3.wwwroot.appsettings.json");


            if (appsettingsStream != null)
            {
                builder.Configuration.AddJsonStream(appsettingsStream);
            }

            builder.Services.AddMauiBlazorWebView();

#if DEBUG
    		builder.Services.AddBlazorWebViewDeveloperTools();
    		builder.Logging.AddDebug();
#endif

            return builder.Build();
        }
    }
}

Проекция конфигурации на классы с использование метода Bind()

Проецировать на класс мы можем как отдельные секции, так и весь файл целиком. Например, спроецируем на класс секцию Greetings. Для этого создадим новую папку Models в корне проекта и добавим в неё новый класс с именем Greetings.

public class Greetings
{
    public string? Message { get; set; }
    public string? Text { get; set; }
}

Имя класса, в принципе, может быть любым, но имена свойств в классе должны соответствовать именам настроек в проецируемой секции. Теперь добавим новое пространство имен в файл Components/_Imports.razor, добавив в него строку:

@using MauiApp3.Models

и изменим файл Home.razor следующим образом:

@inject IConfiguration config

@page "/"

<h1>@(greetings.Message)</h1>
<p>@(greetings.Text)</p>

@code{
    Greetings greetings = new();

    protected override void OnInitialized()
    {
        config.Bind("Greetings", greetings);
    }
}

Здесь мы воспользовались одной из версий метода расширения Bind(), указав в качестве параметров метода название секции из файла (Greetings) и созданный объект типа Greetings, на который необходимо спроецировать настройки из секции. Далее, в разметке компонента мы используем свойства объекта:

<h1>@(greetings.Message)</h1>
<p>@(greetings.Text)</p>

Теперь можно запустить приложение и убедиться, что настройки были получены успешно:

Рассмотрим ещё один способ проекции конфигурации на классы.

Проекция конфигурации на классы с использование метода Get<T>()

Также, мы можем использовать для проекции на класс метод расширения Get<T>(). В отличие от предыдущего метода, Get() не требуется передавать в качестве параметров готовый объект заданного типа. Для примера, воспользуемся этим методом для получения конфигурации для компонента Counter. Добавим в папку Models новый класс:

namespace MauiApp3.Models
{
    public class Counter
    {
        public int Value { get; set; }
    }
}

и изменим код компонента Counter следующим образом:

@page "/counter"

@inject IConfiguration config

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private Models.Counter? options;

    private void IncrementCount()
    {
        currentCount++;
    }

    protected override void OnInitialized()
    {
        options = config.GetRequiredSection("Counter").Get<Models.Counter>();
        if (options != null)
        {
            currentCount = options.Value;   
        }
    }
}

Здесь мы уже не создаем объект, а просто передаем в параметре метода Get() класс объект которого должен нам вернуться после проецирования конфигурации:

options = config.GetRequiredSection("Counter").Get<Models.Counter>();

Можно запустить приложение и убедиться, что конфигурация была успешно получена:

Настройки проецирования

В принципе, нам ничего не мешает спроецировать на класс все настройки из файла. Создадим в папке Models новый класс для проецирования настроек из последней секции файла:

namespace MauiApp3.Models
{
    public class Weather
    {
        public int Max {  get; set; }
        public int Min { get; set; }
        public int Count {  get; set; }    
    }
}

а также класс, в объекте которого будут содержаться все настройки из файла:

public class Options
{
    public Greetings Greetings { get; set; }
    public Counter Counter { get; set; }
    public Weather Weather { get; set; }
}

Спроецируем на этот класс все наши настройки из appsettings.json, например, в компоненте Weather:

@page "/weather"

@inject IConfiguration config

<h1>Weather</h1>

<p>This component demonstrates showing data.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    private Options? options;

    protected override async Task OnInitializedAsync()
    {
        options = config.Get<Options>(c=>
        {
            c.ErrorOnUnknownConfiguration = true;
            c.BindNonPublicProperties = true;
        });

        // Simulate asynchronous loading to demonstrate a loading indicator
        await Task.Delay(500);

        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };

        if (options != null)
        {
            forecasts = Enumerable.Range(1, options.Weather.Count).Select(index => new WeatherForecast
                {
                    Date = startDate.AddDays(index),
                    TemperatureC = Random.Shared.Next(options.Weather.Min, options.Weather.Max),
                    Summary = summaries[Random.Shared.Next(summaries.Length)]
                }).ToArray();
        }

       
    }

    private class WeatherForecast
    {
        public DateOnly Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

Здесь мы воспользовались для проецирования на класс методом Get() и, при этом, в качестве параметра метода использовали делегат в котором передали настройки привязки свойств класса к настройкам:

options = config.Get<Options>(c=>
{
    c.ErrorOnUnknownConfiguration = true;
    c.BindNonPublicProperties = true;
});

Теперь, в случае, если в файле appsettings.json будет содержаться неизвестная настройка, то будет сгенерировано исключение. Также привязываться будут и непубличные свойства класса. Убедимся, что файл был полностью спроецирован:

и в запущенном приложении:

Итого

Проецирование конфигурации на классы позволяет предоставить в приложении все настройки в виде обычного объекта .NET заданного типа. Такой подход удобно применять в случае использования конфигурации в виде JSON-файлов. В классе .NET свойства должны соответствовать именам настроек. Проецировать мы можем как отдельные секции файла, так и файл целиком, использую методы расширения Bind() и Get<T>() для IConfiguration.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии