SignalR ใน ASP.NET Core 2026: คู่มือครบถ้วนสำหรับ Real-Time, Hubs และคำถามสัมภาษณ์งาน

เรียนรู้ SignalR ใน ASP.NET Core สำหรับการสื่อสาร real-time แบบสองทาง คู่มือครบถ้วนเกี่ยวกับ Hubs, Groups, การยืนยันตัวตน, การ scale ด้วย Redis และคำถามสัมภาษณ์ทางเทคนิค

SignalR ใน ASP.NET Core 2026: คู่มือครบถ้วนสำหรับ Real-Time, Hubs และคำถามสัมภาษณ์งาน

SignalR ช่วยให้การสื่อสารแบบสองทาง real-time ระหว่าง server และ client ในแอปพลิเคชัน ASP.NET Core เป็นไปได้ ต่างจากรูปแบบ request-response HTTP แบบดั้งเดิม SignalR รักษาการเชื่อมต่อแบบถาวรที่ช่วยให้ server สามารถส่งการอัปเดตไปยัง client ได้ทันที—ซึ่งจำเป็นสำหรับแอปพลิเคชันแชท, แดชบอร์ดแบบสด, การแก้ไขร่วมกัน และการแจ้งเตือน real-time

SignalR Transport Fallback

SignalR เลือก transport ที่ดีที่สุดที่มีอยู่โดยอัตโนมัติ: WebSockets ก่อน จากนั้น Server-Sent Events แล้ว Long Polling กลไก fallback นี้ช่วยให้มั่นใจว่าสามารถใช้งานได้กับทุกเบราว์เซอร์และการกำหนดค่าเครือข่ายโดยไม่ต้องเปลี่ยนแปลงโค้ด

วิธีการทำงานของ SignalR Hubs สำหรับการสื่อสาร Server-Client

Hub ทำหน้าที่เป็น pipeline ระดับสูงที่จัดการการเรียก method ระหว่าง client และ server Client เรียก method บน hub และ hub สามารถเรียก method บน client ที่เชื่อมต่ออยู่ การ abstraction นี้ช่วยลดความซับซ้อนในการจัดการการเชื่อมต่อ WebSocket ด้วยตนเอง

ตัวอย่างต่อไปนี้แสดง chat hub พื้นฐานที่ส่งข้อความไปยัง client ที่เชื่อมต่อทั้งหมด:

ChatHub.cscsharp
using Microsoft.AspNetCore.SignalR;

public class ChatHub : Hub
{
    // Called when a client sends a message
    public async Task SendMessage(string user, string message)
    {
        // Broadcast to ALL connected clients
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    // Called automatically when a client connects
    public override async Task OnConnectedAsync()
    {
        await Clients.Caller.SendAsync("Connected", Context.ConnectionId);
        await base.OnConnectedAsync();
    }

    // Called automatically when a client disconnects
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        // Clean up resources, notify other users, etc.
        await base.OnDisconnectedAsync(exception);
    }
}

Property Clients ให้การเข้าถึง client ที่เชื่อมต่อทั้งหมดผ่านตัวเลือก targeting ต่างๆ: All, Caller, Others, Group และ User

การกำหนดค่า SignalR ใน ASP.NET Core 9

การลงทะเบียน SignalR ใน ASP.NET Core 9 ใช้รูปแบบ middleware มาตรฐาน การกำหนดค่าด้านล่างนี้เปิดใช้งานโปรโตคอล JSON และ MessagePack พร้อมขนาด buffer ที่กำหนดเอง:

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

// Add SignalR services with configuration
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();

// Map the hub endpoint
app.MapHub<ChatHub>("/chathub");

app.Run();

การตั้งค่า KeepAliveInterval และ ClientTimeoutInterval ควบคุมการตรวจสอบสถานะการเชื่อมต่อ Client ที่ไม่ตอบสนองภายในช่วง timeout จะถูกพิจารณาว่าถูกตัดการเชื่อมต่อ

การจัดการ Groups สำหรับการส่งข้อความแบบเจาะจง

Groups ให้กลไกในการจัดระเบียบการเชื่อมต่อและส่งข้อความไปยังกลุ่มย่อยของ client กรณีการใช้งานทั่วไปเกี่ยวข้องกับ chat room ที่ผู้ใช้เข้าร่วมห้องเฉพาะและรับข้อความจากห้องนั้นเท่านั้น

