суббота, 16 августа 2025 г.

25.08.17, Asp.Net, RazorPages, Mvc

25.08.17, MVC, RazorPages,

https://ask.chadgpt.ru/share/652619c9-155e-45dc-bf67-a02863956274

Asp.Net Core

Razor Pages versus MVC projects - по русски

Razor Pages против MVC в ASP.NET Core — основные отличия и когда что использовать.

Что такое Razor Pages и MVC

  • Razor Pages: странично-ориентированная модель. Каждая страница представлена отдельным классом PageModel и представлением (cshtml). Хорошо подходит для простых и средних экранов, где логика связана с конкретной страницей.
  • MVC (Model-View-Controller): архитектура с разделением на контроллеры, действия и представления. Подходит для крупных приложений с большим количеством взаимосвязанных страниц и общей логикой приложения.

Ключевые различия

  • Архитектура:
    • Razor Pages: PageModel + Razor Page. Фокус на страницах.
    • MVC: Controller + Views + Models. Фокус на контроллерах и представлениях.
  • Простота в настройке:
    • Razor Pages: обычно проще для небольших проектов, минимальная настройка маршрутизации.
    • MVC: чуть более сложная маршрутизация и структура, но сильная организованность в крупных проектах.
  • Связь между кодом и представлением:
    • Razor Pages: теснее связаны страница и её модель; удобно для локального уровня бизнес-логики, форм и запросов одной страницы.
    • MVC: более разделено; контроллеры можно переиспользовать и тестировать легче, когда логика распространяется на несколько представлений.
  • Тестируемость:
    • Razor Pages: тестирование возможно, но для сложной бизнес-логики может потребоваться дополнительная абстракция.
    • MVC: отлично тестируется благодаря чёткой разделённости слоёв и зависимости через сервисы.
  • Поддержка зависимостей:
    • Razor Pages: зависимости обычно внедряются через конструктор PageModel.
    • MVC: также через DI в контроллерах; можно делегировать общую логику сервисам.
  • Маршрутизация:
    • Razor Pages: маршруты строятся вокруг папок и имен страниц; путь по умолчанию соответствует расположению файла cshtml.
    • MVC: маршрутизация через маршруты контроллеров и действий; гибче для сложной навигации.

Когда использовать Razor Pages

  • Приложения с небольшим количеством экранов и простой навигацией.
  • Формы и страницы, где каждый экран имеет свою логику и данные.
  • Быстрая разработка прототипов и MVP.
  • Чуть более простой входной порог для новичков в ASP.NET Core.

Когда использовать MVC

  • Большие корпоративные приложения с большим числом экранов, сложной бизнес-логикой и повторно используемыми компонентами.
  • Требуется централизованная навигация и единая точка входа для множества функций.
  • Нужно более явное разделение слоёв и облегчённое тестирование бизнес-логики через сервисы и репозитории.

Особенности совместного использования

  • В одном проекте можно сочетать Razor Pages и MVC (Controllers). Например, часть функционала реализована через Razor Pages, а остальная — через контроллеры и представления.
  • В проекте можно выбрать единый стиль для всего приложения, но можно по месту применять иной стиль там, где он удобнее.

Советы по миграции/выбору

  • Начните с Razor Pages, если хотите быструю разработку и простоту. Это хороший старт для небольших проектов.
  • Перейдите к MVC, если проект растёт и требуется более явное разделение обязанностей и централизованная маршрутизация.
  • Для команд: если несколько разработчиков работают над одной функциональностью, MVC может предоставить более явную границу ответственности.
  • Применяйте сервисы и D.I. независимо от выбранной модели: это облегчает тестирование и развёртку.

Пример структуры

  • Razor Pages: Pages/Index.cshtml + Pages/Index.cshtml.cs, Pages/Account/Login.cshtml + Pages/Account/Login.cshtml.cs
  • MVC: Controllers/HomeController.cs, Views/Home/Index.cshtml, Models/SomeModel.cs

Если хотите, могу показать минимальные шаблоны проекта: Razor Pages и MVC, или помочь выбрать подход под ваш конкретный кейс на примерах вашего приложения.

Допустим мы рабoтаем с RAzorPage и MVC,
а также с
MAssTransit RabitMQ, который присылает данные и они отрисовываются на странице
Какой тип Проекта лучше выбрать для этого
case
?

Коротко: для сценария, когда у вас есть серверные сообщения из очереди (MassTransit + RabbitMQ) и данные отрисовываются на странице Realtime или близко к этому, лучше выбрать Razor Pages для простоты и быстрого старта, но использовать MVC может быть предпочтительным, если проект уже большой и ориентирован на строгую архитектуру.

Разбор по кейсу:

  • Что вы хотите считать “данные от очереди” и как они попадают на страницу
    • Если это одноразовая загрузка данных при запросе страницы: Razor Pages отлично справляется, логика будет локализована в PageModel и можно легко внедрить сервисы через DI.
    • Если требуется сложная навигация, множество экранов и тесная связь между представлениями: MVC может дать более явную архитектуру Controllers + Views + Models и упростить тестирование отдельных частей.
  • Реалтайм (постоянное обновление UI без перезагрузки страницы)
    • Ни Razor Pages, ни MVC сами по себе не дают веб‑сокета/SignalR. Вам понадобится добавить SignalR или другую технологию push.
    • В Razor Pages это можно сделать так же просто: создать страницу, на ней подключить клиента SignalR, подписаться на обновления. Логика подписки может жить в PageModel, а клиентская часть — в JS на странице.
    • В MVC похожий подход: контроллеры возвращают представления, а клиентская часть через SignalR получает обновления и обновляет DOM. В MVC шаблоны структурируются по папкам Views и Controllers, что может быть удобнее для больших UI.
  • Архитектура и поддержка
    • Razor Pages проще для маленьких и средних проектов: меньше файлов, прямое меппинг страницы к URL, хорошие локальные тесты страниц.
    • MVC лучше для крупных проектов с явной разделённой ответственностью между контроллером и представлением, когда у вас много экранов, модулей и строгая компоновка команд.
  • Работа с MassTransit RabbitMQ
    • В любом случае вы будете настраивать потребителя (consumer) в сервисе BackgroundService или HostedService. Этот сервис может публиковать обновления через SignalR в реальном времени или сохранять данные в кэш/бид фарм и обновлять UI по запросу.
    • Независимо от выбора Razor Pages или MVC, интеграция через DI, конфигурацию очередей и обработчики можно вынести в отдельный сервис, который будет работать одинаково.
  • Пример минимального решения
    • Razor Pages: добавляете PageModel для нужной страницы, внедряете IHubContext вашего SignalR хаба, в Consumer поступившее сообщение отправляете через хаб на клиенты. На клиенте пишете JavaScript, который подписывается на хабы и обновляет страницу.
    • MVC: создаете контроллер, который отдает представление, аналогично подключаете SignalR на представлении и используете тот же хаб. Контроллеры могут возвращать частичные представления или данные через API для динамических обновлений.

Рекомендация по выбору

  • Если проект небольшой/средний и у вас уже есть Razor Pages, используйте Razor Pages + SignalR. Это быстрее начать и проще сопровождать.
  • Если проект уже большой и имеет множество экранов и бизнес‑логики в контроллерах, возможно лучше начать с MVC, чтобы сохранить единообразие архитектуры и разделение обязанностей.
  • В любом случае добавляйте SignalR для реального времени и выделяйте логику очередей в отдельный сервис, чтобы можно было тестировать независимыми частями.

