Проект Blazor WebAssembly «Open Ecology». База данных и постраничный вывод результатов

В предыдущей части мы написали небольшой компонент Razor для парсинга открытых данных Росприроднадзора. При этом, сложность заключалась в двух моментах: 1) сам по себе файл имел большой размер (более 600 Мб), а структура данных в файле, с точки зрения десериализации, оказалась неверной (отсутствие запятых, разделяющих json-объекты, сами объекты не заключены в массив и т.д.). В итоге нам удалось разобрать этот файл достаточно большой ценой в плане времени — более 316 000 записей десериализуются очень и очень долго. Сегодня мы продолжим дорабатывать наш проект «Open Ecology» и запишем все полученные данные из файла в базу данных MySql.

Blazor WebAssembly и базы данных

В отличие от Blazor Server, Blazor WASM имеет серьезные ограничения по работе с базами данных. Если быть точнее, то на сайте Microsoft дословно сказано следующее:

Приложения Blazor WebAssembly выполняются в песочнице WebAssembly, которая запрещает большинство прямых подключений к базе данных.

Следовательно, если нашему приложению Blazor WASM необходимо работать с данными, содержащимися, например, в БД MySQL, то наиболее простой способ это сделать — организовать взаимодействие нашего приложения с каким-либо сервером, который в ответ на запрос Blazor WASM будет возвращать готовый набор данных, например, в формате JSON. То есть, организовать вот такую схему взаимодействия:

Blazor WASM DB

Таким образом, нам необходимо:

  1. Разработать приложение-сервер для непосредственной работы с БД MySQL
  2. Перенести код чтения JSON-файла из приложения Blazor WASM в приложение-сервер
  3. Учитывая большой объем данных (более 316 000 записей объемом свыше 600 Мб), обеспечить по-страничный вывод результатов.

Приложение ASP.NET Core WebAPI для работы с БД MySQL

Основные задачи приложения — это:

  1. Разобрать JSON-файл (при необходимости)
  2. Провести обновление (наполнение) базы данных информацией из этого файла
  3. По запросы пользователя выдавать постранично данные по источникам загрязнения из БД

Так как и это приложение и наш проект Blazor WebAssembly будут по сути использовать одни и те же классы для работы с данными, то имеет смысл создать ещё один проект на который будут ссылаться оба наших приложения — библиотеку классов .NET. Это позволит нам избежать создания лишних копий файлов в обоих проектах.

Библиотека классов .NET

Добавляем в наше решение в Visual Studio проект типа «Библиотека классов»

Я назвал этот проект «OELibrary». В итоге, решение в Visual Studio должно выглядеть вот так:

Теперь в проекте OELibrary создаем новую папку и добавляем в неё файл с классами, которые мы получили в предыдущей статье.

Открываем файл Pollutant.cs и изменяем название пространства имен на OELibrary.Models. Должно получиться следующим образом:

namespace OELibrary.Models
{

    public class Pollutant
    {
        public long Id { get; set; }
        public string name { get; set; }
        public string number { get; set; }
       <--прочие поля и свойства класса-->
    }
    <--прочие классы-->
}

Теперь, чтобы любой проект из решение мог использовать классы из нашей библиотеки, необходимо будет указать ссылку на библиотеку в настройках нового проекта. Как это делается — рассмотрим далее.

Проект ASP.NET Core WebAPI

Добавим в решение новый проект ASP.NET Core WebAPI и назовем его OpenEcologyAPI:

Настройки проекта можно оставить по умолчанию. В случае необходимости мы всегда сможем их изменить:

Теперь наше решение должно выглядеть следующим образом:

Чтобы приложение OpenEcologyAPI могло использовать классы из библиотеки классов OELibrary, необходимо добавить в проект ссылку. Нажимаем правой кнопкой мыши на названии проекта OpenEcologyAPI и выбираем в меню «Добавить —> Ссылку на проект»

Теперь мы можем в любом месте нашего проекта подключить пространство имен OELibrary.Models и использовать классы из библиотеки.

Добавление в проект Entity Framework Core для MySQL

Возможно, логичнее и правильнее было бы выбрать «родную» Microsoft SQL Server, но я сделал выбор в сторону MySQL по следующим причинам:

  1. Возможно, что полученная в результате работы над проектом база данных мне потребуется для других целей где как раз-таки используется MySQL и не используется EF Core и не хотелось бы делать лишних «телодвижений» по переносу БД
  2. Желание посмотреть работу EF Core именно с этой БД

