ءServer-Sent Events در ASP.NET Core و NET 10. 📡🚀
بهروزرسانیهای Real-time دیگر یک قابلیت «خوب است داشته باشیم» نیستند.
بیشتر رابطهای کاربری مدرن انتظار دارند به نوعی جریان زندهای از داده را از سمت سرور دریافت کنند.
سالها در اکوسیستم NET. ، پاسخ پیشفرض برای این نیاز SignalR بوده است.
در حالی که SignalR فوقالعاده قدرتمند است، اما برای سناریوهای سادهتر، داشتن گزینههای دیگر هم بسیار مفید است ✨
با انتشار ASP.NET Core 10، بالاخره یک API بومی و سطحبالا برای Server-Sent Events (SSE) داریم 🎉
این قابلیت فاصله بین polling سادهی HTTP و WebSocketهای دوطرفه از طریق SignalR را پر میکند.
🤔 چرا SSE بهجای SignalR؟
ءSignalR یک ابزار بسیار قدرتمند است که WebSockets، Long Polling و SSE را بهصورت خودکار مدیریت میکند و یک کانال ارتباطی دوطرفه (Full-Duplex) فراهم میکند.
اما این قدرت، هزینههایی هم دارد:
• استفاده از یک پروتکل مشخص (Hubs)
• نیاز به کتابخانهی سمت کلاینت
• نیاز به Sticky Session یا Backplane (مثل Redis) برای مقیاسپذیری
ءSSE متفاوت است، چون:
➡️ یکطرفه (Unidirectional) است: مخصوص استریم داده از سرور به کلاینت
🌐 ءHTTP بومی است: فقط یک درخواست استاندارد HTTP با text/event-stream
🔄 ءReconnect خودکار دارد: مرورگرها بهصورت Native با API به نام EventSource اتصال مجدد را مدیریت میکنند
🪶 سبک و ساده است: بدون کتابخانههای سنگین سمت کلاینت یا منطق handshake پیچیده
✨ سادهترین Endpoint برای Server-Sent Events
زیبایی API جدید SSE در NET 10.، سادگی آن است.
میتوانید از Results.ServerSentEvents استفاده کنید تا یک جریان از رویدادها را از هر <IAsyncEnumerable<T برگردانید.
از آنجایی که IAsyncEnumerable نمایانگر یک جریان داده است که در طول زمان میرسد، سرور متوجه میشود که باید اتصال HTTP را باز نگه دارد، بهجای اینکه بعد از اولین «chunk» آن را ببندد 🔓
در اینجا یک مثال مینیمال از یک Endpoint برای SSE وجود دارد که ثبت سفارشها را بهصورت Real-time استریم میکند 📦📊
app.MapGet("orders/realtime", (
ChannelReader<OrderPlacement> channelReader,
CancellationToken cancellationToken) =>
{
// 1. ReadAllAsync یک IAsyncEnumerable برمیگرداند
// 2. Results.ServerSentEvents به مرورگر میگوید: «این اتصال را باز نگه دار»
// 3. به محض ورود دادهی جدید به Channel، داده به کلاینت Push میشود
return Results.ServerSentEvents(
channelReader.ReadAllAsync(cancellationToken),
eventType: "orders");
});🔍 وقتی کلاینت این Endpoint را صدا میزند چه اتفاقی میافتد؟
🔹️سرور هدر Content-Type: text/event-stream را ارسال میکند 📬
🔸️اتصال باز میماند و در حالت انتظار برای داده قرار میگیرد ⏳
🔹️به محض اینکه اپلیکیشن شما یک سفارش جدید داخل Channel قرار دهد:
🔸️ءIAsyncEnumerable آن آیتم را yield میکند
🔹️ءNET. بلافاصله آن را از طریق همان اتصال HTTP باز به مرورگر ارسال میکند ⚡️
این یک روش فوقالعاده بهینه برای پیادهسازی اعلانهای Push است، بدون سربار یک پروتکل stateful.
🧠 نکتهی پایانی
در این مثال، از Channel فقط بهعنوان یک ابزار استفاده شده است.
در یک اپلیکیشن واقعی، ممکن است:
یک Background Service داشته باشید 🛠
به یک صف پیام مثل RabbitMQ یا Azure Service Bus گوش دهید 📮
یا به تغییرات دیتابیس واکنش نشان دهید 🗄
و سپس رویدادهای جدید را داخل Channel قرار دهید تا کلاینتهای متصل آنها را مصرف کنند.