Если хотите, могу привести пример минимального шаблона проекта с Razor Pages или MVC, который подписывается на MassTransit RabbitMQ и отправляет обновления через SignalR.

Сразу у меня возник уточняющий вопрос.
В чем разница между BackgroundService и HostedService в Asp.Net Core?

Коротко:

  • HostedService (интерфейс IHostedService) — это контракт для любого сервиса, который можно запустить при старте приложения и остановить при завершении. Это абстракция: вы реализуете запуск и остановку сами, а регистрация происходит в DI-контейнере через AddHostedService<t>.
  • BackgroundService — это готовая абстракция поверх IHostedService, которая упрощает создание фоновых задач. Это абстракция абстракции: вы наследуетесь от класса BackgroundService и реализуете метод ExecuteAsync(CancellationToken), внутри которого пишете цикл или длительную работу. BackgroundService сам заботится о том, как корректно запускать задачу и обрабатывать отмену через токен.

Что это значит на практике:

  • Используйте IHostedService, когда:
    • вам нужен более тонкий контроль над тем, как сервис стартует/останавливается, или у вас сложная логика инициализации.
    • вы хотите реализовать несколько разных сервисов, которые начинают работу одновременно или последовательно.
    • вам нужна конкретная конфигурация жизненного цикла вне стандартного фонового потока.
  • Используйте BackgroundService, когда:
    • вам нужна стандартная модель фоновой задачи с циклом выполнения и автоматическим управлением отменой.
    • задача проста: бесконечный или периодический цикл с ожиданием между итерациями.
    • вы хотите минимизировать boilerplate код и сосредоточиться на рабочем процессе.

Типичный пример:

  • BackgroundService можно применить для простой очереди: в ExecuteAsync пишем цикл, вытаскиваем сообщение из очереди и обрабатываем, используя токен отмены для корректного завершения.
  • IHostedService может потребовать явного управления стартом/остановом, например, если вам нужно запустить несколько сервисов параллельно и синхронизировать их, или если запуск выполняется не как бесконечный цикл, а по расписанию или по событиям.

Важно помнить:

  • Один и тот же фоновой процесс можно реализовать и через BackgroundService, и через IHostedService напрямую. BackgroundService лишь упрощает обычную задачу фоновой обработки.
  • При использовании ASP.NET Core с Mvc/Mazor Pages и MassTransit, фоновая обработка часто реализуется через IHostedService или через интеграцию с MassTransit, которая имеет свои хендлеры. Если ваша логика состоит в циклическом ожидании сообщений и обработке, BackgroundService — удобный выбор.

Если хотите, могу привести минимальные шаблоны: пример реализации IHostedService и аналогичный через BackgroundService, чтобы сравнить объем кода и типичное поведение.

Допустим у нас работают эти Сервисы ( IHostedService или BackgroundService) с MAss Transit
MAssTRansit - пишет данные в очередь, а эти Сервисы разгребают очередь и формируют некий массив в виде CircularBufer. Один раз в 1 - 3 - 5 секунд (число выберем потом - надо попробовать так и этак) эти Данные из CircularBuffer должны попасть в эту страницу (Пример для RAzorPage):
---------
RazorPage
using Microsoft.AspNetCore.Mvc.RazorPages;
using ChartDirector;

namespace NetCoreRazorCharts.Pages
{
public class Symbolline2Model : PageModel
{
public void OnGet()
{
ViewData["Title"] = "Symbol Line Chart (2)";
RazorChartViewer viewer = new RazorChartViewer(HttpContext, "chart1");
ViewData["Viewer"] = viewer;
createChart(viewer);
}

//
// Create chart
//
private void createChart(RazorChartViewer viewer)
{
// The data for the line chart
double[] data0 = {42, 49, 33, 38, 64, 56, 29, 41, 44, 57, 59, 42};
double[] data1 = {65, 75, 47, 34, 42, 49, 73, 62, 90, 69, 66, 78};
double[] data2 = {36, 28, 25, 28, 38, 20, 22, 30, 25, 33, 30, 24};
string[] labels = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"};

// Create a XYChart object of size 600 x 375 pixels
XYChart c = new XYChart(600, 375);

// Add a title to the chart using 18pt Times Bold Italic font
c.addTitle("Product Line Global Revenue", "Times New Roman Bold Italic", 18);

// Set the plotarea at (50, 55) and of 500 x 280 pixels in size. Use a vertical gradient
// color from light blue (f9f9ff) to sky blue (aaccff) as background. Set border to
// transparent and grid lines to white (ffffff).
c.setPlotArea(50, 55, 500, 280, c.linearGradientColor(0, 55, 0, 335, 0xf9fcff, 0xaaccff
), -1, Chart.Transparent, 0xffffff);

// Add a legend box at (50, 28) using horizontal layout. Use 10pt Arial Bold as font,
// with transparent background.
c.addLegend(50, 28, false, "Arial Bold", 10).setBackground(Chart.Transparent);

// Set the x axis labels
c.xAxis().setLabels(labels);

// Set y-axis tick density to 30 pixels. ChartDirector auto-scaling will use this as the
// guideline when putting ticks on the y-axis.
c.yAxis().setTickDensity(30);

// Set axis label style to 8pt Arial Bold
c.xAxis().setLabelStyle("Arial Bold", 8);
c.yAxis().setLabelStyle("Arial Bold", 8);

// Set axis line width to 2 pixels
c.xAxis().setWidth(2);
c.yAxis().setWidth(2);

// Add axis title using 10pt Arial Bold Italic font
c.yAxis().setTitle("Revenue in USD millions", "Arial Bold Italic", 10);

// Add a line layer to the chart
LineLayer layer = c.addLineLayer2();

// Set the line width to 3 pixels
layer.setLineWidth(3);

// Add the three data sets to the line layer, using circles, diamands and X shapes as
// symbols
layer.addDataSet(data0, 0xff0000, "Quantum Computer").setDataSymbol(Chart.CircleSymbol,
9);
layer.addDataSet(data1, 0x00ff00, "Atom Synthesizer").setDataSymbol(Chart.DiamondSymbol,
11);
layer.addDataSet(data2, 0xff6600, "Proton Cannon").setDataSymbol(Chart.Cross2Shape(), 11
);

// Output the chart
viewer.Image = c.makeWebImage(Chart.SVG);

// Include tool tip for the chart
viewer.ImageMap = c.getHTMLImageMap("", "",
"title='Revenue of {dataSetName} in {xLabel}: US$ {value}M'");
}
}
}

ВОт примерно такой сценарий

Понял задачу. Ниже предлагаю один из рабочих вариантов архитектуры и пример кода для интеграции MassTransit, BackgroundService/HostedService и передачи данных в Razor Page как CircularBuffer, с периодической выгрузкой в страницу.

