رویکردی نوین به ارتباطات بیدرنگ: رویدادهای ارسالی سرور (Server-Sent Events) در NET 10.
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۱۰ ۰۷:۳۷
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
text/event-stream به کلاینت ارسال میکند. این فرمت استاندارد برای رویدادهای ارسالی سرور است که تضمین میکند کلاینت میتواند پیامها را به درستی تفسیر کند.TypedResults.ServerSentEvents پشتیبانی میکند. این ویژگی هم در Minimal APIs و هم در Controller-based APIs قابل استفاده است که انعطافپذیری زیادی را برای توسعهدهندگان فراهم میآورد. در .NET، پیامهای رویداد به عنوان اشیاء SseItem نمایش داده میشوند که میتوانند شامل یک نوع رویداد (event type)، یک شناسه (ID)، و یک بار داده (data payload) از نوع T باشند.RideStatusService)INotifyPropertyChanged را پیادهسازی میکند. این رابط استاندارد در .NET برای اطلاعرسانی تغییرات در خصوصیات یک شیء است و برای اتصال داده (Data Binding) در WPF و Blazor نیز استفاده میشود. با پیادهسازی این رابط، هر زمان که خاصیت NextDirection تغییر کند، رویداد PropertyChanged فراخوانی میشود و کلاینتها (در این مورد، API) از این تغییر مطلع میشوند.public class RideStatusService: INotifyPropertyChanged
{
public RideStatusService()
{
NextDirection = "Starting the trip";
}
private static List<string> _directions = ["left", "right", "straight"];
private static List<string> _distances = ["50", "60", "70", "80", "90", "100"];
public event PropertyChangedEventHandler? PropertyChanged;
private string _nextDirection;
private string NextDirection
{
get => _nextDirection;
set
{
_nextDirection = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void SetDirection()
{
NextDirection = $"Going {_directions[Random.Shared.Next(_directions.Count)]} for {_distances[Random.Shared.Next(_distances.Count)]} meters";
}
public async IAsyncEnumerable<string> GetNextDirection([EnumeratorCancellation] CancellationToken ct)
{
while (ct is not { IsCancellationRequested: true })
{
yield return NextDirection;
var tcs = new TaskCompletionSource();
PropertyChangedEventHandler handler = (_, _) => tcs.SetResult();
PropertyChanged += handler;
try
{
await tcs.Task.WaitAsync(ct);
}
finally
{
PropertyChanged -= handler;
}
}
}
}SetDirection به صورت تصادفی جهت و فاصله جدید را تولید و مقدار NextDirection را بهروزرسانی میکند. متد GetNextDirection یک جریان نامحدود از بهروزرسانیها را فراهم میکند. این متد یک IAsyncEnumerable را برمیگرداند که اجازه میدهد تا هر زمان که NextDirection تغییر کرد، یک آیتم جدید به جریان اضافه شود. TaskCompletionSource و PropertyChangedEventHandler با هم کار میکنند تا هر زمان که تغییری رخ داد، جریان را پیش ببرند.RideShareBackgroundWorker)RideStatusService را تزریق کرده و متد SetDirection آن را در یک حلقه نامحدود فراخوانی میکند.public class RideShareBackgroundWorker(RideStatusService rideStatusService): BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
rideStatusService.SetDirection();
await Task.Delay(1000, stoppingToken);
}
}
}app.MapGet("/ride-status", (RideStatusService rideStatusService, CancellationToken cancellationToken) =>
TypedResults.ServerSentEvents(rideStatusService.GetNextDirection(cancellationToken), eventType: "ride-status")
);/ride-status تعریف میکند. وقتی کلاینت به این مسیر متصل میشود، TypedResults.ServerSentEvents از جریان GetNextDirection سرویس rideStatusService برای ارسال دادهها استفاده میکند. نوع رویداد eventType روی "ride-status" تنظیم شده است که به کلاینت اجازه میدهد رویدادهای خاص مربوط به وضعیت سفر را فیلتر کند.builder.Services.AddSingleton<RideStatusService>(); builder.Services.AddHostedService<RideShareBackgroundWorker>();
RideStatusService به عنوان یک نمونه سینگلتون (Singleton) ثبت میشود تا اطمینان حاصل شود که یک نمونه واحد از آن در سراسر برنامه وجود دارد. RideShareBackgroundWorker نیز به عنوان یک سرویس هاست شده (Hosted Service) ثبت میشود تا ASP.NET Core آن را به عنوان یک سرویس پسزمینه مدیریت کند.EventSource و گوش دادن به رویدادها استفاده میکنیم. HTML شامل یک عنصر با id="status" است که برای نمایش بهروزرسانیها استفاده میشود.<div class="text-center">
<h1 class="display-4">Welcome to My Ride Share</h1>
<h4>You current ride's routing status is:</h4>
<ul id="status"></ul>
</div>
<script>
const eventSource = new EventSource('http://localhost:5252/ride-status');
const statusList = document.getElementById('status');
eventSource.addEventListener('ride-status', event => {
const li = document.createElement('li');
li.textContent = event.data;
statusList.appendChild(li);
});
eventSource.onerror = error => {
console.error('EventSource failed for ride status:', error);
eventSource.close();
};
</script>EventSource را ایجاد میکند و آن را به نقطه پایانی /ride-status در سرور (با فرض اینکه سرور روی پورت 5252 اجرا میشود) متصل میکند. یک شنونده رویداد به "ride-status" اضافه میشود؛ هر زمان که یک رویداد از نوع "ride-status" دریافت شود، دادههای آن (event.data) به عنوان یک عنصر لیست به statusList اضافه میشود. یک شنونده برای خطاها نیز وجود دارد که در صورت بروز مشکل، خطا را در کنسول گزارش میدهد و اتصال EventSource را میبندد.TypedResults.ServerSentEvents و ترکیب آن با الگوهای طراحی مناسب مانند INotifyPropertyChanged و سرویسهای پسزمینه، توسعهدهندگان .NET میتوانند به راحتی برنامههایی با قابلیتهای بیدرنگ قدرتمند بسازند. سادگی پیادهسازی در مقایسه با سایر روشهای ارتباطی بیدرنگ، SSE را به گزینهای جذاب برای برنامههایی که نیاز به بهروزرسانیهای مداوم و بدون نیاز به تعامل دوطرفه دارند، تبدیل میکند. این پیشرفتها، توسعه برنامههای کاربردی مدرن و واکنشگرا را در اکوسیستم .NET بیش از پیش تسهیل میکند.