SignalR в ASP.NET Core 2026: Комунікація в Реальному Часі, Хаби та Питання на Співбесідах

Повний посібник з SignalR в ASP.NET Core. Дізнайтеся як реалізувати комунікацію в реальному часі, налаштувати хаби, керувати групами та підготуватися до технічних співбесід.

SignalR в ASP.NET Core - комунікація в реальному часі

SignalR забезпечує двосторонню комунікацію в реальному часі між серверами та клієнтами в застосунках ASP.NET Core. На відміну від традиційної моделі HTTP запит-відповідь, SignalR підтримує постійні з'єднання, що дозволяють серверам миттєво надсилати оновлення клієнтам — це необхідно для чат-застосунків, live-дашбордів, спільного редагування та сповіщень у реальному часі.

Механізм Резервного Транспорту SignalR

SignalR автоматично обирає найкращий доступний транспорт: спочатку WebSockets, потім Server-Sent Events, і нарешті Long Polling. Цей резервний механізм забезпечує сумісність з усіма браузерами та мережевими конфігураціями без будь-яких змін у коді.

Як Хаби SignalR Забезпечують Комунікацію Сервер-Клієнт

Хаб діє як високорівневий конвеєр, що обробляє виклики методів між клієнтами та серверами. Клієнти викликають методи на хабі, а хаб може викликати методи на підключених клієнтах. Ця абстракція усуває складність ручного керування WebSocket-з'єднаннями.

Наступний приклад демонструє базовий чат-хаб, який транслює повідомлення всім підключеним клієнтам:

ChatHub.cscsharp
using Microsoft.AspNetCore.SignalR;

public class ChatHub : Hub
{
    // Викликається коли клієнт надсилає повідомлення
    public async Task SendMessage(string user, string message)
    {
        // Трансляція ВСІМ підключеним клієнтам
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    // Викликається автоматично при підключенні клієнта
    public override async Task OnConnectedAsync()
    {
        await Clients.Caller.SendAsync("Connected", Context.ConnectionId);
        await base.OnConnectedAsync();
    }

    // Викликається автоматично при відключенні клієнта
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        // Очищення ресурсів, сповіщення інших користувачів, тощо
        await base.OnDisconnectedAsync(exception);
    }
}

Властивість Clients надає доступ до всіх підключених клієнтів через різні опції таргетування: All, Caller, Others, Group та User.

Налаштування SignalR в ASP.NET Core 9

Реєстрація SignalR в ASP.NET Core 9 відповідає стандартному патерну middleware. Наступна конфігурація активує протоколи JSON та MessagePack з налаштованими розмірами буферів:

Program.cscsharp
var builder = WebApplication.CreateBuilder(args);

// Додавання сервісів SignalR з конфігурацією
builder.Services.AddSignalR(options =>
{
    options.EnableDetailedErrors = builder.Environment.IsDevelopment();
    options.MaximumReceiveMessageSize = 64 * 1024; // 64 KB
    options.StreamBufferCapacity = 10;
    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
})
.AddJsonProtocol(options =>
{
    options.PayloadSerializerOptions.PropertyNamingPolicy = null; // PascalCase
});

var app = builder.Build();

// Мапінг endpoint хаба
app.MapHub<ChatHub>("/chathub");

app.Run();

Налаштування KeepAliveInterval та ClientTimeoutInterval контролюють моніторинг стану з'єднання. Клієнти, які не відповідають протягом визначеного часу, вважаються відключеними.

Керування Групами для Цільової Доставки Повідомлень

Групи забезпечують механізм організації з'єднань та надсилання повідомлень підмножинам клієнтів. Типовий випадок використання — чат-кімнати, де користувачі приєднуються до конкретних кімнат і отримують повідомлення лише з цих кімнат.

