کار با SignalR Alpha 2

ASP.NET SignalR کتابخانه‌ای برای توسعه‌دهندگان ASP.NET است که روند افزودن عملکردهای وب بلادرنگ (real-time) را برای برنامه‌ها آسان می‌کند. منظور از عملکرد وب‌سایت real-time قابلیتی است تا سرور بتواند کدها را در content قرار داده تا کلاینت‌ها به طور مستقیم کانکت شوند، به جای اینکه سرور برای درخواست داده‌های جدید کلاینت منتظر بماند.

کار با SignalR Alpha 2

در این مقاله ما می‌خواهیم نحوه شروع کار با SignalR Alpha 2 را برای ASP.NET Core 2.0 بررسی کنیم.

گفتگو (چت)

هر مقاله‌ای که در مورد SignalR صحبت می‌کند شامل یک نمونه چت می‌باشد. این استاندارد از همان ابتدا بوده است و ما نیز نمونه‌ای از آن را در اینجا قرار می‌دهیم. این یک مثال ساده است، فقط در حدی است که مطمئن شوید که همه چیز را به درستی پیاده کرده و مفاهیم اساسی SignalR را درک کرده‌اید.

همان‌طور که قبلا گفتیم، برای شروع کار به NET Core 2.0.0. نیاز داریم. یک برنامه خالی وب با dotnet new web ایجاد می‌کنیم که به ما ساده‌ترین برنامه ASP.NET Core را می‌دهد؛ که شامل یک Program.cs، یک Startup.cs و یک csproj. می‌باشد.

ابتدا باید رفرنسی به SignalR در فایل csproj. اضافه کنیم، <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.0-alpha2-final”/>، سپس  dotnet restore را انجام دهید.

csproj. شما باید همانند تصویر زیر باشد:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.0-alpha2-final" />
  </ItemGroup>

</Project>

حالا اجازه دهید یک (Chat Hub) ایجاد کنیم. اساسا، این یک کلاس است که از کلاس پایه (Hub) ارث‌بری می‌کند. در متد (Send)، پیام‌ها برای همه کلاینت‌های متصل ارسال می‌شود. در واقع، این متد به همه کلاینت‌ها می‌گوید تا (broadcastMessage) خود را با پارامترهای name و message نمایش دهند.

using Microsoft.AspNetCore.SignalR;

namespace signalr_aspnetcore
{
    public class ChatHub : Hub
    {
        public void Send(string name, string message)
        {
            // Call the broadcastMessage method to update clients.
            Clients.All.InvokeAsync("broadcastMessage", name, message);
        }
    }
}

()services.AddSignalR: SignalR را به سرویس‌های موجود و موتور تزریق وابستگی (dependency injection engine) اضافه می‌کند.

()app.UseFileServer: اجازه سرویس‌دهی به فایل‌های استاتیک می‌دهد. ما به این مورد وقتی نیاز داریم که یک صفحه ساده HTML ایجاد می‌کنیم.

app.UseSignalR(routes =>
{
    routes.MapHub<ChatHub>("chat");
});

این دستورات برنامه را برای استفاده از SignalR و ترسیم Hub‌ای که فقط آن را ایجاد کردیم، مطلع می‌کند و در نقطه پایان چت را نمایش می‌دهد.

در زیر فایل کامل Startup.cs آمده است:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace signalr_aspnetcore
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseFileServer();
            app.UseSignalR(routes =>
            {
                routes.MapHub<ChatHub>("chat");
            });

        }
    }
}

حالا برای کلاینت: ما نیاز داریم تا یک صفحه ساده HTML ایجاد کنیم که می‌تواند پیام‌ها را ارسال کند. این مورد از SignalR JavaScript client استفاده می‌کند که شما می‌توانید با استفاده از npm: npm install @aspnet/signalr-client آن را دانلود کنید، یا می‌توانید از این لینک استفاده کنید.

موارد زیر را در صفحه وب داریم:

یک سری ورودی از نوع text داریم که در آن‌ها پیام‌ها را می‌نویسیم.

دکمه‌ای برای ارسال پیام داریم.

سپس یک رفرنس برای SignalR JavaScript client ایجاد می‌کنیم که باید در همان فولدر فایل HTML باشد.

