Исходные коды программ и игр

Программирование - работа и хобби

Blazor админ панель

Язык программирования C#

Админ-панель для сайта

Админ-панель на платформе BlazorПанель администратора (сленговое: админ-панель, админка) предназначена для управления данными сайта. Создание нового и редактирование текущего контента, а также анализ и отражение результатов эффективности работы сайта. У админ-панели много обязанностей, поэтому к ней предъявляются повышенные требования. Она должна быть удобной, информативной и всегда отражать актуальные данные.

Платформа Blazor предназначена для быстрой разработки интерактивных веб страниц. Скорость разработки повышается благодаря единому языку программирования для серверной и клиентской сторон. Общение сервера и клиента происходит в режиме реального времени на скорости интернет-соединения. Как вывод: приложения Blazor идеальны для использования в качестве панели администратора управления сайтам.

Речь идёт о модели размещения Blazor Server в которой основная работа приложения происходит на сервере и результаты отправляются на клиенты. Обновление страниц происходит в фоновом режиме, необходимыми порциями, без перезагрузки всего контента. Библиотека SignalR поддерживает постоянную связь браузера и приложения Blazor. Оповещения о редактируемых данных с панели администратора мгновенно отправляются на сервер.

Шаблон AdminPanel

На основе исходников описанных на страницах сайта компоненты Blazor и вызов функций JavaScript создан прототип, шаблон для интерактивной панели администратора. Blazor AdminPanel представляет собой типичную конфигурацию админки для отображения и редактирования данных: домашняя страница - общий свод, другие страницы - редактирование данных в базе.

Веб приложение панели администратора состоит из нескольких автономных компонентов. Для поддержания актуальной информации при изменениях в базе данных компоненты используют синглетные классы (singleton - класс, допускающий создание только одного экземпляра на протяжении работы приложения). Редактирование данных вызывает события обновления компонентов на всех открытых страницах, даже в разных браузерах. Такие авто-обновляемые компоненты помогают поддерживать визуальные данные на открытых страницах админ-панели всегда в актуальном состоянии. Редактировать данные должен администратор, просматривать могут и пользователи.

Структура приложения

Приложение состоит из главной страницы отображения данных и страниц редактирования по группам. На главной странице AdminPanel выводятся все компоненты визуализации данных. Это общая страница, свод всех данных из базы. Данные разделены на группы: доходы и клиенты, технологии веб программирования, виды СУБД, география клиентов.

Каждая группа имеет свою страницу редактирования. Интерфейс работы с базой данных состоит из компонентов визуализации и таблицы редактирования данных. Данные сохраняются в базу при потере фокуса текстовым элементом <input ... @onblur="() => Table.Write()">. После записи изменений в базу результаты без задержки отображаются на всех открытых страницах приложения.

База данных

База данных приложения построена на файлах .json. У каждой логической группы свой файл. Файл представляет таблицу. Данные записываются в файл посредством Json сериализации объекта List<Rows>. Объект строк хранится в оперативной памяти, после редактирования данные строк записываются в файл. Формат Json и текстовый файл базы выбран для удобства тестирования веб приложения AdminPanel. В реальный проект можно подключить базу данных любой разновидности.

Модель базы построена на абстрактном классе Table. В данном классе заложена практически вся функциональность для чтения и обновления файлов баз данных формата .json. Table имеет событие генерируемое после записи обновленных данных в файл.

Как видно из листинга ниже в классе два абстрактных члена. Путь к файлам базы и инициализацию данных каждый производный класс переопределяет для себя индивидуально.

Листинг абстрактного класса:
public abstract class Table
{
    // Путь к базе.
    public abstract string PathTable { get; }

    // Методы инициализации данных.
    public abstract void Init();

    // Делегат события обновления таблицы базы данных.
    public delegate void UpdateEventHandler(object sender);

    // Событие обновления таблицы базы данных.
    public event UpdateEventHandler UpdateEvent;

    // База в памяти. Строки таблицы. 
    // Читается из файла и сохраняется в оперативной памяти.
    public List Rows { get; set; } = new List();

    public Table()
    {
        // Первоначальное заполнение таблицы в памяти.
        Read();
    }

    // Запись в файл базы данных.
    public void Write()
    {
        // Перед записью пересчитаем процентное содержание.
        // У кого это есть. Другим это не помешает.
        ComputePercents();

        // Запись базы в файл
        var serializerOptions = new JsonSerializerOptions
        {
            // Формирует вид, привлекательный для чтения и печати.
            WriteIndented = true,

            // Настройка кодировки символов для кириллицы.
            // По умолчанию сериализатор выполняет escape - последовательность символов,
            // отличных от ASCII.То есть он заменяет их на \uxxxx,
            // где xxxx является кодом Юникода символа.
            Encoder = JavaScriptEncoder.Create(UnicodeRanges.All, UnicodeRanges.Cyrillic)
        };

        string s = JsonSerializer.Serialize(Rows, serializerOptions);

        using var sw = new StreamWriter(PathTable);
        sw.Write(s);
        sw.Close();

        // После записи в файл базы,
        // обновляем данные строк таблиц в оперативной памяти.
        Read();

        // Событие обновления после записи.
        UpdateEvent?.Invoke(this);
    }