Ключевые идеи

  • MassTransit пишет данные в очередь.
  • Фоновый сервис (IHostedService или BackgroundService) читает очередь, формирует CircularBuffer (кольцевой буфер) и публикует данные на страницу каждые 1–5 секунд.
  • Razor Page получает текущий набор данных и строит график с использованием вашего примера ChartDirector.
  • Для передачи данных между фоном и страницей можно использовать небольшой сервис состояния, например IChartDataStore, который хранит текущее содержимое CircularBuffer и безопасно доступен из PageModel через внедрение зависимостей.
  • Обновление на странице можно реализовать через перезагрузку страницы (OnGet) или через SignalR, если нужна живой перегруз данных. В вашем примере приведен статичный пример, так что можно просто обновлять данные при каждом запросе.

Что нужно реализовать пошагово

  1. Определить модель данных для CircularBuffer
  • Создать обобщенный CircularBuffer<t> либо использовать готовый реализационный простой массив с индексацией.
  • В этом буфере хранить, например, массив значений double[] и, возможно, метки времени.
  1. Реализовать сервис состояния для графика
  • Интерфейс IChartDataStore с методом GetLatestData() и событием обновления (если хотите push-уведомления).
  • Реализация ChartDataStore хранит CircularBuffer<double> и обеспечивает потокобезопасный доступ (lock или ConcurrentQueue/SemaphoreSlim).
  1. Настройка MassTransit и потребителей
  • В асинхронном потребителе сообщений из очереди получайте данные и кладите их в CircularBuffer внутри ChartDataStore.
  • Можно сделать простой DTO-сообщение, например, ChartPoint { double Value; DateTime Timestamp; }.
  1. Реализация фона
  • Реализуйте два варианта:
    • IHostedService, где внутри вы постоянно читаете сообщения из очереди и обновляете ChartDataStore.
    • Или наследуйте BackgroundService (который упрощает реализацию фона с ExecuteAsync) и используйте IBus или IAsyncEnumerable из MassTransit.
  • При выборе используйте один компонент: BackgroundService упрощает цикл, но IHostedService тоже подходит.
  1. Встраивание в Razor Page
  • Внедрите IChartDataStore в PageModel.
  • В OnGet получите текущие данные из хранилища и передайте в ViewData/Model так, чтобы chart-диаграмма отрисовалась тем же способом, что в вашем примере.
  • Если хотите живой обновляющий график без перезагрузки страницы, можно добавить SignalR-хаб и отправлять новые данные клиенту.

Пример упрощенного кода

  1. CircularBuffer и хранилище
    public class CircularBuffer<t>
    {
    private readonly T[] _buffer;
    private int _head;
    private int _count;
    private readonly object _sync = new(); public CircularBuffer(int capacity)
    {
    _buffer = new T[capacity];
    } public void Add(T item)
    {
    lock (_sync)
    {
    _buffer[_head] = item;
    _head = (_head + 1) % _buffer.Length;
    if (_count < _buffer.Length) _count++;
    }
    } public T[] ToArray()
    {
    lock (_sync)
    {
    var result = new T[_count];
    for (int i = 0; i < _count; i++)
    {
    int idx = (_head - _count + i + _buffer.Length) % _buffer.Length;
    result[i] = _buffer[idx];
    }
    return result;
    }
    } public int Count => _count;

}

public interface IChartDataStore
{
void AddPoint(double value);
double[] GetLatestData();
}

public class ChartDataStore : IChartDataStore
{
private readonly CircularBuffer<double> _buffer;

public ChartDataStore(int capacity = 100)

{

    _buffer = new CircularBuffer&lt;double&gt;(capacity);

}

 

public void AddPoint(double value)

{

    _buffer.Add(value);

}

 

public double[] GetLatestData()

{

    return _buffer.ToArray();

}

}

  1. Фоновый сервис с MassTransit
    public class ChartBackgroundService : BackgroundService
    {
    private readonly IBusControl _bus;
    private readonly IChartDataStore _store; public ChartBackgroundService(IChartDataStore store, IBusControl bus)
    {
    _store = store;
    _bus = bus;
    } protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
    // Пример подписки на сообщения. В реальности используйте конфигурацию MassTransit для подписчика.
    var handle = _bus.ConnectReceiveEndpoint("chart-queue", cfg =>
    {
    cfg.Handler<chartpointmessage>(async context =>
    {
    if (stoppingToken.IsCancellationRequested) return;
    _store.AddPoint(context.Message.Value);
    });
    });

    try
    {
    await Task.CompletedTask; // здесь можно подождать завершения
    }
    finally
    {
    await handle.StopAsync();
    }
    }

}

public class ChartPointMessage
{
public double Value { get; set; }
public DateTime Timestamp { get; set; }
}

  1. Razor Page
    public class Symbolline2Model : PageModel
    {
    private readonly IChartDataStore _store; public Symbolline2Model(IChartDataStore store)
    {
    _store = store;
    } public void OnGet()
    {
    ViewData["Title"] = "Symbol Line Chart (2)";
    RazorChartViewer viewer = new RazorChartViewer(HttpContext, "chart1");
    ViewData["Viewer"] = viewer;
    createChart(viewer, _store.GetLatestData());
    } private void createChart(RazorChartViewer viewer, double[] data)
    {
    // аналогично вашему примеру, но используйте data вместо статических массивов
    double[] data0 = data.Length > 0 ? data : new double[] { };
    // если объем данных меньше ожидаемого, можно заполнить нули или адаптировать
    // Затем строим график как в вашем примере
    XYChart c = new XYChart(600, 375);
    // ... остальной код без изменений, подставляя data0
    // Output the chart
    viewer.Image = c.makeWebImage(Chart.SVG);
    viewer.ImageMap = c.getHTMLImageMap("", "", "title='Revenue of {dataSetName} in {xLabel}: US$ {value}M'");
    }

}

  1. Регистрация в DI и конфигурация в Startup/Program
    // Пример в .NET 6+ Program.cs
    builder.Services.AddSingleton<ichartdatastore, chartdatastore>();
    builder.Services.AddSingleton<backgroundservice, chartbackgroundservice>();
    // Конфигурация MassTransit
    builder.Services.AddMassTransit(x =>
    {
    // конфигурация потребителей
    x.AddConsumer<chartpointconsumer>();
    // ...
    });
    builder.Services.AddMassTransitHostedService();

// Регистрация фонового сервиса
builder.Services.AddHostedService<chartbackgroundservice>();

note

  • Ваша текущая страница может требовать обновления данных без перезагрузки. Если хотите real-time, добавьте SignalR: Hub, клиентскую логику на JS для периодического запроса или подписку на события.

Как запустить и тестировать

  • Запустите приложение. Очередь MassTransit получает данные через продюсер и публикует их в ChartDataStore.
  • Откройте Razor Page, и она отрисует текущий набор точек как график, аналогично вашему примеру.
  • Чтобы изменить период обновления данных на 1, 3 или 5 секунд, корректируйте задержку в месте формирования CircularBuffer, например, в потребителе MassTransit или в фоне, если он инициирует добавление точек через внешний источник с задержкой.

Если хотите, могу привести конкретный минимальный рабочий шаблон проекта Razor Pages с MassTransit и примерами Consumer-а и PageModel-ов, адаптированный под ваш кейс. Также могу помочь добавить SignalR для реального онлайн обновления графика.