حالا برای SignalR magic:

;var transport = signalR.TransportType.WebSockets: مشخص می‌کنیم که می‌خواهیم از WebSockets استفاده کنیم.

;var connection = new signalR.HubConnection("http://${document.location.host}/chat", { transport: transport}):

یک کانکشن (SignalR) ایجاد می‌کنیم تا (chat/) ما را با استفاده از WebSockets  در Startup.cs تعریف کند.

connection.on('broadcastMessage', (name, message) => {
    var liElement = document.createElement('li');
    liElement.innerHTML = '<strong>' + name + '</strong>:&nbsp;&nbsp;' + message;
    document.getElementById('discussion').appendChild(liElement);
});

در اینجا ما متد broadcastMessage را تعریف کرده و اجرا می‌کنیم – اساسا پیامی را که در لیست مرتب در صفحه دریافت کرده‌ایم را اضافه می‌کنیم.

button.addEventListener("click", event => {
    connection.invoke('send', name, messageInput.value);
    messageInput.value = '';
    messageInput.focus();
});

سپس به سادگی اتصال را شروع می‌کنیم: ;()connection.start

در زیر index.html به صورت کامل آمده است:

<!DOCTYPE html>
<html>

<head>
    <title>Simple Chat</title>
</head>

<body>
    <div class="container">
        <input type="text" id="message" />
        <input type="button" id="sendMessage" value="Send" />
        <ul id="discussion"></ul>
    </div>
    <script type="text/javascript" src="signalr-client-1.0.0-alpha2-final.js"></script>

    <script type="text/javascript">
        var transport = signalR.TransportType.WebSockets;
        var connection = new signalR.HubConnection(`http://${document.location.host}/chat`, { transport: transport });
        var messageInput = document.getElementById('message');
        var name = prompt('Enter your name:', '');
        var button = document.getElementById("sendMessage");
        connection.on('broadcastMessage', (name, message) => {
            var liElement = document.createElement('li');
            liElement.innerHTML = '<strong>' + name + '</strong>:&nbsp;&nbsp;' + message;
            document.getElementById('discussion').appendChild(liElement);
        });
        button.addEventListener("click", event => {
            connection.invoke('send', name, messageInput.value);
            messageInput.value = '';
            messageInput.focus();
        });
        connection.start();
    </script>
</body>

</html>

حالا اگر می‌خواهید ببینید که آیا واقعا کار می‌کند، باید برنامه وب را استارت کنیم. ( Visual Studio/Code یا از طریق dotnet run)، سپس در مرورگر به این آدرس http://localhost:5000 می‌رود.

در این مرحله، شما موفق شدید یک برنامه ساده SignalR ایجاد کنید. حالا می‌دانید چگونه متد hub و متدهای کلاینت را ایجاد کرده و همچنین نحوه اتصال برنامه وب با استفاده از JavaScript client را نیز می‌دانید.

افزودن یک کنسول کلاینت #C

حالا اگر بخواهیم به یک کلاینت مبتنی بر سی شارپ متصل شویم، فقط باید پکیج Microsoft.AspNetCore.SignalR.Client را به یک برنامه ساخته شده جدید dotnet new console اضافه کنیم، <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="1.0.0-alpha2-final”/>

اجازه دهید مهم‌ترین مسائل در مورد کنسول کلاینت را بررسی کنیم:

ابتدا کانکشن را با استفاده از http://localhost:5000/chat استارت می‌کنیم.

public static async Task StartConnectionAsync()
{
    _connection = new HubConnectionBuilder()
         .WithUrl("http://localhost:5000/chat")
         .WithConsoleLogger()
         .Build();

    await _connection.StartAsync();
}

سپس، متد را برای broadcastMessage ثبت و پیاده می‌کنیم:

_connection.On<string, string>("broadcastMessage", (name, message) =>
{
    Console.WriteLine($"{name} said: {message}");
});

در زیر Program.cs بصورت کامل آمده است:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;

namespace console_client
{
    class Program
    {
        private static HubConnection _connection;
        static void Main(string[] args)
        {

            StartConnectionAsync();
            _connection.On<string, string>("broadcastMessage", (name, message) =>
            {
                Console.WriteLine($"{name} said: {message}");
            });

            Console.ReadLine();
            DisposeAsync();
        }