GroupChatHub.cscsharp
public class GroupChatHub : Hub
{
    // Add the current connection to a group
    public async Task JoinRoom(string roomName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
        
        // Notify the group that someone joined
        await Clients.Group(roomName).SendAsync(
            "UserJoined", 
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Remove the current connection from a group
    public async Task LeaveRoom(string roomName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
        
        await Clients.Group(roomName).SendAsync(
            "UserLeft",
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Send a message to a specific group only
    public async Task SendToRoom(string roomName, string message)
    {
        await Clients.Group(roomName).SendAsync(
            "ReceiveMessage",
            Context.User?.Identity?.Name,
            message
        );
    }
}

การเป็นสมาชิก group ผูกกับการเชื่อมต่อ ไม่ใช่ผู้ใช้ เมื่อผู้ใช้ reconnect ต้องเข้าร่วม group อีกครั้ง สำหรับการเป็นสมาชิก group แบบถาวร ให้เก็บการเชื่อมโยง group ในฐานข้อมูลและเข้าร่วมอีกครั้งโดยอัตโนมัติใน OnConnectedAsync

ความคงทนของ Group

Groups มีอยู่ในหน่วยความจำเท่านั้นและไม่ถูกเก็บรักษาหลังจาก server restart หรือในสถานการณ์ scaled-out โดยไม่มี backplane สำหรับการ deployment production กับหลาย server ให้ใช้ Redis หรือ Azure SignalR Service เป็น backplane

การรักษาความปลอดภัยการเชื่อมต่อ SignalR ด้วยการยืนยันตัวตน

SignalR ทำงานร่วมกับการยืนยันตัวตน ASP.NET Core Attribute [Authorize] จำกัดการเข้าถึง hub เฉพาะผู้ใช้ที่ยืนยันตัวตนแล้ว และ Context.User ให้การเข้าถึง claims ของผู้ใช้ที่ยืนยันตัวตนแล้ว

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

[Authorize] // Require authentication for the entire hub
public class SecureHub : Hub
{
    // Access user claims through Context.User
    public async Task SendPrivateMessage(string recipientUserId, string message)
    {
        var senderName = Context.User?.Identity?.Name 
            ?? throw new HubException("User not authenticated");
        
        // Send to a specific user (all their connections)
        await Clients.User(recipientUserId).SendAsync(
            "PrivateMessage",
            senderName,
            message
        );
    }

    [Authorize(Roles = "Admin")] // Role-based authorization on method
    public async Task BroadcastAnnouncement(string announcement)
    {
        await Clients.All.SendAsync("Announcement", announcement);
    }
}

สำหรับการเชื่อมต่อ WebSocket ต้องส่ง token ยืนยันตัวตนผ่าน query string เนื่องจาก WebSocket ไม่รองรับ custom headers กำหนดค่า JavaScript client ให้รวม token:

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();

บนฝั่ง server กำหนดค่าการยืนยันตัวตน JWT เพื่ออ่าน token จาก query string:

Program.cs - JWT configuration for SignalRcsharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // Read token from query string for 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 และแบบทดสอบเทคนิคครับ

การ Scale SignalR ด้วย Redis Backplane

ในสภาพแวดล้อม load-balanced client อาจเชื่อมต่อกับ server instance ที่แตกต่างกัน หากไม่มี backplane ข้อความที่ส่งจาก server หนึ่งจะไปถึงเฉพาะ client ที่เชื่อมต่อกับ server นั้น Redis ทำหน้าที่เป็น backplane เพื่อซิงโครไนซ์ข้อความข้ามทุก server instance

Program.cs - Redis backplane configurationcsharp
builder.Services.AddSignalR()
    .AddStackExchangeRedis(options =>
    {
        options.Configuration = builder.Configuration
            .GetConnectionString("Redis");
        
        // Optional: configure Redis-specific options
        options.Configuration.ChannelPrefix = 
            RedisChannel.Literal("MyApp_SignalR_");
    });

Redis backplane publish ข้อความ SignalR ทั้งหมดไปยัง Redis channels แต่ละ server instance subscribe ไปยัง channels เหล่านี้และส่งข้อความไปยัง client ในเครื่อง วิธีนี้เพิ่ม latency น้อยมาก (โดยทั่วไป 1-2ms) ในขณะที่ช่วยให้สามารถ horizontal scaling ได้

สำหรับการ deployment ระดับ enterprise Azure SignalR Service ให้ทางเลือกที่จัดการโดยอัตโนมัติซึ่งจัดการ scaling, การจัดการการเชื่อมต่อ และความพร้อมใช้งาน

Strongly-Typed Hubs สำหรับความปลอดภัย Compile-Time

Strongly-typed hubs แทนที่ magic strings ด้วย interface methods ช่วยให้สามารถตรวจสอบการเรียก method client ในเวลา compile:

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)
    {
        // Compile-time checking - no magic strings
        await Clients.All.ReceiveNotification(title, message, "info");
    }

    public async Task ReportProgress(string groupName, int percentage)
    {
        // IDE autocomplete works here
        await Clients.Group(groupName).UpdateProgress(percentage);
    }
}

การ refactor กลายเป็นเรื่องปลอดภัยมากขึ้นเพราะการเปลี่ยนชื่อ method ใน interface จะแสดงตำแหน่งการเรียกทั้งหมดที่ต้องอัปเดตทันที

Streaming ข้อมูลด้วย SignalR Channels

SignalR รองรับ streaming สำหรับสถานการณ์ที่ข้อมูลถูกผลิตทีละน้อย เช่น การอัปเดตความคืบหน้า, log tailing หรือข้อมูลเซนเซอร์ real-time รองรับทั้ง streaming แบบ server-to-client และ client-to-server

StreamingHub.cscsharp
public class StreamingHub : Hub
{
    // Server-to-client streaming with 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);
        }
    }