Давайте пока упрстим задачу.
Нам на этом этапе необходимо СНАЧАЛА ПОНЯТЬ как у нас получиться обновлять этот график.
1. Поэтому мы считаем, что пока у нас нет никакого
MAssTranzit.
2. Мы должны сделать сервис, который генерит примерно такие данные случайным образом.
//
The data for the line chart
double[] data0 = {42, 49, 33, 38, 64, 56, 29, 41, 44, 57, 59, 42};
double[] data1 = {65, 75, 47, 34, 42, 49, 73, 62, 90, 69, 66, 78};
double[] data2 = {36, 28, 25, 28, 38, 20, 22, 30, 25, 33, 30, 24};
string[] labels = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"
Nov", "Dec"};
Похожие на эти Данные генерим каждую секунду (однуб две, три секунды).
Надо подобрать.
3. ПОказывем эти данные на графике. Код я уже выставлял, но повторю.
using Microsoft.AspNetCore.Mvc.RazorPages;
using ChartDirector;

namespace NetCoreRazorCharts.Pages
{
public class Symbolline2Model : PageModel
{
public void OnGet()
{
ViewData["Title"] = "Symbol Line Chart (2)";
RazorChartViewer viewer = new RazorChartViewer(HttpContext, "chart1");
ViewData["Viewer"] = viewer;
createChart(viewer);
}

//
// Create chart
//
private void createChart(RazorChartViewer viewer)
{
// The data for the line chart
double[] data0 = {42, 49, 33, 38, 64, 56, 29, 41, 44, 57, 59, 42};
double[] data1 = {65, 75, 47, 34, 42, 49, 73, 62, 90, 69, 66, 78};
double[] data2 = {36, 28, 25, 28, 38, 20, 22, 30, 25, 33, 30, 24};
string[] labels = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"};

// Create a XYChart object of size 600 x 375 pixels
XYChart c = new XYChart(600, 375);

// Add a title to the chart using 18pt Times Bold Italic font
c.addTitle("Product Line Global Revenue", "Times New Roman Bold Italic", 18);

// Set the plotarea at (50, 55) and of 500 x 280 pixels in size. Use a vertical gradient
// color from light blue (f9f9ff) to sky blue (aaccff) as background. Set border to
// transparent and grid lines to white (ffffff).
c.setPlotArea(50, 55, 500, 280, c.linearGradientColor(0, 55, 0, 335, 0xf9fcff, 0xaaccff
), -1, Chart.Transparent, 0xffffff);

// Add a legend box at (50, 28) using horizontal layout. Use 10pt Arial Bold as font,
// with transparent background.
c.addLegend(50, 28, false, "Arial Bold", 10).setBackground(Chart.Transparent);

// Set the x axis labels
c.xAxis().setLabels(labels);

// Set y-axis tick density to 30 pixels. ChartDirector auto-scaling will use this as the
// guideline when putting ticks on the y-axis.
c.yAxis().setTickDensity(30);

// Set axis label style to 8pt Arial Bold
c.xAxis().setLabelStyle("Arial Bold", 8);
c.yAxis().setLabelStyle("Arial Bold", 8);

// Set axis line width to 2 pixels
c.xAxis().setWidth(2);
c.yAxis().setWidth(2);

// Add axis title using 10pt Arial Bold Italic font
c.yAxis().setTitle("Revenue in USD millions", "Arial Bold Italic", 10);

// Add a line layer to the chart
LineLayer layer = c.addLineLayer2();

// Set the line width to 3 pixels
layer.setLineWidth(3);

// Add the three data sets to the line layer, using circles, diamands and X shapes as
// symbols
layer.addDataSet(data0, 0xff0000, "Quantum Computer").setDataSymbol(Chart.CircleSymbol,
9);
layer.addDataSet(data1, 0x00ff00, "Atom Synthesizer").setDataSymbol(Chart.DiamondSymbol,
11);
layer.addDataSet(data2, 0xff6600, "Proton Cannon").setDataSymbol(Chart.Cross2Shape(), 11
);

// Output the chart
viewer.Image = c.makeWebImage(Chart.SVG);

// Include tool tip for the chart
viewer.ImageMap = c.getHTMLImageMap("", "",
"title='Revenue of {dataSetName} in {xLabel}: US$ {value}M'");
}
}
}
3.
Мы должны брать последние сгенеренные данные и отображать на Графике.
С периодом указанном выше.
--------------------
То есть у нас работает СЕрвис.
Он генерит случайные похожие цифры.
Доставляет эти данные на Страницу
Генерит график и отображает этот график на Странице.
Наверное, надо как-то предусмотрть Кнопки
Start - Stop.

Звучит как разумный план. Ниже кратко как можно реализовать шаги, не используя MassTransit на старте, и как добавить простейшие кнопки Start/Stop, чтобы обновлять график раз в несколько секунд.

Что нужно сейчас

  • Сделать сервис, который каждую секунду (или каждые 2–3 секунды) генерирует новый набор данных в схожем формате: three double[], один для каждого набора, и обновляет внешний источник (п save в памяти или в статическом поле).
  • На странице Razor нужен механизм отображения текущего набора данных в ChartDirector ChartViewer, и механизм для периодического обновления на клиенте (например, через повторный вызов страницы, или через SignalR позже). В текущем примере без SignalR мы можем обновлять через кадр браузера с помощью пост-allback-эффекта или через запрос на страницу, который возвращает обновленный график. Но чтобы обновлять автоматически без перезагрузки, нужен либо SignalR, либо polling через JavaScript.