        public static async Task StartConnectionAsync()
        {
            _connection = new HubConnectionBuilder()
                 .WithUrl("http://localhost:5000/chat")
                 .WithConsoleLogger()
                 .Build();

            await _connection.StartAsync();
        }

        public static async Task DisposeAsync()
        {
            await _connection.DisposeAsync();
        }
    }
}

حالا ببینیم چگونه کار می‌کند:

بررسی رویدادهای اتصال و قطع اتصال

یکی از مواردی که در (SignalR) سابق از آن استفاده می‌کردیم این بود که رویدادهای اتصال و قطع اتصال را از طریق برخی از متدهای (overridden) در (hub)‌های خود بررسی می‌کردیم. ما هنوز هم می‌توانیم این کار را به روشی مشابه انجام دهیم. در زیر می‌توانید متدهای OnConnectedAsync و OnDisconnectedAsync  را ببینید:

public override Task OnConnectedAsync()
{
    Clients.All.InvokeAsync("broadcastMessage", "system", $"{Context.ConnectionId} joined the conversation");
    return base.OnConnectedAsync();
}

این متد،  broadcastMessage را فراخوانی کرده و connection ID‌ای که اخیرا کلاینت با آن کانکت شده است را صدا می‌زند، سپس return ‌می‌کند و اجرا را با OnConnectedAsync از hub ادامه می‌دهد.

متد OnDisconnectedAsync نیز اساسا همان کار را انجام می‌دهد.

public override Task OnDisconnectedAsync(System.Exception exception)
{
    Clients.All.InvokeAsync("broadcastMessage", "system", $"{Context.ConnectionId} left the conversation");
    return base.OnDisconnectedAsync(exception);
}

کار با MVC/WebApi

یکی دیگر از موارد محبوب SignalR این است که اعلان‌ها را از طریق MVC یا WebAPI، خصوصا از طریق یک متد کنترلر ارائه می‌دهد. این به این معنی است که اولین نقطه تعامل را به طور مستقیم توسط متد hub فراخوانی نمی‌کند، بلکه بعد از اینکه کلاینت درخواست REST را برای کنترلر ایجاد کرد، سپس از متد کنترلر، ما باید یک به‌روزرسانی را برای همه کلاینت‌های متصل ارسال کنیم.

ما باید با یک dotnet new webapi ساده شروع کرده و SignalR را همان‌طور که قبلا انجام دادید اضافه کنیم. سپس با ValuesController کار خواهیم کرد.

حالا ما یک NotificationsHub ساده اضافه می‌کنیم که هیچ متدی ندارد- کلاینت‌ها متدهای hub را به طور مستقیم فراخوانی نمی‌کنند، اما ترجیحا از کنترلر مطلع می‌شوند.

using Microsoft.AspNetCore.SignalR;

namespace signalr_mvc
{
    public class NotificationsHub : Hub
    {
    }
}

در حال حاضر کلاینت یکسان است، تنها تفاوت این است که وقتی دکمه فشرده می‌شود،‌ هیچ فراخوانی SignalR hubای  وجود ندارد، بلکه یک واکشی ساده برای یک متد کنترلر است.

در سازنده کنترلر نمونه‌ای (instance) از کانتکس برای NotificationsHub تزریق می‌کنیم. این کار به ما اجازه می‌دهد راهی برای تعامل با کلاینت‌های متصل به NotificationsHub از خارج hub داشته باشیم. (GlobalHost را در نسخه‌های قدیمی‌تر SignalR به خاطر دارید؟)

سپس به سادگی متدی را برای کلاینت‌های متصل فراخوانی می‌کنیم.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

namespace signalr_mvc.Controllers
{
    [Route("api/[controller]")]
    public class SimpleController : Controller
    {
        private IHubContext<NotificationsHub> _hubContext;

        public SimpleController(IHubContext<NotificationsHub> hubContext)
        {
            _hubContext = hubContext;
        }

        // GET api/simple
        [HttpGet]
        public string Get()
        {
            _hubContext.Clients.All.InvokeAsync("updateStuff", "some random text");
            return "I have been called!";
        }

    }

}

