Asp.NEtCore RazorPage, WebApi,
MVC + Rabbit
PHIND-70B MODEL
Let's explore how to architect
a modern ASP.NET Core application combining Razor Pages, WebAPI, MVC, and
RabbitMQ messaging. This architecture allows you to leverage the strengths of
each component while maintaining clean separation of concerns.
Architecture Overview
The proposed architecture
integrates multiple ASP.NET Core frameworks with RabbitMQ for reliable message
handling. Each component serves a specific purpose:
- Razor Pages handles straightforward CRUD
operations and forms
- WebAPI provides REST endpoints for
external services
- MVC manages complex UI workflows
- RabbitMQ enables asynchronous
communication and background processing
The diagram above illustrates
the complete system architecture where:
- Blue components represent frontend
interfaces (Razor Pages and MVC)
- Green components show core business logic
(WebAPI and models)
- Purple indicates RabbitMQ-related
components
- Orange represents data storage
The RabbitMQ components work together
as follows:
1. The Message Producer component
sends messages from WebAPI to RabbitMQ queues
2. Messages are stored temporarily
in RabbitMQ until processed
3. The Message Consumer component
retrieves messages and updates domain models
Implementation Details
Project Structure
src/
├── WebApp/
│ ├── Areas/
│ │
└── RazorPages/
│ │
└── Pages/
│ ├── Controllers/
│ │
├── Api/
│ │
└── Mvc/
│ ├── Models/
│ └── Services/
└── Tests/
Core Components Implementation
1
RabbitMQ Integration
public static class RabbitMqConfig
{
public static void AddRabbitMq(this IServiceCollection services, IConfiguration
configuration)
{
var
rabbitMqSettings =
configuration.GetSection("RabbitMq").Get<RabbitMqSettings>();
services.AddSingleton<IConnection>(sp =>
{
var factory = new ConnectionFactory()
{
HostName =
rabbitMqSettings.Hostname,
Port = rabbitMqSettings.Port,
UserName =
rabbitMqSettings.Username,
Password =
rabbitMqSettings.Password
};
return factory.CreateConnection();
});
}
}
public class RabbitMqSettings
{
public string Hostname { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
2
Message Producer Service
public interface IMessageProducer
{
Task PublishAsync<T>(string exchange, string routingKey,
T message);
}
public class MessageProducer : IMessageProducer
{
private readonly IConnection
_connection;
public MessageProducer(IConnection connection)
{
_connection = connection;
}
public async Task PublishAsync<T>(string exchange, string routingKey,
T message)
{
using var channel =
_connection.CreateModel();
var body =
Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
var properties = new BasicProperties
{
Persistence = true,
Type = "text/json"
};
await channel.BasicPublishAsync(exchange:
exchange,
routingKey: routingKey,
body:
body,
basicProperties: properties);
}
}
3
Message Consumer Service
public interface IMessageConsumer
{
void StartConsuming<T>(string queueName, Func<T, Task>
onMessageReceived);
}
public class MessageConsumer : IMessageConsumer
{
private readonly IConnection
_connection;
private IModel _channel;
public MessageConsumer(IConnection connection)
{
_connection = connection;
_channel = connection.CreateModel();
}
public void StartConsuming<T>(string queueName, Func<T, Task>
onMessageReceived)
{
_channel.QueueDeclare(queue:
queueName,
durable: true,
exclusive: false,
autoDelete: false);
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += async (model, ea)
=>
{
var body = ea.Body.ToArray();
var message =
JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(body));
await onMessageReceived(message);
_channel.BasicAck(ea.DeliveryTag,
false);
};
_channel.BasicConsume(queue:
queueName,
autoAck: false,
consumer:
consumer);
}
}
Usage Example
// In your controller
public class OrderController : ControllerBase
{
private readonly IMessageProducer _producer;
public OrderController(IMessageProducer producer)
{
_producer = producer;
}
[HttpPost]
public async Task<IActionResult> PlaceOrder(Order order)
{
// Process
order synchronously
await _producer.PublishAsync("orders_exchange",
"order.created",
order);
return Accepted();
}
}
// In your
Razor Page
public class OrderModel : PageModel
{
private readonly IMessageProducer _producer;
[BindProperty]
public Order Order { get; set; }
public OrderModel(IMessageProducer producer)
{
_producer = producer;
}
public async Task<IActionResult> OnPostAsync()
{
await _producer.PublishAsync("orders_exchange",
"order.created",
Order);
return RedirectToPage("./Index");
}
}
Best Practices
1
Message Handling
- Implement retry policies for failed
messages
- Use dead-letter queues for unprocessable
messages
- Consider implementing message
acknowledgment patterns
2
Error Handling
- Log all message processing failures
- Implement circuit breakers for RabbitMQ
connections
- Monitor queue lengths and consumer lag
3
Performance Optimization
- Batch messages when possible
- Use connection pooling for RabbitMQ
connections
- Implement proper prefetch settings for
consumers
|
|
Sources
1Integrate ASP.NET Core Razor components with MVC or Razor Pages
Razor
components can be integrated into Razor Pages or MVC a...
learn.microsoft.com
2Razor pages and webapi in the same project - Stack Overflow
I created a
web app (razor pages) in .net core 3.0. Then I a...
stackoverflow.com
3Getting Started with RabbitMQ in ASP.NET
In this
example we'd like to make our user creation a backgr...
aspnetmonsters.com
4I left the Web Form and switched to Razor Pages. | by Runaho | CodeX
In this
article, I will talk about my thoughts about .NET Co...
medium.com
+1 more
Переведи
все это на рууский
PHIND-70B MODEL
Давайте рассмотрим архитектуру современного
веб-приложения ASP.NET Core,
объединяющую Razor Pages, WebAPI, MVC и RabbitMQ для
обработки сообщений. Такая архитектура позволяет использовать преимущества
каждого компонента при сохранении чистого разделения ответственности.
Обзор
Архитектуры
Предлагаемая архитектура интегрирует
несколько фреймворков ASP.NET Core с
использованием RabbitMQ для надежной обработки сообщений. Каждый компонент выполняет
определенную задачу:
- Razor Pages
обрабатывает простые операции CRUD и
формы
- WebAPI
предоставляет REST-конечные точки для внешних сервисов
- MVC управляет сложными
рабочими процессами интерфейса
- RabbitMQ
обеспечивает асинхронное взаимодействие и фоновой обработку данных
На диаграмме выше показана общая структура
системы, где:
- Синие
компоненты представляют собой фронтенд (страницы Razor и
представления MVC)
- Зеленые
компоненты отображают бизнес-логику (WebAPI и
модели)
- Фиолетовые компоненты связаны с RabbitMQ
- Оранжевые компоненты представляют
хранилище данных
Компоненты RabbitMQ работают
следующим образом:
1.
Производитель
сообщений отправляет данные из WebAPI в очереди RabbitMQ
2.
Сообщения
временно хранятся в RabbitMQ до обработки
3.
Потребитель
сообщений получает их и обновляет модели домена
Детали
Реализации
Структура Проекта
src/
├── WebApp/
│ ├── Areas/
│ │ └── RazorPages/
│ │ └── Pages/
│ ├── Controllers/
│ │ ├── Api/
│ │ └── Mvc/
│ ├── Models/
│ └── Services/
└── Tests/
Основные Компоненты
1
Интеграция
RabbitMQ
public static class RabbitMqConfig
{
public static void AddRabbitMq(this IServiceCollection services, IConfiguration
configuration)
{
var
rabbitMqSettings =
configuration.GetSection("RabbitMq").Get<RabbitMqSettings>();
services.AddSingleton<IConnection>(sp =>
{
var factory = new ConnectionFactory()
{
HostName =
rabbitMqSettings.Hostname,
Port =
rabbitMqSettings.Port,
UserName = rabbitMqSettings.Username,
Password =
rabbitMqSettings.Password
};
return factory.CreateConnection();
});
}
}
public class RabbitMqSettings
{
public string Hostname { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
2
Сервис Производителя Сообщений
public interface IMessageProducer
{
Task PublishAsync<T>(string exchange, string routingKey,
T message);
}
public class MessageProducer : IMessageProducer
{
private readonly IConnection
_connection;
public MessageProducer(IConnection connection)
{
_connection = connection;
}
public async Task PublishAsync<T>(string exchange, string routingKey,
T message)
{
using var channel =
_connection.CreateModel();
var body =
Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
var properties = new BasicProperties
{
Persistence = true,
Type = "text/json"
};
await channel.BasicPublishAsync(exchange:
exchange,
routingKey: routingKey,
body:
body,
basicProperties: properties);
}
}
3
Сервис Потребителя Сообщений
public interface IMessageConsumer
{
void StartConsuming<T>(string queueName, Func<T, Task> onMessageReceived);
}
public class MessageConsumer : IMessageConsumer
{
private readonly IConnection
_connection;
private IModel _channel;
public MessageConsumer(IConnection connection)
{
_connection = connection;
_channel = connection.CreateModel();
}
public void StartConsuming<T>(string queueName, Func<T, Task>
onMessageReceived)
{
_channel.QueueDeclare(queue:
queueName,
durable: true,
exclusive: false,
autoDelete: false);
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += async (model, ea)
=>
{
var body = ea.Body.ToArray();
var message =
JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(body));
await onMessageReceived(message);
_channel.BasicAck(ea.DeliveryTag,
false);
};
_channel.BasicConsume(queue:
queueName,
autoAck: false,
consumer: consumer);
}
}
Пример Использования
// В вашем контроллере
public class OrderController : ControllerBase
{
private readonly IMessageProducer _producer;
public OrderController(IMessageProducer producer)
{
_producer = producer;
}
[HttpPost]
public async Task<IActionResult> PlaceOrder(Order order)
{
// Обработка
заказа синхронно
await _producer.PublishAsync("orders_exchange",
"order.created",
order);
return Accepted();
}
}
// В вашей странице
Razor
public class OrderModel : PageModel
{
private readonly IMessageProducer _producer;
[BindProperty]
public Order Order { get; set; }
public OrderModel(IMessageProducer producer)
{
_producer = producer;
}
public async Task<IActionResult> OnPostAsync()
{
await _producer.PublishAsync("orders_exchange",
"order.created",
Order);
return RedirectToPage("./Index");
}
}
Лучшие Практики
1
Обработка Сообщений
- Реализуйте
политику повторных попыток для неудачных сообщений
- Используйте
очередь мертвых писем для непроцессуемых сообщений
- Рассмотрите реализацию паттернов
подтверждения сообщений
2
Обработка Ошибок
- Логируйте все сбойные обработки сообщений
- Внедрите
предохранители для подключений к RabbitMQ
- Отслеживайте
длину очередей и задержку потребителей
3
Оптимизация Производительности
- Группируйте сообщения при возможности
- Используйте пул соединений с RabbitMQ
- Настройте
правильные параметры предварительной выборки для потребителей
|
|
Задача вот
какая. Есть Asp.Net Приложение Feeder - сервис котировок, который - генерит
котировки ( DateTime DT, Tciker string, Bid double, Ask double, Last double, VolumeInLast double) -
передает котировки остальным Asp.Net
Приложениям. - другие Asp.Net слушают шину и принмают эти котировки Все
работает в реальном времени. Для того чтобы контролировать правильную работу
сервиса Котировок необходимо передавать котировки не только все Потребителям,
но также и себе самому и визуализировать эти котировки на гафике. Мы можем
замкнуть этот цикл на уровне AspNet Core -
контроллеров - Первый вариант. Второй вариант мы можем закольцевать на уровне Rabbit, то есть подписаться на свои же котировки.
Как должна выглядеть архитектура такого Приложения ? 1. Asp.NetCore Feeder ->
Сервис -> котировки -> контроллер Asp.NetCore Feeder -> RazorPage визуализация 2. Asp.NetCore Feeder ->
Сервис -> котировки -> Rabbit -> контроллер Asp.NetCore Feeder -> RazorPage визуализация ----------------- Какая
должна быть архитектура Asp.NetCore Приложения - в Пером случае - без Rabbit - во втором случае с Rabbit -----------------------------------
Предложи варинаты архитектурно. Кода пока не нужно. Если есть вопросы - Задавай
PHIND-70B MODEL
Давайте рассмотрим архитектурные варианты для
сервиса котировок с возможностью визуализации данных.
Вариант 1:
Архитектура без RabbitMQ
В этом варианте все компоненты находятся
внутри одного ASP.NET Core
приложения, что обеспечивает простоту реализации и минимальную задержку при
передаче данных.
В этой архитектуре:
- Генератор
котировок (GC) создает новые данные в реальном времени
- Сервис
котировок (CS) обрабатывает и хранит данные
- Контроллер
котировок (CC) управляет передачей данных между сервисом и
фронтендом
- Кэш
используется для оптимизации производительности при частых обновлениях
- Razor Pages
обеспечивает визуализацию данных в реальном времени
Преимущества этого подхода:
- Минимальная задержка при передаче данных
- Простота реализации и поддержки
- Низкие требования к инфраструктуре
- Легко
масштабируется в рамках одного сервера
Вариант 2:
Архитектура с RabbitMQ
Этот вариант использует RabbitMQ как шину
обмена сообщениями, что обеспечивает надежную асинхронную передачу данных и
масштабируемость.
В этой архитектуре:
- Генератор
котировок отправляет данные через производителя сообщений в RabbitMQ
- RabbitMQ
обеспечивает надежную доставку данных всем подписчикам
- Потребитель
сообщений получает данные из RabbitMQ и
передает их в сервис котировок
- Кэш
используется для оптимизации производительности при частых обновлениях
- Razor Pages
получает данные через контроллер котировок
Преимущества этого подхода:
- Высокая надежность доставки данных
- Возможность масштабирования по
горизонтали
- Гарантированная обработка всех сообщений
- Возможность
добавления новых потребителей без изменения существующей архитектуры
Сравнение вариантов
Характеристика |
Вариант 1 (без RabbitMQ) |
Вариант 2 (с RabbitMQ) |
Сложность
реализации |
Простая |
Средняя |
Задержка
доставки |
Минимальная |
Немного
выше |
Масштабируемость |
Ограничена
одним сервером |
Высокая |
Надежность |
Зависит
от приложения |
Высокая |
Требования
к инфраструктуре |
Минимальные |
Требуется
сервер RabbitMQ |
Обработка
ошибок |
Простая |
Профессиональная |
Рекомендации
Для вашего случая рекомендуется использовать
Вариант 2 (с RabbitMQ), потому что:
1.
Обеспечивает
надежную доставку котировок всем потребителям, включая визуализацию
|
|
Sources
1Building a Robust Chat Application with .NET Core 8, SignalR, and ...
The
ChatRoomWithBot project is a browser-based chat applicat...
medium.com
2Building Microservices On .NET Core – Part 6 Real time server client ...
In this
article we are going to show you how you can combine...
altkomsoftware.com
3Event-Driven Microservices with .NET Core and RabbitMQ - Medium
An effective
way to manage communication between these micro...
medium.com
Первый
вариант неправильно понят В этом варианте Котировки отправляются Потребителям
(например через SignalR) и самому
себе. В этом варианте котировки тоже пересекают границы Сервиса, но
возвращаются обратно без Rabit. В этом случае сервис работает и как Pub и Sub, и визуализирует полученные котировки на Графике. То есть работает также
как другие внешние Клиенты. Внешние Клиенты получают котировки через SignalR и отбражают их на своем Графике. Тоже
самое делает и PubSuber - рисует
свою работу, которую он отправили себе.
PHIND-70B MODEL
Вы абсолютно правы. Давайте рассмотрим
правильную архитектуру первого варианта, где сервис работает как паблишер и
одновременно как подписчик своих же котировок.
В этой архитектуре:
- SignalR Hub
работает как центральный узел для распределения котировок
- Сервис
котировок одновременно является паблишером (отправляет котировки) и
подписчиком (получает их обратно)
- Кэш
используется для оптимизации производительности при частых обновлениях
- Razor Pages получает
данные через контроллер котировок
Рассмотрим, как работает
система:
1. Генерация и отправка котировок:
·
Генератор котировок создает новые данные
·
Сервис котировок получает эти данные
·
Сервис
отправляет котировки через SignalR Hub всем
подписчикам
2. Внутреннее потребление:
·
Тот же
сервис котировок подписывается на свои собственные котировки
·
Полученные котировки сохраняются в кэш
·
Контроллер
котировок получает данные из кэша
·
Razor Pages визуализирует котировки
3. Работа с внешними клиентами:
·
Внешние
клиенты подключаются к тому же SignalR Hub
·
Они
получают те же самые котировки
·
Визуализация происходит на стороне клиента
Таким образом, сервис работает как единое
целое, где:
- Отправка
и получение котировок происходит через один и тот же SignalR Hub
- Внутренняя
визуализация использует те же данные, что и внешние клиенты
- Архитектура остается простой и понятной
- Нет
необходимости в дополнительных компонентах типа RabbitMQ
Этот подход обеспечивает:
- Единообразие обработки данных
- Простоту поддержки и отладки
- Высокую производительность
- Надежную доставку котировок всем
подписчикам
|
|
Как
происходит двустороннее движение данных между сервисом котировок и другими
компонентами системы?
PHIND-70B MODEL
Давайте рассмотрим, как происходит
двустороннее движение данных в системе котировок. Для этого сначала
визуализируем потоки данных:
На диаграмме показаны все ключевые компоненты
системы и их взаимодействие:
- Генератор котировок создает новые данные
- Сервис
котировок обрабатывает и распределяет данные
- SignalR Hub
обеспечивает двустороннее распространение данных
- Кэш оптимизирует доступ к данным
- Контроллер
котировок управляет передачей данных на визуализацию
- Razor Pages отображает
данные в реальном времени
- Внешние
клиенты получают те же данные через SignalR
Рассмотрим подробно каждый поток данных:
1. Отправка котировок:
·
Генератор котировок создает новую котировку
·
Сервис
котировок получает данные и обрабатывает их
·
Через SignalR Hub данные
отправляются всем подписчикам
·
Внешние
клиенты получают данные и отображают их на своих графиках
2. Возврат данных в сервис:
·
SignalR Hub
возвращает те же данные обратно в сервис котировок
·
Данные
сохраняются в кэш для оптимизации доступа
·
Контроллер
котировок получает данные из кэша
·
Razor Pages получает
данные для визуализации
3. Обновление данных:
·
Razor Pages запрашивает обновление данных
·
Контроллер
котировок запрашивает данные из кэша
·
Кэш возвращает актуальные данные
·
График на Razor Pages обновляется
Такая архитектура обеспечивает:
- Единообразное распространение данных всем
подписчикам
- Оптимизированный
доступ к данным через кэш
- Надежную
доставку данных всем компонентам системы
- Возможность масштабирования системы
- Простоту поддержки и отладки
Важно отметить, что сервис котировок работает
как единое целое, где отправка и получение данных происходит через один и тот
же SignalR Hub, что
обеспечивает согласованность данных во всей системе.
Комментариев нет:
Отправить комментарий