    // Client-to-server streaming
    public async Task UploadStream(IAsyncEnumerable<LogEntry> stream)
    {
        await foreach (var entry in stream)
        {
            // Process each log entry as it arrives
            await ProcessLogEntry(entry);
        }
    }
}

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

JavaScript client บริโภค stream โดยใช้ 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:

เคล็ดลับสัมภาษณ์

อธิบายกระบวนการเจรจา transport: SignalR ลอง WebSockets ก่อน, fallback ไปยัง Server-Sent Events, จากนั้น Long Polling เข้าใจว่าแต่ละ transport ถูกใช้เมื่อใดและข้อจำกัดของมัน

ถ: SignalR จัดการสถานะการเชื่อมต่ออย่างไรเมื่อ client หลุดการเชื่อมต่อชั่วคราว?

SignalR รักษาสถานะการเชื่อมต่อบน server ในช่วงเวลาที่กำหนดค่าได้ เมื่อเปิดใช้ automatic reconnection client จะพยายาม reconnect โดยใช้ connection ID เดิม หากสำเร็จ การเชื่อมต่อจะดำเนินต่อโดยไม่สูญเสียการเป็นสมาชิก group หากการตัดการเชื่อมต่อเกิน timeout จะสร้างการเชื่อมต่อใหม่และ client ต้องเข้าร่วม groups อีกครั้ง

ถ: ความแตกต่างระหว่าง Clients.User() และ Clients.Client() คืออะไร?

Clients.User(userId) เป้าหมายไปยังการเชื่อมต่อทั้งหมดที่เป็นของผู้ใช้ที่ยืนยันตัวตนเฉพาะ—มีประโยชน์เมื่อผู้ใช้หนึ่งคนเปิดหลาย browser tabs Clients.Client(connectionId) เป้าหมายไปยังการเชื่อมต่อเฉพาะหนึ่งเดียว การ targeting ตามผู้ใช้ต้องการการยืนยันตัวตนและใช้ IUserIdProvider เพื่อ map การเชื่อมต่อกับ user IDs

ถ: จะ implement presence detection (แสดงว่าใครออนไลน์) อย่างไร?

ติดตามการเชื่อมต่อใน OnConnectedAsync และ OnDisconnectedAsync เก็บ mapping user-to-connection ใน concurrent dictionary หรือ distributed cache สำหรับการ deployment แบบ scaled-out ใช้ shared store เช่น Redis Broadcast การอัปเดต presence ไปยัง groups ที่เกี่ยวข้องเมื่อผู้ใช้ connect หรือ disconnect

ถ: อธิบายจุดประสงค์ของ SignalR backplane

Backplane ซิงโครไนซ์ข้อความข้าม server instances หลายตัวในสภาพแวดล้อม load-balanced หากไม่มี ข้อความที่ส่งจาก server A จะไปถึงเฉพาะ client ที่เชื่อมต่อกับ server A Redis และ Azure SignalR Service เป็นตัวเลือก backplane ที่พบบ่อย Backplane publish ข้อความไปยัง shared channel ที่ทุก server subscribe