اساسا، هر زمان که به کانتکس hub  در ASP.NET Core خارج از hub نیاز داشته باشید، می‌توانید نمونه‌ای از کانتکس hub را از تزریق وابستگی درخواست کنید، و فریم‌ورک آن را بر عهده خواهد گرفت.

تاکنون نحوه ایجاد یک برنامه چت با Web و console client، سپس نحوه ارسال اعلان‌ها از یک کنترلر را دیدیم. حالا بیایید نحوه جریان داده (stream data) را ببینیم.

Streaming

دوباره از یک dotnet new web خالی شروع می‌کنیم و SignalR را همانند اولین مثال اضافه کرده و یک کلاس StreamingHub اضافه می‌کنیم و یک متد streaming در hub اضافه می‌کنیم. بیایید ببینیم در اینجا چه اتفاقی می‌افتد:

برای استریم در متد، باید یک IObservable<T> برگرداند که هر ثانیه یک پیام را ارسال کند.

متد (SendStreamInit) برای (broadcast) همه کلاینت‌های متصل که streaming را آغاز کرده‌اند، استفاده می‌شود و هر کلاینت رویداد استریم را به شیوه خاص خود مدیریت می‌کند. بعدا در مورد اجرای کلاینت‌ها بیشتر خواهیم گفت.

public void SendStreamInit()
{
    Clients.All.InvokeAsync("streamStarted");
}

public IObservable<string> StartStreaming()
{
    return Observable.Create(
        async (IObserver<string> observer) =>
        {
            for (int i = 0; i < 10; i++)
            {
                observer.OnNext($"sending...{i}");
                await Task.Delay(1000);
            }
        });
    }
}

حالا برای جاوااسکریپت کلاینت‌ها

ما آنچه را که روی فراخوانی streaming بعدی اتفاق می‌افتد – ""next، در مورد خطا – "err" و زمان خاتمه ""streaming را تعریف می‌کنیم.

یک رویداد listener برای دکمه اضافه کرده تا متد hub‌ای که streaming را روی کلاینت آغاز کرده است را فراخوانی کنیم. سپس متد onStreamReceived را که فقط پیام را به یک لیست unordered اضافه می‌کند را تعریف و پیاده‌سازی می‌کنیم.

چیزی که در اینجا باید به آن توجه کنید این است که وقتی یک کلاینت دکمه استریم را فشار می‌دهد، تمام کلاینت‌های متصل streaming را آغاز خواهند کرد – شما می‌توانید وقتی دکمه را فشار می‌دهید به طور مستقیم streaming را فراخوانی کنید، اگر فقط می‌خواهید برای شروع streaming فراخوانی را انجام دهید.

function startStreaming(){
    connection.stream("StartStreaming").subscribe({
        next: onStreamReceived,
        err: function(err){
            console.log(err);
        },
        complete: function(){
            console.log("finished streaming");
        }
    });
}

connection.on("streamStarted", function(){
    startStreaming();
});

button.addEventListener("click", event => {
    connection.invoke("sendStreamInit");
});

function onStreamReceived(data){
    console.log("received: " + data);
    var liElement = document.createElement('li');
    liElement.innerHTML = '<strong>' + "received" + '</strong>:&nbsp;&nbsp;' + data;
    document.getElementById('discussion').appendChild(liElement);
}

حالا بیایید مدیریت کلاینت سی شارپ استریم را بررسی کنیم:

در اینجا ما جالب‌ترین قسمت کلاینت #C را می‌بینیم، جایی که ما در واقع دیتا استریم را مدیریت می‌کنیم. Streaming را همان طور که در جاوا اسکریپت انجام دادیم، شروع می‌کنیم. سپس سعی می‌کنیم یک رشته را از کانال بخوانیم. – سپس به طور ساده پیام را روی کنسول چاپ می‌کنیم.

public async static Task StartStreaming()
{
    var channel = await _connection.StreamAsync<string>("StartStreaming", CancellationToken.None);
    while (await channel.WaitToReadAsync())
    {
        while (channel.TryRead(out string message))
        {
            Console.WriteLine($"Message received: {message}");
        }
    }   
}

حالا به سی‌شارپ کلاینت‌ها و وب که در حال اجرا هستند نگاهی بیندازید:

