Содержание
Как мы уже знаем, при использование 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 Hybrid — Home
, 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
.