В любом случае, если вы захотите повторить мой опыт и использовать другую БД, то c EF Core, я думаю, вы сделаете это достаточно просто. Теперь рассмотрим, что необходимо сделать, чтобы использовать MySQL в проекте ASP.NET Core WebAPI.

 Устанавливаем необходимые пакеты

Для работы с MySQL в Entity Framework Core нам потребуется пакет от стороннего разработчика под названием Pomelo.EntityFrameworkCore.MySql. Открываем менеджер пакетов NuGet:

Ищем и устанавливаем пакет Pomelo.EntityFrameworkCore.MySql

Также, Visual Studio установить дополнительно необходимые пакеты для работы с EF Core. После установки пакета список зависимостей проекта будет выглядеть следующим образом:

Вносим изменения в классы библиотеки OELibrary

Чтобы мы могли использовать классы из OELibrary для работы с Entity Framework Core, необходимо внести некоторые изменения в уже созданные классы, а именно:

  1. определить необходимые ключи
  2. внести изменения в типы данных

Начнем с ключей. На данный момент у нас в библиотеке определены следующие классы:

  • Pollutant — основной класс, содержащий информацию о объекту (источнику загрязнения)
  • Factкласс, содержащий информацию по определенным воздействиям на окружающую среду. Например, мы сейчас работаем только с одним воздействием — загрязнением атмосферного воздуха, но в этом же классе может содержаться информация по отходам, сбросам и т.д.
  • Air — информация о конкретном воздействии на атмосферный воздух (выбросе конкретного загрязняющего вещества)

Таким образом, Pollutant — это главная сущность для Fact, а сам Fact — это главная сущность для Air. Соответственно, определим в классах следующие ключи:

public class Pollutant
 {
     public long Id { get; set; } //ключ
     <--другие свойства-->
 }

 public class Fact
 {
     public long Id { get; set; } //ключ
     public long PollutantId { get; set; } //внешний ключ
     <--другие свойства-->
 }

 public class Air
 {
     public long Id { get; set; } //ключ
     public long FactId { get; set; } //внешний ключ
    <--другие свойства-->
 }

Теперь, чтобы EF Core смог работать с наборами данных (массивами facts и air у классов Pollutant и Fact) необходимо заменить их интерфейсами ICollection. Таким образом, окончательный вид классов для работы у нас будет следующий:

namespace OELibrary.Models
{

    public class Pollutant
    {
        public long Id { get; set; }
        public string name { get; set; }
        public string number { get; set; }
        public string? registry_type { get; set; }
        public string? registry_category { get; set; }
        public DateTime? inclusion_date { get; set; }
        public string org_full_name { get; set; }
        public float? value_co2 { get; set; }
        public ICollection<Fact> facts { get; set; }
    }

    public class Fact
    {
        public long Id { get; set; }
        public long PollutantId { get; set; }
        public ICollection<Air> air { get; set; }
    }

    public class Air
    {
        public long Id { get; set; }
        public long FactId { get; set; }
        public float? annual_value { get; set; }
        public string? code { get; set; }
        public string? name { get; set; }
    }

}
Создаем контекст данных

Всё взаимодействие с базой данных в Entity Framework Core происходит с использованием специального класса, который называется контекст данных. Добавим в наш проект папку Data и создадим в этой папке новый класс, который назовем PollutantContext и который будет иметь следующий код:

using OELibrary.Models;
using Microsoft.EntityFrameworkCore;

namespace OpenEcologyApi.Data
{
    public class PollutantContext: DbContext
    {
        public PollutantContext(DbContextOptions<PollutantContext> options) : base(options)
        {
            Database.EnsureCreated();
        }

        public DbSet<Pollutant> Pollutants { get; set; }
    }
}

Наш класс является наследником класса DbContext, который располагается в пространстве имен Microsoft.EntityFrameworkCore и определяет контекст данных, используемый для взаимодействия с базой данных. В свою очередь DbSet<Pollutant> — это набор объектов, которые хранятся в базе данных.

Теперь создадим новую строку подключения к БД MySQL. Для этого откроем файл appsettings.json и добавим в него следующую строку:

{
  "ConnectionStrings": {
    "DefaultConnection": "server=localhost;user=login;password=123456;database=dbName;"
  },
    <--Прочие настройки -->
  }