กลยุทธ์การเพิ่มประสิทธิภาพ

ประสิทธิภาพ SignalR ขึ้นอยู่กับขนาดข้อความ ความถี่ และจำนวนการเชื่อมต่อ เอกสาร best practices ประสิทธิภาพ ASP.NET Core ให้ benchmark โดยละเอียด

  • ใช้โปรโตคอล MessagePack สำหรับ binary serialization—ลดขนาด payload 30-50% เมื่อเทียบกับ JSON
  • Batch ข้อความ เมื่อเป็นไปได้แทนที่จะส่งข้อความเล็กๆ หลายข้อความ
  • จำกัดขนาด group ที่หลักพัน members; สำหรับกลุ่มผู้ชมที่ใหญ่กว่า ให้พิจารณารูปแบบ pub/sub
  • เปิดใช้ compression สำหรับ payload ที่มี text เยอะ: .AddJsonProtocol().AddHubOptions(o => o.EnableDetailedErrors = false)
  • ตั้งค่า timeout ที่เหมาะสม เพื่อปล่อย resources จาก stale connections อย่างรวดเร็ว
csharp
// MessagePack configuration for smaller payloads
builder.Services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = 
            MessagePackSerializerOptions.Standard
                .WithCompression(MessagePackCompression.Lz4Block);
    });

สรุป

  • SignalR abstraction การเลือก transport (WebSockets, SSE, Long Polling) และให้โมเดลการเขียนโปรแกรมที่สอดคล้องกันสำหรับการสื่อสาร real-time
  • Hubs รวมศูนย์การจัดการการเชื่อมต่อ; strongly-typed hubs เพิ่มความปลอดภัย compile-time สำหรับการเรียก method client
  • Groups ช่วยให้ส่งข้อความแบบเจาะจงไปยังกลุ่มย่อยของการเชื่อมต่อ—เข้าร่วม groups อีกครั้งหลัง reconnection
  • การยืนยันตัวตนรวมเข้ากับ ASP.NET Core identity; ส่ง token ผ่าน query string สำหรับการเชื่อมต่อ WebSocket
  • Scale แบบ horizontal ด้วย Redis backplane หรือ Azure SignalR Service เพื่อซิงโครไนซ์ข้อความข้ามทุก server instances
  • Streaming รองรับการส่งข้อมูลแบบทีละน้อยด้วย IAsyncEnumerable สำหรับทั้งสถานการณ์ server-to-client และ client-to-server

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ชาเลนจ์ประจำวัน

คุณหาบั๊กใน .NET เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 12 สิงหาคม 2569

แชร์

บทความที่เกี่ยวข้อง

งาน freelance .NET developer ปี 2026: หาสัญญาที่ไหน อัตราค่าจ้างรายวัน และวิธีเตรียมตัว

งาน freelance .NET developer ปี 2026: หาสัญญาที่ไหน อัตราค่าจ้างรายวัน และวิธีเตรียมตัว

ตลาด freelance .NET ในปี 2026 มอบอัตราค่าจ้างรายวันที่ดีและความต้องการทักษะ cloud-native ที่เพิ่มขึ้น คู่มือนี้ครอบคลุมการหาสัญญา อัตราค่าจ้างที่คาดหวัง และการเตรียมตัวสัมภาษณ์ด้านเทคนิค

งาน .NET Developer 2026: แหล่งหางาน ช่วงเงินเดือน และการเตรียมตัว

งาน .NET Developer 2026: แหล่งหางาน ช่วงเงินเดือน และการเตรียมตัว

คู่มือครบถ้วนสำหรับการหางาน .NET developer ในปี 2026 รวมถึงช่วงเงินเดือน แพลตฟอร์มสรรหาที่ดีที่สุด และเคล็ดลับการเตรียมสัมภาษณ์เทคนิค

Clean Architecture .NET พร้อม CQRS และ MediatR

Clean Architecture .NET 2026: CQRS, MediatR และคำถามสัมภาษณ์นักพัฒนา

คู่มือฉบับสมบูรณ์เกี่ยวกับ Clean Architecture ใน .NET พร้อม CQRS และ MediatR 14 เรียนรู้รูปแบบสถาปัตยกรรมแบบเลเยอร์ pipeline behaviors และเตรียมตัวสัมภาษณ์ senior developer