    // Чтение из базы в память
    public void Read()
    {
        using var streamReader = new StreamReader(PathTable);

        Rows = JsonSerializer.Deserialize>(streamReader.ReadToEnd());

        streamReader.Close();
    }

    // Вычисляет проценты для кого необходимо.
    private void ComputePercents()
    {
        // Распределение процентов
        int _100Percents = 0;
        foreach (var i in Rows)
        {
            _100Percents += i.NumCustomers;
        }

        foreach (var i in Rows)
        {
            double percent = ((double)i.NumCustomers / (double)_100Percents) * 100.0;
            i.Percent = Math.Round(percent, 2);
        }
    }
}

Унификация данных

Унификация данных достигается посредством универсального класса строки таблицы. Одновременно все параметры строки не используются ни в одной таблице, каждый производный класс имеет свой набор данных. Но использование единственного класса строки и общего абстрактного класса позволили значительно уменьшить количество кода. Производные же классы заполняют свои таблицы только необходимыми значениями, "ненужные" свойства просто игнорируются.

Листинг универсального класса:
// Универсальный шаблон строки для таблиц данных.
public class Row
{
    // Название параметра
    public string Name { get; set; }
    // Количество клиентов
    public int NumCustomers { get; set; }
    // Процентное содержание
    public double Percent { get; set; }
    // Двузначный код страны
    public string ISO { get; set; }
    // Название месяца
    public string Month { get; set; }
    // Доход
    public int Earnings { get; set; }
}

Как упоминалось выше, база данных состоит из 4-х таблиц и, соответственно, из такого же количества классов производных от Table. Данные таблиц универсальные и подойдут для административных панелей различных сайтов.

Список и характеристика классов-таблиц:

EarningsTable
Доходы и клиенты. Данные: общий доход, средний за месяц, кол-во клиентов, рост дохода по сравнению с прошлым годом. Диаграммы доходов и регистрации клиентов.
WebProgTable
Веб программирование. Данные: виды технологий программирования, процентное содержание каждого вида. Визуализирует соотношения компонент progressbar.
DBMSTable
Виды баз данных. Данные: виды СУБД, процентное содержание каждого вида. Визуализирует соотношения компонент progressbar.
RegionsTable
География клиентов. Данные: кол-во клиентов в регионах, процентное соотношение клиентов в каждом регионе. На графической карте мира выделяются цветом регионы клиентов.

Программный код классов классов WebProgTable, DBMSTable, RegionsTable одинаковый и может быть показан на примере одного класса. Общая функциональность сосредоточена в родительском классе.

Листинг класса-потомка:
public class WebProgTable : Table
{
    // Путь к своей таблице
    public override string PathTable => Constants.PathTechnologyTable;

    // Инициализация своих данных
    public override void Init()
    {
        var r = new Random();

        Rows.Add( new Row()
          {
            Name = "PHP",
            NumCustomers = r.Next(10000, 40000)
          }
        );
        ...
        Write();
    }
}

Обход нарушения унификации

В классе таблицы доходов EarningsTable присутствуют свойства итоговых значений принадлежащих только классу доходов. Итоговые данные вычисляются для всей таблицы доходов, но не для каждой строки. Эти данные вычисляются на ходу и хранятся только в оперативной памяти. Наличием итоговых значений этот класс отличается от своих собратьев.

Итоговые данные вычисляются каждый раз после записи обновленных данных в файл. Событие обновления данных в родительском классе возникает после записи в файл, когда итоговые данные еще не подсчитаны. В силу этого, программный код класса EarningsTable заменяет событие и метод записи родителя своими версиями.

Листинг класса-таблицы Доходы-клиенты:
public class EarningsTable : Table
{
    // Итоговые данные.
    public int TotalEarning { get; set; }
    public int TotalCustomers { get; set; }
    public int MonthAverage { get; set; }
    public int EarningsGrowth { get; set; }

    // Скрываем событие родителя своей реализацией.
    // Событие обновления итогов.
    new public  event UpdateEventHandler UpdateEvent;

    // Значение для сравнения с текущим годом.
    public const int LastYearEarnings = 1800000;

    // Путь к файлу базы данных.
    public override string PathTable => Constants.PathEarningsTable;

    // Инициализация общих итоговых данных.
    public EarningsTable() : base()
    {
        Total();
    }

    // Только для инициализации данных.
    public override void Init()
    {
        var r = new Random();

        Rows.Clear();

        for (int i = 0; i < 12; i++)
        {
            DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo("ru-RU").DateTimeFormat;
            string month = dtfi.GetMonthName(i + 1);

            Row item = new Row
            {
                Month = month,
                Earnings = r.Next(80000, 260001),
                NumCustomers = r.Next(300, 900)
            };

            Rows.Add(item);
        }

        Write();
    }