здесь, в DefaultConnection содержится строка подключения к БД MySQL. В этой строке:

  • server — адрес сервера MySQL (обычно это localhost)
  • user — имя пользователя БД
  • password — пароль пользователя
  • database — имя БД к которой будет осуществляться подключение.

Теперь откроем файл Program.cs нашего проекта и подключим сервис для работы с БД:

<--Прочие настройки приложения-->
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

<--Подключаем сервис для работы с БД-->

builder.Services.AddDbContext<PollutantContext>(options =>
        options.UseMySql(builder.Configuration.GetConnectionString("DefaultConnection"), new MySqlServerVersion("8.0.26") ));

<------------------------------------->

<--Прочие настройки приложения-->
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

Как видите, для подключения сервиса для работы с БД мы указали контекст данных и передали в метод UseMySql строку подключения из настроек. Что касается второго параметра метода, то в него передается версия сервера MySQL и на момент написания этой статьи у меня был установлен сервер версии 8.0.26.

На данном этапе, работы по подключению MySQL к нашему проекту можно считать завершенными. Осталось написать контроллер с помощью которого мы будем получить и возвращать данные об источниках загрязнения.

Пишем контроллер

Добавим в проект папку Controllers и создадим в этой папке класс UpdateController. Используя этот контроллер, мы будем обновлять нашу базу данных сведениями из JSON-файла. Содержимое классе будет следующим:

using Microsoft.AspNetCore.Mvc;
using OELibrary.Models;
using OpenEcologyApi.Data;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace OpenEcologyAPI.Controllers
{
    [ApiController]
    [Route("Update")]
    public class UpdateController : Controller
    {

        PollutantContext _context;
        ILogger<UpdateController> _logger;
        IWebHostEnvironment _appEnvironment;
        public UpdateController(PollutantContext context, ILogger<UpdateController> logger, IWebHostEnvironment appEnvironment)
        {
            _context = context;
            _logger = logger;
            _appEnvironment = appEnvironment;
        }

        [HttpGet]
        public async Task<IActionResult> Index()
        {
            int count = 0;
            string path = Path.Combine(new string[] { _appEnvironment.ContentRootPath, @"onv_pub.json" });
            FileStream data = new(path, FileMode.Open, FileAccess.Read);
            List<Pollutant> list = new();
            using (StreamReader streamReader = new(data))
            {
                while (true)
                {
                    string temp = await streamReader.ReadLineAsync();
                    if (list.Count > 0)
                        if ((list.Count % 100 == 0) || ((temp == null)))
                        {
                            await _context.Pollutants.AddRangeAsync(list);
                            count += _context.SaveChanges();
                            list.Clear();
                            _logger.Log(logLevel: LogLevel.Information, $"Добавили запись в БД. Всего записей добавлено {count}");
                        }

                    if (temp == null)
                        break;

                    Pollutant rootobject = JsonSerializer.Deserialize<Pollutant>(Regex.Unescape(temp));
                    if (_context.Pollutants.FirstOrDefault(p => p.number == rootobject.number) != null)
                    {
                        continue;
                    }
                    list.Add(rootobject);
                }

            }
            return Ok();
        }
    }
}

По сути, в метод Index() мы перенесли тот код, который разработали в прошлой части. За некоторомы исключениями, касающимися работы с БД. Кратко рассмотрим их.

Во-первых, мы проверяем есть ли запись с определенным значением поля number в БД:

Pollutant rootobject = JsonSerializer.Deserialize<Pollutant>(Regex.Unescape(temp));
if (_context.Pollutants.FirstOrDefault(p => p.number == rootobject.number) != null)
{
    continue;
}

На момент наполнения БД данными всего из одного файла такой проверки будет достаточно. Впоследствии эту часть метода необходимо будет доработать и добавить сравнение не только по полю number, но и по значениям других полей. При проверке мы использовали возможности LINQ.

Если запись в БД не найдена, то объект, полученный из JSON добавляется в список list.

Во-вторых, как только количество элементов в списке list достигнет 100 или же мы дойдем до конца файла, объекты из списка записываются в БД:

string temp = await streamReader.ReadLineAsync();
if (list.Count > 0)
    if ((list.Count % 100 == 0) || ((temp == null)))
    {
        await _context.Pollutants.AddRangeAsync(list);
        count += _context.SaveChanges();
        list.Clear();
        _logger.Log(logLevel: LogLevel.Information, $"Добавили запись в БД. Всего записей добавлено {count}");
    }