ارسال داده‌های باینری

تاکنون ما نحوه استفاده از پروتکل JSON را برای ارسال داده دیدیم. یکی از ویژگی‌های عالی نسخه جدید SignalR این است که توانایی ارسال داده‌های اینکد شده باینری را دارد. نحوه قرار دادن کلاینت‌ها برای استفاده از پروتکل‌های فرمت باینری را بررسی خواهیم کرد.

اول، کلاینت سی شارپ- هنگام تعریف کانکشن، همه ما احتیاج داریم تا HubConnectionBuilder را برای استفاده از پروتکل بسته پیام فراخوانی کنیم- نحوه ایجاد کانکشن مانند زیر است:

public static async Task StartConnectionAsync()
{
    _connection = new HubConnectionBuilder()
         .WithUrl("http://localhost:5000/chat")
         .WithConsoleLogger()
         .WithMessagePackProtocol()
         .Build();

    await _connection.StartAsync();
}

تنها تفاوت ()WithMessagePackProtocol. است – از این نقطه، کلاینت سی شارپ از پروتکل بسته پیام استفاده می‌کند.

حالا اجازه دهید کلاینت جاوااسکریپت را ببینیم. – در اینجا نیز باید اسکریپت را برای بسته پیام قرار دهیم: signalr-msgpackprotocol-1.0.0-alpha2-final.js ( که شما می‌توانید npm install @aspnet/signalr-client را انجام دهید)، سپس پروتکل را به این صورت تعریف کنید: var ;()protocol = new signalRMsgPack.MessagePackHubProtocol

var protocol = new signalRMsgPack.MessagePackHubProtocol();
var connection = new signalR.HubConnection(`http://${document.location.host}/chat`, { transport: transport, protocol: protocol });

می‌توانید مثال‌های کامل‌تری را در GitHub ببینید.

اگر نمونه‌های باینری را اجرا کنیم می‌بینیم که پروتکل در هر دو مورد به messsagepack تنظیم شده است.

تا اینجا ما نحوه ایجاد چت ساده، نحوه تزریق کانتکس hub در کنترلر، دیتا استریم برای C# Client و Javascript و تنظیم پروتکل باینری را دیدیم. حالا می‌خواهیم به یکی دیگر از موارد استفاده‌های متداول نگاهی بیندازیم -Redis scaleout.

Redis scaleout

برای هر کلاینت متصل شده، SignalR شناسه کانکشن (connection ID)  را در حافظه نگه می‌دارد. این به این معناست که اگر ما همیشه نیاز داشته باشیم تا برنامه خود را برای چند نمونه (instance) پیمایش کنیم، فرستادن پیام برای همه کلاینت‌های متصل دیگر به این سادگی کار نخواهد کرد، زیرا هر نمونه فقط یک بخش از همه کلاینت‌های متصل را نگه داشته است.

در این شرایط ما از Redis cache استفاده می‌کنیم و SignalR به طور خودکار پیام‌ها را بین نمونه‌ها پخش می‌کند. اساسا، ما فقط نیاز به اضافه کردن رفرنس به Redis و SignalR.Redis داریم: Microsoft.AspNetCore.SignalR.Redis

public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR()
        .AddRedis(options => options.Factory = writer =>
        {
            return ConnectionMultiplexer.Connect("localhost", writer);
        });
}

حالا نیاز به یک نمونه از Redis و تنظیم hint است: می‌توانید با یک نگهدارنده Redis Docker در چند ثانیه آغاز کنید با: docker run -p 6379:6379 --name redis redis

حالا اگر با دو نمونه از وب اپلیکیشن شروع کنیم، نحوه پیمایش برنامه به صورت نرمال انجام می‌شود. (در اینجا دو پورت متفاوت داریم، اما به طور معمول شما در مقابل درخواست خود یک (load balancer) دارید): ASPNETCORE_URLS="http://*:5000" dotnet run و ASPNETCORE_URLS="http://*:5001" dotnet run، سپس به مرورگر بروید:

در این مثال ما سعی کردیم تا نمونه‌هایی از رایج‌ترین سناریوهای SignalR را بررسی کنیم. امیدوار هستیم که این مقاله برای شما مفید واقع شده باشد.