Как реализовать по шагам

  1. Простой сервис генерации данных
  • Добавьте сервис в ASP.NET Core, который держит в памяти текущее состояние данных и метод GenerateNext() для обновления. Например:
    • хранить текущие данные в памяти, например, класс DataStore с полями data0, data1, data2 и labels.
    • метод GenerateNext(), который модифицирует каждое значение на небольшую случайную величину, ограничивает диапазон.
    • метод GetCurrent() возвращает три массива и подпись.
  1. Интеграция с Razor Page
  • В PageModel добавьте зависимость на этот сервис (Singleton или Scoped, на ваше усмотрение). В OnGet() можно получить текущие данные и построить график, как вы делаете сейчас.
  • Добавьте кнопку Start/Stop на страницу. При нажатии Start сервис начинает вызывать GenerateNext() по таймеру, а при Stop останавливает таймер.
  • Для обновления графика на клиенте без перезагрузки можно реализовать polling: JavaScript через setInterval делает запрос к endpoint (например, /Chart/Refresh) и возвращает обновленный SVG/URL-данные. В этом случае вам нужно endpoint, который возвращает обновленный график в виде SVG или обновляет изображение и image map.
  1. Реализация без MassTransit
  • Не обязательно использовать MassTransit на данном этапе. Просто создайте фоновой таймер в .NET:
    • В сервисе храните System.Threading.Timer и поле isRunning.
    • Start запускает таймер с интервалом 1000–3000 мс, вызывает GenerateNext() и сохраняет новые данные.
    • Stop отключает таймер.
  • Endpoint в Razor Pages или API контроллере, который возвращает текущие данные, или обновляет график. Вы можете либо:
    • пересоздать RazorChartViewer заново и вернуть SVG, либо
    • отдавать обновленный набор данных и строить график на стороне клиента (не рекомендуется без ChartDirector на клиенте).
  1. Пример структуры кода (упрощённый набросок)
  • DataStore.cs:
    • public double[][] CurrentDatas; // data0, data1, data2
    • public string[] Labels;
    • public void GenerateNext() { // модификации }
  • ChartService.cs:
    • Depends on DataStore
    • public (double[] d0, double[] d1, double[] d2, string[] labels) GetCurrent()
  • Pages/Symbolline2Model.cs (PageModel):
    • Добавьте IActionResult Refresh() => возвращает Chart в SVG или данные
    • Start/Stop endpoints или используйте параметры запроса action=start/stop
  • Razor Page View:
    • Добавьте кнопки Start и Stop, и JavaScript, который делает fetch('/Symbolline2/Refresh') и обновляет элементViewer, либо перезагружает страницу с новым SVG.
  1. Пример алгоритма генерации (псевдкод)
  • data0[i] = clamp(data0[i] + rand(-5, 5), minValue, maxValue)
  • так же для data1, data2
  • labels остаются неизменными
  1. Как начать с UI
  • В тексте страницы разместите кнопки:
    • <button id="startBtn">Start</button>
    • <button id="stopBtn">Stop</button>
  • JavaScript:
    • при клике Start: отправляете POST на /Symbolline2/Start, получаете подтверждение и начинаете polling через setInterval
    • при Stop: отправляете POST на /Symbolline2/Stop, останавливаете polling
    • В polling вызываете /Symbolline2/GetChart, который возвращает SVG или данные для обновления графика
  • В ответ вы получите новый SVG для Viewer. Если вы возвращаете SVG, то легко заменить viewer.Image на полученный SVG.
  1. Безопасность и простота
  • Для начала можно реализовать локальный таймер в самом PageModel, но корректнее — отдельный сервис.
  • Убедитесь, что вы используете однозначные типы и не перегружаете память.

Что именно могу сделать сейчас

  • Могу привести готовый минимальный пример Razor Pages проекта, где:
    • есть DataStore и ChartService
    • есть PageModel с Start/Stop и Refresh
    • есть страница с кнопками и элементом для графика
    • используется только график без MassTransit, без SignalR
  • Также могу адаптировать ваш существующий код ChartDirector под новый поток данных, чтобы при каждом Refresh вызывался createChart с новыми данными.

Сообщите, хотите ли привести минимальный рабочий шаблон проекта прямо здесь, и какие именно детали подключить (Start/Stop endpoints, polling vs push).

Давайте сделаем вариант с Перегрузкой - это нормально.
Главное, чтобы данные были разные.
Начнем тетстировать с Одной (1) секундой.
Если будет плохо - будем увеличивать.
Давайте Пишите код. Вроде все у Вас есть

Понял. Ниже пример простого веб-приложения на ASP.NET Core с перегрузкой данных (перебор новых случайных чисел) и графиком, обновляющимся каждую секунду. Реализованы Start/Stop кнопки. Используется ChartDirector для графика. Если у вас другой стек, скажите — адаптирую под него.

Структура проекта (минимальная версия)

  • Program.cs: настройка веб-приложения и эндпойнтов Start/Stop
  • Pages/Index.razor: страница с кнопками и графиком (используем Blazor Server для простоты)
  • DataService.cs: генерация случайных данных с перегрузкой
  • _Imports.razor и _Layout.cshtml в зависимости от шаблона проекта

Код примера:

Program.cs

using Microsoft.AspNetCore.Builder;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.Extensions.Hosting;

using System.Threading;

using System.Threading.Tasks;

 

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddServerSideBlazor();

builder.Services.AddSingleton<DataService>(); // общий сервис данных

 

var app = builder.Build();

 

if (!app.Environment.IsDevelopment())

{

    app.UseExceptionHandler("/Error");

}

 

app.UseStaticFiles();

app.UseRouting();

 

app.MapBlazorHub();

app.MapFallbackToPage("/_Host");

 

app.Run();

DataService.cs

using System;

using System.Collections.Generic;

using System.Threading;

using System.Threading.Tasks;

 

public class DataPoint

{

    public DateTime Time { get; set; }

    public double Value { get; set; }

}

 

public class DataService

{

    private readonly Random _rand = new Random();

    private CancellationTokenSource _cts;

    private Task _loopTask;

 

    public event Action<List<DataPoint>> OnDataUpdated;

 

    private List<DataPoint> _points = new List<DataPoint>();

 

    public IReadOnlyList<DataPoint> Points => _points.AsReadOnly();

 

    public void Start(int intervalMs = 1000, int keepLastN = 60)

    {

        Stop();

        _cts = new CancellationTokenSource();

        _loopTask = Task.Run(async () =>

        {

            while (!_cts.Token.IsCancellationRequested)

            {

                GenerateNewPoint(keepLastN);

                OnDataUpdated?.Invoke(new List<DataPoint>(_points));

                await Task.Delay(intervalMs, _cts.Token);

            }

        }, _cts.Token);

    }

 

    public void Stop()

    {

        if (_cts != null)

        {

            _cts.Cancel();

            try { _loopTask?.Wait(); } catch { }

            _cts = null;

        }

    }

 

    private void GenerateNewPoint(int keepLastN)

    {

        var now = DateTime.UtcNow;

        // простая перегрузка: сгенерировать значение, зависящее от времени

        double baseVal = 50 + Math.Sin(now.Ticks / 1_000_000_000.0) * 10;

        // случайная перегрузка

        double delta = (_rand.NextDouble() - 0.5) * 8; // +/-4

        double value = baseVal + delta;

 

        _points.Add(new DataPoint { Time = now, Value = value });

 

        // сохраняем только последние N точек

        if (_points.Count > keepLastN)

        {

            _points.RemoveRange(0, _points.Count - keepLastN);

        }

    }

}

Pages/Index.razor

@page "/"

@inject DataService DataService

 

<h3>График с перегрузкой данных</h3>

 

<div>

    <button @onclick="Start">Start</button>

    <button @onclick="Stop" disabled="@isStopped">Stop</button>

</div>

 

<CanvasChart @ref="chart" DataPoints="pointsToShow" />

 

@code {

    private bool isStopped = true;

    private List<DataPoint> pointsToShow = new();

 

    protected override void OnInitialized()

    {

        DataService.OnDataUpdated += OnDataUpdated;

    }

 

    private void OnDataUpdated(List<DataPoint> pts)

    {

        // обновляем локальный набор точек для графика

        pointsToShow = pts;

        InvokeAsync(StateHasChanged);

    }

 

    private void Start()

    {

        isStopped = false;

        // генерируем данные каждую секунду

        DataService.Start(1000, 60);

    }

 

    private void Stop()

    {

        isStopped = true;

        DataService.Stop();

    }

 

    // сохранить при удалении компонента

    public void Dispose()

    {

        DataService.OnDataUpdated -= OnDataUpdated;

        DataService.Stop();

    }

}

Components/CanvasChart.razor

@using System.Globalization

@code {

    [Parameter] public List<DataPoint> DataPoints { get; set; } = new();

    private string CanvasId = "chart_" + Guid.NewGuid().ToString("N");

 

    protected override void OnAfterRender(bool firstRender)

    {

        if (firstRender && DataPoints != null)

        {

            DrawChart();

        }

        else

        {

            DrawChart();

        }

    }

 

    private void DrawChart()

    {

        // Простой холст без внешних зависимостей

        // Рисуем линию через Canvas 2D

        var data = DataPoints ?? new List<DataPoint>();

        // Код рисования через JS-интероп можно добавить здесь,

        // например вызвать JS-функцию для отрисовки графика на canvas.

    }

}

 

<canvas id="@CanvasId" width="800" height="400" style="border:1px solid #ccc"></canvas>

 

@code {

    // можно добавить JSInvokable или JS interop для реального рисования

}

Notes:

  • Это минимальная заготовка: Start запускает обновления каждую секунду, Stop останавливает.
  • Данные генерируются как случайные значения с небольшой зависимостью от времени, чтобы график выглядел насыщенно.
  • В реальном проекте можно заменить CanvasChart на графическую библиотеку (ChartDirector, Chart.js через JS interop и т.д.).
  • Если хотите полностью без Blazor, могу привести аналогичный пример на чистом ASP.NET Core с SignalR или с чистым JS/HTML.

Уточните стек и окружение (Blazor Server, Blazor WebAssembly, MVC) — подстрою точный рабочий шаблон и дам полный код файлов проекта.

Нам не нужно никаких Canvas.
У нас выбран компонент для рисования графика - это
ChartDirector.
Вам нужно только генерить данные и Запускать генерацию графика.
Код рисования графика у Вас есть.
Вот он.
using Microsoft.AspNetCore.Mvc.RazorPages;
using ChartDirector;

namespace NetCoreRazorCharts.Pages
{
public class Symbolline2Model : PageModel
{
public void OnGet()
{
ViewData["Title"] = "Symbol Line Chart (2)";
RazorChartViewer viewer = new RazorChartViewer(HttpContext, "chart1");
ViewData["Viewer"] = viewer;
createChart(viewer);
}

//
// Create chart
//
private void createChart(RazorChartViewer viewer)
{
// The data for the line chart
double[] data0 = {42, 49, 33, 38, 64, 56, 29, 41, 44, 57, 59, 42};
double[] data1 = {65, 75, 47, 34, 42, 49, 73, 62, 90, 69, 66, 78};
double[] data2 = {36, 28, 25, 28, 38, 20, 22, 30, 25, 33, 30, 24};
string[] labels = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"};

// Create a XYChart object of size 600 x 375 pixels
XYChart c = new XYChart(600, 375);

// Add a title to the chart using 18pt Times Bold Italic font
c.addTitle("Product Line Global Revenue", "Times New Roman Bold Italic", 18);

// Set the plotarea at (50, 55) and of 500 x 280 pixels in size. Use a vertical gradient
// color from light blue (f9f9ff) to sky blue (aaccff) as background. Set border to
// transparent and grid lines to white (ffffff).
c.setPlotArea(50, 55, 500, 280, c.linearGradientColor(0, 55, 0, 335, 0xf9fcff, 0xaaccff
), -1, Chart.Transparent, 0xffffff);

// Add a legend box at (50, 28) using horizontal layout. Use 10pt Arial Bold as font,
// with transparent background.
c.addLegend(50, 28, false, "Arial Bold", 10).setBackground(Chart.Transparent);

// Set the x axis labels
c.xAxis().setLabels(labels);

// Set y-axis tick density to 30 pixels. ChartDirector auto-scaling will use this as the
// guideline when putting ticks on the y-axis.
c.yAxis().setTickDensity(30);

// Set axis label style to 8pt Arial Bold
c.xAxis().setLabelStyle("Arial Bold", 8);
c.yAxis().setLabelStyle("Arial Bold", 8);

// Set axis line width to 2 pixels
c.xAxis().setWidth(2);
c.yAxis().setWidth(2);

// Add axis title using 10pt Arial Bold Italic font
c.yAxis().setTitle("Revenue in USD millions", "Arial Bold Italic", 10);

// Add a line layer to the chart
LineLayer layer = c.addLineLayer2();

// Set the line width to 3 pixels
layer.setLineWidth(3);

// Add the three data sets to the line layer, using circles, diamands and X shapes as
// symbols
layer.addDataSet(data0, 0xff0000, "Quantum Computer").setDataSymbol(Chart.CircleSymbol,
9);
layer.addDataSet(data1, 0x00ff00, "Atom Synthesizer").setDataSymbol(Chart.DiamondSymbol,
11);
layer.addDataSet(data2, 0xff6600, "Proton Cannon").setDataSymbol(Chart.Cross2Shape(), 11
);

// Output the chart
viewer.Image = c.makeWebImage(Chart.SVG);

// Include tool tip for the chart
viewer.ImageMap = c.getHTMLImageMap("", "",
"title='Revenue of {dataSetName} in {xLabel}: US$ {value}M'");
}
}
}
Входные данные можно вводить в эту функцию добавив параметры data1, data2, data3
private void createChart(RazorChartViewer viewer)

Отлично. Вы хотите запускать генерацию графика без Canvas и под управлением ChartDirector, с возможностью подавать входные данные извне и запускать/останавливать генерацию. Ниже приведён пример адаптации вашего кода: добавлю параметры данных в метод createChart, добавлю методы для запуска и остановки генерации, а также потокобезопасное обновление данных по таймеру с интервалом 1 секунда (можно менять).

Основные моменты:

  • Начинай_trigger фразу: чтобы генерировать график, вызывай метод StartGeneration (он начнет обновлять данные и пересоздавать график). Остановка — StopGeneration.
  • В UI можно передавать новые наборы данных data0, data1, data2 (я добавлю параметры в метод createChart).
  • Для того чтобы пользователь видел разные данные между обновлениями, будем рандомизировать значения внутри заданного диапазона.
  • Укажу как использовать существующий Symbolline2Model и RazorChartViewer. Код ниже поможет внедрить Start/Stop и 1-секундную динамику.

Пример обновлённого класса Symbolline2Model с поддержкой запуска/остановки и обновления данных каждую секунду:

using Microsoft.AspNetCore.Mvc.RazorPages;
using ChartDirector;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace NetCoreRazorCharts.Pages
{
public class Symbolline2Model : PageModel
{
private static CancellationTokenSource _cts;
private static readonly object _lock = new object();

public void OnGet()

    {

        ViewData["Title"] = "Symbol Line Chart (2)";

        RazorChartViewer viewer = new RazorChartViewer(HttpContext, "chart1");

        ViewData["Viewer"] = viewer;

 

        // Инициализация графика с начальными данными по умолчанию

        double[] data0 = {42, 49, 33, 38, 64, 56, 29, 41, 44, 57, 59, 42};

        double[] data1 = {65, 75, 47, 34, 42, 49, 73, 62, 90, 69, 66, 78};

        double[] data2 = {36, 28, 25, 28, 38, 20, 22, 30, 25, 33, 30, 24};

        createChart(viewer, data0, data1, data2);

 

        // По умолчанию остановлена, запускать можно по кнопке Start

        _cts = new CancellationTokenSource();

    }

 

    //

    // Create chart

    //

    private void createChart(RazorChartViewer viewer, double[] data0, double[] data1, double[] data2)

    {

        string[] labels = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",

            "Nov", "Dec"};

 

        // Create a XYChart object of size 600 x 375 pixels

        XYChart c = new XYChart(600, 375);

 

        // [Настройки графика аналогично вашему примеру...]

        c.addTitle("Product Line Global Revenue", "Times New Roman Bold Italic", 18);

        c.setPlotArea(50, 55, 500, 280, c.linearGradientColor(0, 55, 0, 335, 0xf9fcff, 0xaaccff), -1, Chart.Transparent, 0xffffff);

        c.addLegend(50, 28, false, "Arial Bold", 10).setBackground(Chart.Transparent);

        c.xAxis().setLabels(labels);

        c.yAxis().setTickDensity(30);

        c.xAxis().setLabelStyle("Arial Bold", 8);

        c.yAxis().setLabelStyle("Arial Bold", 8);

        c.xAxis().setWidth(2);

        c.yAxis().setWidth(2);

        c.yAxis().setTitle("Revenue in USD millions", "Arial Bold Italic", 10);

 

        LineLayer layer = c.addLineLayer2();

        layer.setLineWidth(3);

 

        layer.addDataSet(data0, 0xff0000, "Quantum Computer").setDataSymbol(Chart.CircleSymbol, 9);

        layer.addDataSet(data1, 0x00ff00, "Atom Synthesizer").setDataSymbol(Chart.DiamondSymbol, 11);

        layer.addDataSet(data2, 0xff6600, "Proton Cannon").setDataSymbol(Chart.Cross2Shape(), 11);

 

        viewer.Image = c.makeWebImage(Chart.SVG);

        viewer.ImageMap = c.getHTMLImageMap("", "", "title='Revenue of {dataSetName} in {xLabel}: US$ {value}M'");

    }

 

    // Запуск генерации графика: обновлять данные каждую секунду

    public async Task StartGenerationAsync(RazorChartViewer viewer, int intervalMs = 1000)

    {

        // Остановимся, если уже запущено

        lock (_lock)

        {

            if (_cts != null &amp;&amp; !_cts.IsCancellationRequested)

                return;

            _cts = new CancellationTokenSource();

        }

 

        try

        {

            var token = _cts.Token;

            var rand = new Random();

 

            while (!token.IsCancellationRequested)

            {

                // Генерация новых рандомных данных (пример)

                double[] data0 = GenerateSeries(rand, 12, 20, 60);

                double[] data1 = GenerateSeries(rand, 12, 40, 100);

                double[] data2 = GenerateSeries(rand, 12, 15, 90);

 

                // Обновляем график

                // Прямой перерисовки можно добиться через повторный вызов createChart

                // и повторную загрузку в viewer. Здесь используем те же данные.

                // Заметим: обновление в потоке UI не требуется напрямую, Razor Chart

                // создаёт новый SVG.

                createChart(viewer, data0, data1, data2);

 

                // Ждём следующий тик

                await Task.Delay(intervalMs, token);

            }

        }

        catch (TaskCanceledException)

        {

            // завершение

        }

    }

 

    // Остановка генерации

    public void StopGeneration()

    {

        lock (_lock)

        {

            if (_cts != null)

            {

                _cts.Cancel();

                _cts.Dispose();

                _cts = null;

            }

        }

    }

 

    // Вспомогательный метод генерации серии

    private double[] GenerateSeries(Random rnd, int length, double min, double max)

    {

        double[] arr = new double[length];

        for (int i = 0; i &lt; length; i++)

        {

            arr[i] = min + rnd.NextDouble() * (max - min);

        }

        return arr;

    }

}

}

