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) и данные отрисовываются на странице Real‑time или близко к этому, лучше выбрать 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, если нужна живой перегруз данных. В вашем примере
приведен статичный пример, так что можно просто обновлять данные при
каждом запросе.
Что
нужно реализовать пошагово
- Определить
модель данных для CircularBuffer
- Создать обобщенный CircularBuffer<t> либо использовать готовый реализационный простой массив с
индексацией.
- В этом буфере хранить, например, массив значений double[] и, возможно, метки времени.
- Реализовать
сервис состояния для графика
- Интерфейс IChartDataStore с методом GetLatestData() и событием обновления (если хотите push-уведомления).
- Реализация ChartDataStore хранит CircularBuffer<double> и обеспечивает потокобезопасный доступ (lock или ConcurrentQueue/SemaphoreSlim).
- Настройка
MassTransit и потребителей
- В асинхронном потребителе сообщений из очереди
получайте данные и кладите их в CircularBuffer внутри ChartDataStore.
- Можно сделать простой DTO-сообщение, например, ChartPoint { double Value; DateTime Timestamp; }.
- Реализация фона
- Реализуйте два
варианта:
- IHostedService, где внутри вы постоянно читаете сообщения из
очереди и обновляете ChartDataStore.
- Или наследуйте BackgroundService (который упрощает реализацию фона с ExecuteAsync) и используйте IBus или IAsyncEnumerable из MassTransit.
- При выборе используйте один компонент: BackgroundService упрощает цикл, но IHostedService тоже подходит.
- Встраивание в
Razor Page
- Внедрите
IChartDataStore в PageModel.
- В OnGet получите текущие данные из хранилища и передайте в ViewData/Model так, чтобы chart-диаграмма отрисовалась тем же способом, что в вашем
примере.
- Если хотите живой обновляющий график без
перезагрузки страницы, можно добавить SignalR-хаб и отправлять новые данные клиенту.
Пример
упрощенного кода
- 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<double>(capacity);
}
public void AddPoint(double
value)
{
_buffer.Add(value);
}
public double[] GetLatestData()
{
return _buffer.ToArray();
}
}
- Фоновый сервис с
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; }
}
- 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'");
}
}
- Регистрация в 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.
Как
реализовать по шагам
- Простой сервис
генерации данных
- Добавьте сервис в ASP.NET Core, который держит в памяти текущее состояние данных и
метод GenerateNext() для обновления. Например:
- хранить текущие данные в памяти, например, класс DataStore с полями data0, data1, data2 и labels.
- метод GenerateNext(), который модифицирует каждое значение на небольшую случайную
величину, ограничивает диапазон.
- метод GetCurrent() возвращает три массива и подпись.
- Интеграция с
Razor Page
- В PageModel добавьте зависимость на этот сервис (Singleton или Scoped, на ваше усмотрение). В OnGet() можно получить текущие данные и построить график,
как вы делаете сейчас.
- Добавьте кнопку Start/Stop на страницу. При нажатии Start сервис начинает вызывать GenerateNext() по таймеру, а при Stop останавливает таймер.
- Для обновления графика на клиенте без перезагрузки
можно реализовать polling: JavaScript через setInterval делает запрос к endpoint (например, /Chart/Refresh) и возвращает обновленный SVG/URL-данные. В этом случае вам нужно endpoint, который возвращает обновленный график в виде SVG или обновляет изображение и image map.
- Реализация без
MassTransit
- Не обязательно использовать MassTransit на данном этапе. Просто создайте фоновой таймер в .NET:
- В сервисе храните System.Threading.Timer и поле isRunning.
- Start запускает таймер с интервалом 1000–3000 мс,
вызывает GenerateNext() и сохраняет новые данные.
- Stop отключает
таймер.
- Endpoint в Razor Pages или API контроллере, который возвращает текущие данные, или обновляет график.
Вы
можете либо:
- пересоздать RazorChartViewer заново и вернуть SVG, либо
- отдавать обновленный набор данных и строить график
на стороне клиента (не рекомендуется без ChartDirector на клиенте).
- Пример структуры кода (упрощённый набросок)
- 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.
- Пример алгоритма
генерации (псевдкод)
- data0[i] =
clamp(data0[i] + rand(-5, 5), minValue, maxValue)
- так же для
data1, data2
- labels остаются
неизменными
- Как начать с 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.
- Безопасность и
простота
- Для начала можно реализовать локальный таймер в
самом 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 &&
!_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 < 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
Код
- 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<datapoint>
Points { get; set; } = new List<datapoint>();
}
// Генерация: 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 < 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);
}
}
}
- 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
=> s.Points.map(p => p.Y)));
const minY = Math.min(...data.flatMap(s
=> s.Points.map(p => p.Y)));
const rangeY = maxY - minY || 1;
const toY = (y) => h - 40 - ((y
- minY) / rangeY) * (h - 60);
const colors = ["#e41e1e", "#1e88e5",
"#2e7d32"];
data.forEach((series, idx) => {
ctx.strokeStyle = colors[idx %
colors.length];
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i <
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();
});
})();
</script>
}
- 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<generatedseriesview> 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<generatedseriesview>
{
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<datagenerationservice.datapoint> Points { get; set; }
}
}
}
- 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<datapoint>
Points { get; set; } = new List<datapoint>();
}
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 < 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);
}
}
}
- 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();
- _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.
Комментариев нет:
Отправить комментарий