    // Скрываем родительский метод своей реализацией.
    // После записи родителем данных в файл, подсчитаем итоги.
    // Теперь событие гарантировано после подсчёта итоговых данных. 
    new public void Write()
    {
        base.Write();

        Total();

        // Скрыто родительское событие, 
        // поскольку оно происходит
        // до подсчёта итогов.
        UpdateEvent?.Invoke(this);
    }

    // Вычисление итоговых значений.
    public void Total()
    {
        TotalEarning = 0;
        TotalCustomers = 0;

        foreach (var i in Rows)
        {
            TotalEarning += i.Earnings;
            TotalCustomers += i.NumCustomers;
        }

        MonthAverage = TotalEarning / Rows.Count;
        EarningsGrowth = (int)(TotalEarning / (double)LastYearEarnings * 100.0 - 100.0);
    }
}

Автообновление компонентов

При редактировании базы данных, компоненты, визуализирующие информацию, должны отражать последние изменения. Для админов и пользователей комфортней, если обновление будет происходить в режиме реального времени, без перезагрузки страницы. Приложения Blazor легко реализуют эту возможность на языке C#.

В шаблоне AdminPanel выполняется автообновление компонентов при изменении информации в базе данных. AdminPanel настроена так, что автоматически обновляет компоненты даже если страницы приложения открыты в разных браузерах. Для этого эффекта приложение использует службы классов-одиночек (singleton). Службы добавляются в методе настройки служб файла startup.cs.

Настройка служб приложения:

public void ConfigureServices(IServiceCollection services)
{
    ...
    // Служба одного экземпляра класса на приложение
    services.AddSingleton();
    services.AddSingleton();
    services.AddSingleton();
    services.AddSingleton();
}

Синглетные службы гарантируют автоматическое обновление компонентов при редактировании базы данных на одной странице. Если открыто несколько вкладок браузера или страницы открыты в разных браузерах синглетных служб недостаточно для тотальных перерисовок компонентов. Для этого в классах таблиц организовано событие обновления данных. Оно генерируется после записи изменений в файл.

Программный код организации события. Полностью листинг класса см. выше.
public abstract class Table
{
    ...
	
    // Событие обновления таблицы базы данных.
    public event UpdateEventHandler UpdateEvent;

    // База данных в памяти.
    public List Rows { get; set; } = new List();

    ...

    // Запись в файл базы данных.
    public void Write()
    {
        ...
     
        using var sw = new StreamWriter(PathTable);
        sw.Write(s);
        sw.Close();

        // Обновляем данные в оперативной памяти.
        // Именно они отражаются на визуальных компонентах.
        Read();

        // Генерация события для обновления компонентов 
        // после записи файл и чтения в Rows.
        UpdateEvent?.Invoke(this);
    }
}

Код автообновляемого компонента

В качестве примера опишем код самообновляемого компонента с JavaScript диаграммой. Компонент показывает анимированный график доходов, извлекая данные из класса-таблицы EarningsTable (Доходы и клиенты). Параметр компонента Models.EarningsTable earningsTable представляет синглтон (класс-одиночка) получаемый от родительского компонента. После редактирования базы, на любой странице приложения AdminPanel, генерируется событие, перерисовывающее данный компонент.

Другие компоненты приложения построены подобным образом. Неважно в каком месте происходит запись новых данных, класс-singleton распространяет это событие по всем родственным компонентам.

@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

@* Обязательно для вызова метода Dispose, иначе метод вызываться не будет *@
@implements IDisposable

<div class="card shadow">
    <div class="card-header d-flex justify-content-between align-items-center">
        <h6 class="text-secondary font-weight-bold m-0">Доходы</h6>
    </div>
    <div class="card-body">
        <div class="chart-area"><canvas id="chartEarnings"></canvas></div>
    </div>
</div>

@code {
    // Родительский компонент в этот параметр укажет объект-синглтон.
    [Parameter]
    public Models.EarningsTable earningsTable { get; set; }

    // После того как все элементы DOM веб страницы загружены.
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            earningsTable.UpdateEvent += UpdateDB;

            // Активируем диаграмму
            await JSRuntime.InvokeVoidAsync("startChartEarnings");

            // Обновляем график свежими данными.
            await Task.Run(() => UpdateChart());
        }
    }

    private async Task UpdateChart()
    {
        int[] data = earningsTable.Rows.Select(r => r.Earnings).ToArray();
        await JSRuntime.InvokeVoidAsync("changeEarningsData", data);
    }

    public async void UpdateDB(object sender)
    {
        await UpdateChart();
    }

    // Очистка перед удалением компонента.
    public void Dispose()
    {
        earningsTable.UpdateEvent -= UpdateDB;
    }
}

Исходник приложения Blazor AdminPanel

Приложение создано в MS Visual Studio 2019 версии 16.7. Модель размещения Blazor Server. Требование .NET Core 3.1

Файл: BlazorAdminPanel-vs16.zip
Размер: 1993 Кбайт
Загрузки: 7