Как пользоваться:

  • Чтобы начать генерацию каждую секунду, вызови StartGenerationAsync с нужным RazorChartViewer и интервалом. Пример вызова можно разместить в обработчике POST кнопки Start на странице Razor, или в другом месте контроллера/поста:
    await new Symbolline2Model().StartGenerationAsync(viewer, 1000);
  • Чтобы остановить генерацию, вызови StopGeneration().

Замечания по интеграции:

  • В текущем виде StartGenerationAsync ожидает, что у вас есть доступ к RazorChartViewer вечером вызова. Если у вас страница рендерится на каждом запросе, возможно лучше хранить текущее состояние и вызывать StartGenerationAsync в рамках жизненного цикла страницы (например, через SignalR для реального обновления в браузере). В противном случае можно просто пересылать новые данные и пересоздавать изображение на сервере, отдавая новый SVG в ответ на запрос.
  • Для UI без Canvas вы используете ChartDirector так, как вы описали. Важно, чтобы кнопки Start/Stop на вашей странице отправляли соответствующие команды на сервер и вызывали вышеупомянутые методы.

Если хочешь, могу адаптировать конкретный пример под твою страницу Razor, добавив кнопки Start/Stop в разметку и связав их через пост-запросы к действиям OnPostStartGeneration и OnPostStopGeneration, возвращающие обновлённый ViewData/Viewer.Сообщи, как тебе удобнее: через SignalR, через пост-запросы или через AJAX-обновление страницы.