GroupChatHub.cscsharp
public class GroupChatHub : Hub
{
    // Додавання поточного з'єднання до групи
    public async Task JoinRoom(string roomName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
        
        // Сповіщення групи про приєднання
        await Clients.Group(roomName).SendAsync(
            "UserJoined", 
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Видалення поточного з'єднання з групи
    public async Task LeaveRoom(string roomName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
        
        await Clients.Group(roomName).SendAsync(
            "UserLeft",
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Надсилання повідомлення лише конкретній групі
    public async Task SendToRoom(string roomName, string message)
    {
        await Clients.Group(roomName).SendAsync(
            "ReceiveMessage",
            Context.User?.Identity?.Name,
            message
        );
    }
}

Членство в групі прив'язане до з'єднань, а не до користувачів. Коли користувач перепідключається, він повинен знову приєднатися до своїх груп. Для постійного членства в групах слід зберігати асоціації груп у базі даних та автоматично приєднуватися в OnConnectedAsync.

Персистентність Груп

Групи існують лише в пам'яті і не зберігаються між перезапусками сервера або в сценаріях масштабування без backplane. Для production-розгортань з кількома серверами використовуйте Redis або Azure SignalR Service як backplane.

Захист З'єднань SignalR через Автентифікацію

SignalR інтегрується з автентифікацією ASP.NET Core. Атрибут [Authorize] обмежує доступ до хаба лише автентифікованими користувачами, а Context.User надає доступ до claims автентифікованого користувача.

SecureHub.cscsharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;

[Authorize] // Вимагає автентифікації для всього хаба
public class SecureHub : Hub
{
    // Доступ до claims користувача через Context.User
    public async Task SendPrivateMessage(string recipientUserId, string message)
    {
        var senderName = Context.User?.Identity?.Name 
            ?? throw new HubException("User not authenticated");
        
        // Надсилання конкретному користувачу (всі його з'єднання)
        await Clients.User(recipientUserId).SendAsync(
            "PrivateMessage",
            senderName,
            message
        );
    }

    [Authorize(Roles = "Admin")] // Авторизація на основі ролей для методу
    public async Task BroadcastAnnouncement(string announcement)
    {
        await Clients.All.SendAsync("Announcement", announcement);
    }
}

Для WebSocket-з'єднань токени автентифікації повинні передаватися через query string, оскільки WebSockets не підтримують кастомні заголовки. Налаштування JavaScript-клієнта для включення токену:

signalr-client.tstypescript
import * as signalR from "@microsoft/signalr";

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/securehub", {
        accessTokenFactory: () => localStorage.getItem("authToken") || ""
    })
    .withAutomaticReconnect()
    .build();

await connection.start();

На стороні сервера налаштуйте JWT-автентифікацію для читання токену з query string:

Program.cs - Конфігурація JWT для SignalRcsharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // Читання токену з query string для SignalR
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                
                if (!string.IsNullOrEmpty(accessToken) && 
                    path.StartsWithSegments("/securehub"))
                {
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
    });

Готовий до співбесід з .NET?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Масштабування SignalR з Redis Backplane

У середовищі з балансуванням навантаження клієнти можуть підключатися до різних екземплярів серверів. Без backplane повідомлення, надіслані з одного сервера, досягають лише клієнтів, підключених до цього сервера. Redis служить як backplane для синхронізації повідомлень між усіма екземплярами серверів.

Program.cs - Конфігурація Redis backplanecsharp
builder.Services.AddSignalR()
    .AddStackExchangeRedis(options =>
    {
        options.Configuration = builder.Configuration
            .GetConnectionString("Redis");
        
        // Опціонально: налаштування Redis-специфічних опцій
        options.Configuration.ChannelPrefix = 
            RedisChannel.Literal("MyApp_SignalR_");
    });

Redis backplane публікує всі повідомлення SignalR у Redis-канали. Кожен екземпляр сервера підписується на ці канали та доставляє повідомлення своїм локальним клієнтам. Цей підхід додає мінімальну затримку (зазвичай 1-2мс), водночас забезпечуючи горизонтальне масштабування.

Для enterprise-розгортань Azure SignalR Service надає керовану альтернативу, що автоматично обробляє масштабування, керування з'єднаннями та доступність.

Строго Типізовані Хаби для Безпеки на Етапі Компіляції

Строго типізовані хаби замінюють magic strings методами інтерфейсу, забезпечуючи перевірку викликів методів клієнта на етапі компіляції:

INotificationClient.cscsharp
public interface INotificationClient
{
    Task ReceiveNotification(string title, string message, string severity);
    Task UpdateProgress(int percentage);
    Task TaskCompleted(Guid taskId, bool success);
}

// NotificationHub.cs
public class NotificationHub : Hub<INotificationClient>
{
    public async Task NotifyAll(string title, string message)
    {
        // Перевірка на етапі компіляції - без magic strings
        await Clients.All.ReceiveNotification(title, message, "info");
    }

    public async Task ReportProgress(string groupName, int percentage)
    {
        // Автодоповнення IDE працює тут
        await Clients.Group(groupName).UpdateProgress(percentage);
    }
}

Рефакторинг стає безпечнішим, оскільки перейменування методу в інтерфейсі одразу показує всі місця викликів, що потребують оновлення.

Потокова Передача Даних з Каналами SignalR

SignalR підтримує потокову передачу для сценаріїв, де дані генеруються інкрементально, наприклад оновлення прогресу, відстеження логів або дані сенсорів у реальному часі. Підтримується як потокова передача сервер-до-клієнта, так і клієнт-до-сервера.

StreamingHub.cscsharp
public class StreamingHub : Hub
{
    // Потокова передача сервер-до-клієнта з IAsyncEnumerable
    public async IAsyncEnumerable<StockPrice> StreamStockPrices(
        string[] symbols,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        var random = new Random();
        
        while (!cancellationToken.IsCancellationRequested)
        {
            foreach (var symbol in symbols)
            {
                yield return new StockPrice
                {
                    Symbol = symbol,
                    Price = random.NextDouble() * 1000,
                    Timestamp = DateTime.UtcNow
                };
            }
            
            await Task.Delay(1000, cancellationToken);
        }
    }