а сам список очищается и добавление новых объектов в него начинается заново. Процесс добавления записей в БД досточно продолжительный, поэтому  дополнительно мы будем выводить в лог сообщение о том сколько записей было добавлено.

Теперь можем запустить проект и, так как мы запустим его в режиме разработки (Development), то откроется окно браузера со Swagger’ом где мы и увидим созданный нами методи и даже, при необходимости, сможем его протестировать.

Сам файл с данными JSON необходимо положить в папку к проекту, чтобы приложение могло его обнаружить.

Организация постраничной навигации по набору данных в Entity Framework

Так как у нас в БД планируется хранение даже не одно, а сотен тысяч записей, то, естественно, выдать за один запрос такой набор данных пользователю — смерти подобно. Поэтому, удобнее всего, в этом случае организовать постраничную навигацию по набору данных. Чтобы это сделать, нам потребуется разработать ещё два класса:

  1. класс для хранения информации о том на какой странице находится пользователь и сопутствующей информации (например, есть ли ещё страницы в наборе данных)
  2. класс, содержащий данные по полученному для очередной страницы набору данных

Так как эти классы мы будем сериализовать в итоге в JSON и передавать клиенту, то разместим их в нашей библиотеке OELibrary. Создадим в папке Models проекта OELibrary класс PageViewModel следующего вида:

using System.Text.Json.Serialization;
namespace OpenEcologyAPI.Models
{
    public class PageViewModel
    { 
        public int CurrentPage { get; set; } //текущая страница на которой находится пользователь
        public int PageCount { get; set; } //общее количество страниц

        public PageViewModel(int count, int pageNumber, int pageSize)
        {
            CurrentPage = pageNumber;
            PageCount = (int)Math.Ceiling(count / (double)pageSize); 
        }
       
        public PageViewModel() 
        { 
           CurrentPage = 0; 
           PageCount = 0; 
        }

        //true - если пользователь может запросить предыдущую страницу
        [JsonIgnore]
        public bool HasPreviousPage 
        {
            get
            {
                return (CurrentPage > 1);
            }
        }
        
        //true - если пользователь может запросить следующую страницу 
        [JsonIgnore]
        public bool HasNextPage
        {
            get
            {
                return (CurrentPage < PageCount);
            }
        }
    }
}

В этом классе мы определили атрибуты сериализации, а именно, исключили из сериализации свойства HasNextPage и HasPreviousPage так как оба эти свойства, по сути, определяются в зависимости от значений других свойств. Также мы добавили конструктор без параметров, так как это является одним из условий сериализации/десериализации JSON в C#.

Всё в той же папке Models создадим ещё один класс — IndexViewModel. Этот класс будет содержать набор данных по источникам загрязнения из БД и объект класса PageViewModel

using OELibrary.Models;

namespace OpenEcologyAPI.Models
{
    public class IndexViewModel
    { 
        public IEnumerable<Pollutant> Pollutants { get; set; }
        public PageViewModel ViewModel { get; set; }
    }
}

Вернемся в проект OpenEcologyApi. Теперь создадим в папке Controllers ещё один контроллер — PollutantController и напишем для него метод Index, который на запрос пользователя будет возвращать JSON, содержащий объект класса IndexViewModel:

using Microsoft.AspNetCore.Mvc;
using OpenEcologyAPI.Data;
using OpenEcologyAPI.Models;
using OELibrary.Models;
using Microsoft.EntityFrameworkCore;

namespace OpenEcologyAPI.Controllers
{
    [ApiController]
    [Route("pollutants")]
    public class PollutantController : Controller
    {
        PollutantContext _context;
        public PollutantController(PollutantContext context)
        { 
            _context = context;
        }
        
        [HttpGet]
        public async Task<IActionResult> Index(int page = 1, int pageSize = 10)
        {

            int elementCount = pageSize;
            if (elementCount>50)
                elementCount = 50;

            var count = await _context.Pollutants.CountAsync();
            var items = await _context.Pollutants.Skip((page - 1) * elementCount).Take(elementCount).Include(x=>x.facts).ToListAsync();

            foreach (Pollutant pollutant in items)
            foreach (Fact fact in pollutant.facts)
            {
               IQueryable<Air>? airs = _context.Air.Where(x => x.FactId == fact.Id);
    			fact.air = airs.ToArray();
            }

            PageViewModel pageViewModel = new PageViewModel(count, page, pageSize);
            IndexViewModel viewModel = new IndexViewModel
            {
                ViewModel = pageViewModel,
                Pollutants = items
            };


            return new JsonResult(viewModel);
        }
    }
}