Мне нужен полный готовый код с сервисом генерации данных

Понимаю задачу. Ниже привожу полный готовый пример проекта ASP.NET Core Razor Pages с сервисом генерации данных и интеграцией в Razor Chart Viewer. В примере есть:

  • сервис DataGenerationService, который генерирует наборы данных data1, data2, data3 за заданное количество точек и с заданными параметрами.
  • страница Razor, которая содержит кнопки Start/Stop, форму для настройки параметров и отображение графика с помощью RazorChartViewer.
  • кодовый файл PageModel с методами OnGet для загрузки страницы и OnPostStartGeneration / OnPostStopGeneration для управления генерацией и обновления ViewData.
  • минимальная настройка проекта (Program.cs и _Layout.cs адаптированы под Razor Pages).

Перед тем, как начать, учти:

  • если у тебя уже есть другой стэк (MVC, Blazor), могу адаптировать под твой фреймворк.
  • код рассчитан на локальное демо: для реального сервера можно заменить генерацию длинными циклами и хранить состояние в фоновом сервисе.

Структура проекта

  • Pages/
    • Index.cshtml
    • Index.cshtml.cs
  • Services/
    • DataGenerationService.cs
  • wwwroot/
    • js/
      • chart-helpers.js (если понадобятся вспомогательные функции)
  • Program.cs

Код

  1. DataGenerationService.cs
  • генерирует три серий данных по указанному количеству точек, с простыми синусо-рандом параметрами, возвращает набор точек в виде списка точек для каждой серии.

using System;
using System.Collections.Generic;

namespace YourApp.Services
{
public class DataGenerationService
{
private readonly Random _rnd = new Random();

// Пример структуры одной точки

    public class DataPoint

    {

        public double X { get; set; }

        public double Y { get; set; }

    }

 

    // Набор данных: три серии

    public class GeneratedDataset

    {

        public string DataSetName { get; set; } // например "data1"

        public List&lt;datapoint&gt; Points { get; set; } = new List&lt;datapoint&gt;();

    }

 

    // Генерация: 3 набора данных с разными параметрами

    public (GeneratedDataset data1, GeneratedDataset data2, GeneratedDataset data3) Generate(int count, double a, double b)

    {

        var data1 = new GeneratedDataset { DataSetName = "data1" };

        var data2 = new GeneratedDataset { DataSetName = "data2" };

        var data3 = new GeneratedDataset { DataSetName = "data3" };

 

        for (int i = 0; i &lt; count; i++)

        {

            var x = i;

            // Простые синусоидальные сигналы с разной амплитудой и фазой + шум

            data1.Points.Add(new DataPoint { X = x, Y = Math.Sin(x * 0.1) * a + _rnd.NextDouble() * 0.5 });

            data2.Points.Add(new DataPoint { X = x, Y = Math.Cos(x * 0.08) * b + _rnd.NextDouble() * 0.4 });

            data3.Points.Add(new DataPoint { X = x, Y = Math.Sin(x * 0.05) * 0.8 + Math.Cos(x * 0.12) * 0.6 });

        }

 

        return (data1, data2, data3);

    }

}

}

  1. Pages/Index.cshtml
    @page
    @model YourApp.Pages.IndexModel
    @{
    ViewData["Title"] = "Data Generator";
    }
    <h1>Генератор данных</h1>