    // Потокова передача клієнт-до-сервера
    public async Task UploadStream(IAsyncEnumerable<LogEntry> stream)
    {
        await foreach (var entry in stream)
        {
            // Обробка кожного запису логу в міру надходження
            await ProcessLogEntry(entry);
        }
    }
}

public record StockPrice(string Symbol, double Price, DateTime Timestamp);
public record LogEntry(string Level, string Message, DateTime Timestamp);

JavaScript-клієнт споживає потік використовуючи async iterator:

streaming-client.tstypescript
const stream = connection.stream("StreamStockPrices", ["AAPL", "MSFT"]);

stream.subscribe({
    next: (price) => console.log(`${price.symbol}: $${price.price}`),
    error: (err) => console.error(err),
    complete: () => console.log("Stream completed")
});

Поширені Питання про SignalR на Співбесідах

Технічні співбесіди часто охоплюють архітектуру SignalR та реальні сценарії. Наступні питання регулярно зустрічаються під час сесій співбесід .NET:

Порада для Співбесіди

Поясніть процес узгодження транспорту: SignalR спочатку намагається WebSockets, потім Server-Sent Events, а далі Long Polling. Важливо розуміти коли використовується кожен транспорт та його обмеження.

П: Як SignalR обробляє стан з'єднання коли клієнт тимчасово втрачає зв'язок?

SignalR підтримує стан з'єднання на сервері протягом налаштовуваного періоду. З увімкненим автоматичним перепідключенням клієнт намагається перепідключитися використовуючи той самий ID з'єднання. У разі успіху з'єднання відновлюється без втрати членства в групах. Якщо відключення перевищує таймаут, встановлюється нове з'єднання і клієнт повинен знову приєднатися до груп.

П: Яка різниця між Clients.User() та Clients.Client()?

Clients.User(userId) таргетує всі з'єднання, що належать конкретному автентифікованому користувачу — корисно коли один користувач має відкрито кілька вкладок браузера. Clients.Client(connectionId) таргетує одне конкретне з'єднання. Таргетування на основі користувача вимагає автентифікації та використовує IUserIdProvider для мапінгу з'єднань на ID користувачів.

П: Як реалізувати виявлення присутності (показ хто онлайн)?

Відстежуйте з'єднання в OnConnectedAsync та OnDisconnectedAsync. Зберігайте мапінги користувач-до-з'єднання в concurrent dictionary або розподіленому кеші. Для масштабованих розгортань використовуйте спільне сховище як Redis. Транслюйте оновлення присутності відповідним групам коли користувачі підключаються або відключаються.

П: Поясніть призначення backplane SignalR.

Backplane синхронізує повідомлення між кількома екземплярами серверів у середовищі з балансуванням навантаження. Без нього повідомлення, надіслане з сервера A, досягає лише клієнтів, підключених до сервера A. Redis та Azure SignalR Service є поширеними опціями backplane. Backplane публікує повідомлення до спільного каналу, на який підписані всі сервери.

Стратегії Оптимізації Продуктивності

Продуктивність SignalR залежить від розміру повідомлень, частоти та кількості з'єднань. Документація найкращих практик продуктивності ASP.NET Core надає детальні бенчмарки.

  • Використовуйте протокол MessagePack для бінарної серіалізації — зменшує розмір payload на 30-50% порівняно з JSON
  • Групуйте повідомлення коли можливо замість надсилання багатьох малих повідомлень
  • Обмежуйте розміри груп до тисяч учасників; для більших аудиторій розгляньте патерни pub/sub
  • Увімкніть компресію для текстових payload
  • Встановіть відповідні таймаути для швидкого звільнення ресурсів від застарілих з'єднань
csharp
// Конфігурація MessagePack для менших payload
builder.Services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = 
            MessagePackSerializerOptions.Standard
                .WithCompression(MessagePackCompression.Lz4Block);
    });

Висновок

  • SignalR абстрагує вибір транспорту (WebSockets, SSE, Long Polling) і надає послідовну модель програмування для комунікації в реальному часі
  • Хаби централізують керування з'єднаннями; строго типізовані хаби додають безпеку на етапі компіляції для викликів методів клієнта
  • Групи забезпечують цільову доставку повідомлень підмножинам з'єднань — приєднуйтеся до груп повторно після reconnect
  • Автентифікація інтегрується з ASP.NET Core identity; передавайте токени через query string для WebSocket-з'єднань
  • Горизонтальне масштабування з Redis backplane або Azure SignalR Service для синхронізації повідомлень між екземплярами серверів
  • Потокова передача підтримує інкрементальну доставку даних з IAsyncEnumerable для сценаріїв сервер-до-клієнта та клієнт-до-сервера

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Fullstack-розробник, засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 12 серпня 2026 р.

Теги

#signalr
#asp.net-core
#real-time
#websockets
#dotnet

Поділитися

Пов'язані статті