В параметрах метода мы получаем номер очередной страницы и количество элементов, которые необходимо вернуть пользователю. Если пользователь задает количество элементов на странице более 50, то сервер вернет только 50 записей. Наполнение страницы данными происходит с использованием методов Skip и Take LINQ, о которых вы можете более подробнее узнать в этой статье.

Теперь можно запустить приложение и протестировать работу с БД (конечно, предварительно наполнив её данными с использованием ранее написанного контролера UpdateController) . Например, я выполнил вот такой запрос:

https://localhost:7194/pollutants?page=100&pageSize=2

в ответ я получил следующий JSON:

{
  "pollutants": [
    {
      "id": 199,
      "name": "пилорама ИП Триллер Ж.В.",
      "number": "04-0224-002089-П",
      "registry_type": "Regional",
      "registry_category": "3",
      "inclusion_date": "2018-12-07T13:57:17",
      "org_full_name": "Индивидуальный предприниматель Триллер Жанна Валерьевна",
      "value_co2": 0,
      "facts": [
        {
          "id": 207,
          "pollutantId": 199,
          "air": [
            {
              "id": 2223,
              "factId": 207,
              "annual_value": 0.100037,
              "code": "2908",
              "name": "Пыль неорганическая, содержащая 70-20% двуокиси кремния (шамот, цемент, пыль цементного производства - глина, глинистый сланец, доменный шлак, песок, клинкер, зола кремнезем и др.)"
            },
            {
              "id": 2224,
              "factId": 207,
              "annual_value": 0.011731,
              "code": "2732",
              "name": "Керосин"
            },
            {
              "id": 2225,
              "factId": 207,
              "annual_value": 0.005549,
              "code": "0304",
              "name": "Азот (II) оксид (Азота оксид)"
            },
            {
              "id": 2226,
              "factId": 207,
              "annual_value": 0.114658,
              "code": "0328",
              "name": "Углерод (Сажа)"
            },
            {
              "id": 2227,
              "factId": 207,
              "annual_value": 0.03415,
              "code": "0301",
              "name": "Азота диоксид (Азот (IV) оксид)"
            },
            {
              "id": 2228,
              "factId": 207,
              "annual_value": 0.032792,
              "code": "0330",
              "name": "Сера диоксид (Ангидрид сернистый)"
            },
            {
              "id": 2229,
              "factId": 207,
              "annual_value": 0.026578,
              "code": "2936",
              "name": "Пыль древесная"
            },
            {
              "id": 2230,
              "factId": 207,
              "annual_value": 0.000521,
              "code": "2909",
              "name": "Пыль неорганическая, содержащая двуокись кремния менее 20% двуокиси кремния (доломит, пыль цементного производства - известняк, мел, огарки, сырьевая смесь, пыль вращающихся печей, боксит и др.)"
            },
            {
              "id": 2231,
              "factId": 207,
              "annual_value": 2e-7,
              "code": "0703",
              "name": "Бенз/а/пирен (3,4-Бензпирен)"
            },
            {
              "id": 2232,
              "factId": 207,
              "annual_value": 0.304296,
              "code": "0337",
              "name": "Углерод оксид"
            }
          ]
        }
      ]
    },
    {
      "id": 200,
      "name": "Обособленное подразделение \"Централизованная сервисная служба\"",
      "number": "14-0131-000502-П",
      "registry_type": "Federal",
      "registry_category": "3",
      "inclusion_date": "2016-12-20T10:48:05",
      "org_full_name": "ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ \"БИЗНЕС ФУД СФЕРА\"",
      "value_co2": 0,
      "facts": [
        {
          "id": 208,
          "pollutantId": 200,
          "air": [
            {
              "id": 2233,
              "factId": 208,
              "annual_value": 0.143323,
              "code": "0301",
              "name": "Азота диоксид (Азот (IV) оксид)"
            },
            {
              "id": 2234,
              "factId": 208,
              "annual_value": 0.06048,
              "code": "0123",
              "name": "диЖелезо триоксид /в пересчете на железо/ (Железа оксид)"
            },
            {
              "id": 2235,
              "factId": 208,
              "annual_value": 0.023289,
              "code": "0304",
              "name": "Азот (II) оксид (Азота оксид)"
            },
            {
              "id": 2236,
              "factId": 208,
              "annual_value": 0.441468,
              "code": "0337",
              "name": "Углерод оксид"
            },
            {
              "id": 2237,
              "factId": 208,
              "annual_value": 0.04032,
              "code": "2930",
              "name": "Пыль абразивная (Корунд белый; Монокорунд)"
            }
          ]
        }
      ]
    }
  ],
  "viewModel": {
    "currentPage": 100,
    "pageCount": 13127,
    "hasPreviousPage": true,
    "hasNextPage": true
  }

Ответ, как и ожидалось, содержит данные по различным объектам. Теперь нам осталось только настроить наш сервер, а именно, разрешить кроссдоменные запросы — включить CORS. Перейдем в файл Program.cs и добавим в него следующие строки:

<--Код сокращен для экономии места-->

builder.Services.AddCors();

<--Код сокращен для экономии места-->
var app = builder.Build();

<--Код сокращен для экономии места-->

app.UseCors(builder => builder.AllowAnyOrigin());

<--Код сокращен для экономии места-->

На данном этапе нам достаточно тех возможностей сервера, которые мы в него добавили, а именно: наполнение БД из файла JSON и постраничная навигация по набору данных. Теперь можно вернуться к основному приложения Blazor WASM и организовать получение данных от сервера.

Blazor WebAssembly. Получение и отображение данных из БД MySQL

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

@page "/Air_data"
@inject HttpClient Http

@using OELibrary.Models

<h3>Данные об объектах негативного воздействия</h3>

<table class="table">
    <thead>
        <tr>
            <th scope="col">Код</th>
            <th scope="col">Название</th>
            <th scope="col">Категория</th>
            <th scope="col">Организация</th>
        </tr>
    </thead>
    <tbody>
        @{
            if ((model != null)&&(model.Pollutants !=null))
            {
                foreach (Pollutant item in model.Pollutants)
                {
                    <tr>
                        <td>@item.number</td>
                        <td>@item.name</td>
                        <td>@item.registry_category</td>
                        <td>@item.org_full_name</td>
                    </tr>
                }
            }
        }
    </tbody>
</table>

@if (prevPage > 0)
{
    <button @onclick="(()=>GetData(prevPage))">Назад</button>
    
}
@if (nextPage > 0)
{
    <button @onclick="(() => GetData(nextPage))">Вперед</button>	
}

@if ((model != null)&&(model.Pollutants !=null))
{
    <p>Страница @model.ViewModel.CurrentPage из @model.ViewModel.PageCount</p>
}

@code {
    public int prevPage = 0;
    public int nextPage = 0;
    public int currentPage = 1;
    public IndexViewModel model { get; internal set; } = new();



    protected override async Task OnInitializedAsync()
    {
        await GetData();
    }

    public async Task GetData(int page = 1)
    {   
        model = await Http.GetFromJsonAsync<IndexViewModel>($"https://localhost:5001/pollutants?page={page}");
        if (model.ViewModel.HasPreviousPage)
            prevPage = page-1;
        if (model.ViewModel.HasNextPage)
            nextPage = page+1;
        currentPage = page;
        await InvokeAsync(StateHasChanged);
    }

}

Рассмотрим, что происходит теперь в компоненте AirData.  В методе GetData мы запрашиваем очередную страницу с данными с сервера и запоминаем текущую, следующую и предыдущие страницы (их номера). В визуальной части компонента мы выводим таблицу с полученными данными, а также, в зависимости от того на какой странице мы находимся — показываем кнопки «Назад» и «Вперед», а также информацию на какой странице мы находимся. Визуально это выглядит следующим образом:

На сегодня, думаю, достаточно. В следующий раз продолжим работу с набором данных и сделаем наш компонент ещё удобнее.

Итого

Сегодня наш проект пополнился новыми возможностями — мы начали разработку библиотеку классов, написали (пусть ещё и не полностью) небольшой сервер для работы с базой данных MySQL и научились запрашивать данные с сервера из нашего приложения Blazor WASM. В следующей части мы добавим новые возможности в наш проект и сделаем его более интересным.

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