پیادهسازی Server-Sent Events در ASP.NET Core و NET 10.
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۲/۰۳ ۰۸:۴۴
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
text/event-stream استفاده میکند.TypedResults فراهم کرده است. این قابلیت به توسعهدهندگان اجازه میدهد تا با حداقل کد، یک نقطه پایانی (Endpoint) برای ارسال رویدادهای سرور ایجاد کنند. در ادامه، مراحل پیادهسازی یک مثال ساده با استفاده از Minimal APIs، یک سرویس پسزمینه (Background Service)، و جاوااسکریپت برای کلاینت توضیح داده میشود.using System.ComponentModel;
using System.Runtime.CompilerServices;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<FoodService>();
builder.Services.AddHostedService<FoodServiceWorker>();
var app = builder.Build();
app.UseDefaultFiles().UseStaticFiles();
app.MapGet("/orders", (FoodService foods, CancellationToken token) =>
TypedResults.ServerSentEvents(
foods.GetCurrent(token),
eventType: "order")
);
app.Run();FoodService بهعنوان یک سرویس Singleton ثبت میشود تا مدیریت دادههای مشترک بین کلاینتها را بر عهده گیرد.FoodServiceWorker برای بهروزرسانی دورهای دادهها ثبت میشود./orders با استفاده از متد TypedResults.ServerSentEvents تعریف شده است که یک جریان (Stream) از نوع IAsyncEnumerable<string> را از متد GetCurrent دریافت میکند.eventType نوع رویداد را مشخص میکند (در اینجا order) که در سمت کلاینت برای شناسایی رویدادها استفاده میشود.CancellationToken امکان لغو اشتراک کلاینت را فراهم میکند، که برای مدیریت منابع سرور ضروری است.FoodService مسئول تولید جریان دادهها و همگامسازی کلاینتها با یک منبع داده واحد است. این کلاس از الگوی اعلان تغییرات (INotifyPropertyChanged) استفاده میکند تا تمام کلاینتها بهروزرسانیهای یکسانی دریافت کنند:public class FoodService : INotifyPropertyChanged
{
public FoodService()
{
Current = Foods[Random.Shared.Next(Foods.Length)];
}
public event PropertyChangedEventHandler? PropertyChanged;
private static readonly string[] Foods = ["🍔", "🍟", "🥤", "🍤", "🍕", "🌮", "🥙"];
private string Current
{
get => field;
set
{
field = value;
OnPropertyChanged();
}
}
public async IAsyncEnumerable<string> GetCurrent(
[EnumeratorCancellation] CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
yield return Current;
var tcs = new TaskCompletionSource();
PropertyChangedEventHandler handler = (_, _) => tcs.SetResult();
PropertyChanged += handler;
try
{
await tcs.Task.WaitAsync(ct);
}
finally
{
PropertyChanged -= handler;
}
}
}
public void Set()
{
Current = Foods[Random.Shared.Next(Foods.Length)];
}
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}Foods شامل اموجیهایی است که بهصورت تصادفی به کلاینتها ارسال میشوند.GetCurrent یک جریان IAsyncEnumerable تولید میکند که در هر چرخه، مقدار Current را بازمیگرداند و منتظر تغییر بعدی میماند.TaskCompletionSource و رویداد PropertyChanged تضمین میکند که تمام کلاینتها بهروزرسانیهای همزمان دریافت کنند.CancellationToken امکان توقف جریان را در صورت قطع ارتباط کلاینت فراهم میکند.public class FoodServiceWorker(FoodService foodService) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
foodService.Set();
await Task.Delay(1000, stoppingToken);
}
}
}Set را فراخوانی میکند تا مقدار Current در FoodService بهروزرسانی شود، که بهطور خودکار تمام کلاینتهای متصل را از طریق جریان IAsyncEnumerable مطلع میکند.EventSource برای اشتراک در نقطه پایانی SSE استفاده میکنیم. کد HTML و جاوااسکریپت زیر این فرآیند را نشان میدهد:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Khalid's Fast-Food Fair</title>
</head>
<style>
ul {
display: flex;
flex-direction: row;
list-style: none;
flex-wrap: wrap;
width: 90%;
gap: 1rem;
padding: 0;
}
li {
font-size: 2rem;
}
</style>
<body>
<h1>Khalid's Fast-Food Fair</h1>
<ul id="orders"></ul>
<script>
const eventSource = new EventSource('/orders');
const angelsList = document.getElementById('orders');
eventSource.addEventListener('order', event => {
const li = document.createElement('li');
li.textContent = event.data;
angelsList.appendChild(li);
});
eventSource.onerror = error => {
console.error('EventSource failed:', error);
eventSource.close();
};
</script>
</body>
</html>EventSource به نقطه پایانی /orders متصل میشود.order توسط addEventListener شنیده میشود و داده دریافتی (اموجی غذا) به لیست ul اضافه میشود.reconnect در EventSource میتواند پایداری ارتباط را افزایش دهد.Lib.AspNetCore.ServerSentEvents میتوانند پیادهسازی SSE را سادهتر کنند، بهویژه برای مدیریت چندین نقطه پایانی یا سناریوهای پیچیدهتر.IAsyncEnumerable، و کلاینت جاوااسکریپت است، نشاندهنده سادگی و انعطافپذیری این فناوری است. برای توسعهدهندگانی که به دنبال راهحلهای سبک و کارآمد برای ارتباطات بلادرنگ هستند، SSE جایگزینی مناسب برای فناوریهای پیچیدهتر مانند WebSocket و SignalR است. با این حال، انتخاب فناوری مناسب باید بر اساس نیازهای خاص پروژه انجام شود.