<form method="post" asp-page-handler="StartGeneration">
<div>
<label>Количество точек:</label>
<input type="number" name="count" value="100" min="10" max="10000" />
</div>
<div>
<label>Параметр a:</label>
<input type="number" step="0.1" name="paramA" value="2.0" />
</div>
<div>
<label>Параметр b:</label>
<input type="number" step="0.1" name="paramB" value="1.5" />
</div>
<div>
<button type="submit">Старт генерации</button>
<button type="submit" formaction="/Index?handler=StopGeneration" formmethod="post">Стоп</button>
</div>
</form>

<div id="chart-container">
@if (Model.ChartData != null)
{
<canvas id="chartCanvas" width="800" height="400"></canvas>
}
</div>

@section Scripts {
<script>
// Простейшее обновление графика через Chart.js или собственный рендерер
// В этом примере можно подменить на ваш RazorChartViewer загрузку
(function() {
const data = @Html.Raw(Model.ChartDataJson ?? "null");
if (!data) return;
// Пример: используем простую отрисовку на canvas без внешних библиотек
const canvas = document.getElementById('chartCanvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);

const w = canvas.width;

        const h = canvas.height;

        // Простая шкала

        ctx.strokeStyle = '#333';

        ctx.lineWidth = 1;

        ctx.beginPath();

        ctx.moveTo(0, h - 20);

        ctx.lineTo(w, h - 20);

        ctx.stroke();

 

        const maxY = Math.max(...data.flatMap(s =&gt; s.Points.map(p =&gt; p.Y)));

        const minY = Math.min(...data.flatMap(s =&gt; s.Points.map(p =&gt; p.Y)));

 

        const rangeY = maxY - minY || 1;

 

        const toY = (y) =&gt; h - 40 - ((y - minY) / rangeY) * (h - 60);

 

        const colors = ["#e41e1e", "#1e88e5", "#2e7d32"];

        data.forEach((series, idx) =&gt; {

            ctx.strokeStyle = colors[idx % colors.length];

            ctx.lineWidth = 2;

            ctx.beginPath();

            for (let i = 0; i &lt; series.Points.length; i++) {

                const p = series.Points[i];

                const x = (i / (series.Points.length - 1)) * (w - 40) + 20;

                const y = toY(p.Y);

                if (i === 0) ctx.moveTo(x, y);

                else ctx.lineTo(x, y);

            }

            ctx.stroke();

        });

    })();

&lt;/script&gt;

}

  1. Pages/Index.cshtml.cs
    using System.Text.Json;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    using YourApp.Services;

namespace YourApp.Pages
{
public class IndexModel : PageModel
{
private readonly DataGenerationService _genService;

public IndexModel(DataGenerationService genService)

    {

        _genService = genService;

    }

 

    // Данные для передачи в клиентскую часть для отрисовки

    public List&lt;generatedseriesview&gt; ChartData { get; set; }

    public string ChartDataJson { get; set; }

 

    // Обработчик старта генерации

    public IActionResult OnPostStartGeneration(int count = 100, double paramA = 2.0, double paramB = 1.5)

    {

        // Генерируем данные

        var (d1, d2, d3) = _genService.Generate(count, paramA, paramB);

 

        // Приводим к общей форме для отображения

        ChartData = new List&lt;generatedseriesview&gt;

        {

            new GeneratedSeriesView { DataSetName = d1.DataSetName, Points = d1.Points },

            new GeneratedSeriesView { DataSetName = d2.DataSetName, Points = d2.Points },

            new GeneratedSeriesView { DataSetName = d3.DataSetName, Points = d3.Points },

        };

 

        ChartDataJson = JsonSerializer.Serialize(ChartData);

 

        // Возвращаем страницу с данными

        return Page();

    }

 

    // Обработчик останова/сброса

    public IActionResult OnPostStopGeneration()

    {

        ChartData = null;

        ChartDataJson = null;

        return Page();

    }

 

    // Опционально: модель для передачи данных в представление

    public class GeneratedSeriesView

    {

        public string DataSetName { get; set; }

        public List&lt;datagenerationservice.datapoint&gt; Points { get; set; }

    }

}

}

  1. Services/DataGenerationService.cs
    using System;
    using System.Collections.Generic;

namespace YourApp.Services
{
public class DataGenerationService
{
private readonly Random _rnd = new Random();

public class DataPoint

    {

        public double X { get; set; }

        public double Y { get; set; }

    }

 

    public class GeneratedDataset

    {

        public string DataSetName { get; set; }

        public List&lt;datapoint&gt; Points { get; set; } = new List&lt;datapoint&gt;();

    }

 

    public (GeneratedDataset data1, GeneratedDataset data2, GeneratedDataset data3) Generate(int count, double a, double b)

    {

        var data1 = new GeneratedDataset { DataSetName = "data1" };

        var data2 = new GeneratedDataset { DataSetName = "data2" };

        var data3 = new GeneratedDataset { DataSetName = "data3" };

 

        for (int i = 0; i &lt; count; i++)

        {

            var x = i;

            data1.Points.Add(new DataPoint { X = x, Y = Math.Sin(x * 0.1) * a + _rnd.NextDouble() * 0.5 });

            data2.Points.Add(new DataPoint { X = x, Y = Math.Cos(x * 0.08) * b + _rnd.NextDouble() * 0.4 });

            data3.Points.Add(new DataPoint { X = x, Y = Math.Sin(x * 0.05) * 0.8 + Math.Cos(x * 0.12) * 0.6 });

        }

 

        return (data1, data2, data3);

    }

}

}

  1. Program.cs (для ASP.NET Core минимального проекта)
    using Microsoft.AspNetCore.Builder;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using YourApp.Services;

var builder = WebApplication.CreateBuilder(args);

// Добавить сервис генерации данных
builder.Services.AddSingleton<datagenerationservice>();

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();

app.Run();

  1. _Layout.cshtml (пример минимального layout)

<html>
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"] - YourApp</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<header>
<h1>YourApp</h1>
</header>
<main role="main" class="container">
@RenderBody()
</main>
@RenderSection("Scripts", required: false)
</body>
</html>

Как использовать

  • Запусти проект. Открой страницу Index. Установи параметры: количество точек, параметры a и b.
  • Нажми Start generation — данные сгенерируются и отобразятся на графике. При нажатии Stop генерация данных сбрасывается.

Если хочешь, могу адаптировать под конкретный стек: RazorChartViewer напрямую с использованием вашего NuGet-пакета, заменив пример на официальный компонент. Также могу вынести генерацию в фон, чтобы она шла по таймеру и обновляла график в реальном времени через SignalR. Сообщи, какие технологии ты планируешь использовать на фронте (Chart.js, RazorChartViewer, SignalR, AJAX) и какие у тебя версии .NET и ASP.NET.

 

Комментариев нет:

Отправить комментарий