Отправьте уведомление pu sh с помощью WebSockets после нажатия кнопки - PullRequest
0 голосов
/ 08 марта 2020

Я очень близок к внедрению системы на C#, которая позволяет мне отправлять уведомления pu sh нажатием кнопки с интерфейса, написанного на ASP. net, на * 1066. * консольное приложение, действующее как клиент. Все это с использованием WebSockets.

После прочтения большого количества руководств и повторного использования кода, найденного в Интернете, я уже смог успешно установить sh соединение WebSocket. Я пока не могу отправить уведомление.

Часть, с которой я борюсь, - это функция, которая запускается при нажатии кнопки:

//Close ticket and send push-notification over websocket
public void Close(int id) {
    //Ticket ticket = mgr.GetTicket(id);

    //Create a new notification
    Notification notif = new Notification();
    notif.message = "Rofl test123 Notification lol";

    //Initialize WebSocketMiddleware here??
    //WebSocketsMiddleware wsm = new WebSocketsMiddleware(what parameter??);
    //wsm.Invoke(what HttpContext parameter???)

    NotificationManager notifMgr;
    //notifMgr.AddSubscriber(wsm);
    //notifMgr.SendNotificationAsync(notif);


    return;
}

Указание c вопросов / проблем, с которыми я сталкиваюсь:

  1. Как инициализировать класс WebSocketsMiddleware? Нужно ли его инициализировать, если да, каков параметр с типом RequestDelegate? Что мне передать этому параметру?
  2. WebSocketsMiddleware имеет функцию Invoke с контекстом параметра типа HttpContext. Мне просто нужно передать new HttpContext() на это? Этого достаточно?
  3. Кто-то создал класс NotificationManager, этот класс использует промежуточное ПО для фактической отправки уведомления. Нужно ли просто передавать инициализированную переменную WebSocketsMiddleware в качестве параметра для NotificationManager.AddSubscriber ()? Будут ли уведомления каждого клиента красиво разделены?
  4. Можно ли после этого просто использовать SendNotificationAsyn c () для отправки уведомления?
  5. Бонусный вопрос: скажем, что у каждого клиента есть свой кнопка. Когда я нажимаю кнопку клиента, только этот клиент может получить уведомление pu sh. Как убедиться, что все остальные клиенты также не получают одинаковое уведомление?

Чтобы помочь мне с этими вопросами, вам понадобятся следующие классы. Вопрос только о WebSockets, но больше о том, как инициировать и использовать классы, которые я собрал из учебных пособий.

Notification.cs - Класс, представляющий Уведомление (текст уведомления, дата отправки, ... ):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace SC.UI.MVC.Models
{
    public class Notification
    {
        public Guid? notificationId { get; set; }
        public int id { get; set; }
        public DateTime timestamp { get; set; }
        public string message { get; set; }
        public string type { get; set; }

        public Notification()
        {
            // add a new guid as a unique identifier for the notification in the db
            notificationId = Guid.NewGuid();
        }
    }
}

WebSocketsMiddleware.cs - обрабатывает низкоуровневую часть WebSockets, вызывая соединение et c:

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

namespace NotificationsApi.Notifications
{
    public class WebSocketsMiddleware
    {
        // private variable to track the next delegate to call in the request chain
        private readonly RequestDelegate _next;

        public WebSocketsMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            CancellationToken ct = context.RequestAborted;
            string currentSubscriberId = null;
            WebSocket currentSocket = null;

            // we want to listen on a specific path for websocket communications
            if (context.Request.Path == "/notifications/ws")
            {
                // make sure the request is a websocket request
                if (context.WebSockets.IsWebSocketRequest)
                {
                    currentSocket = await context.WebSockets.AcceptWebSocketAsync();
                    currentSubscriberId = NotificationManager.Instance.AddSubscriber(currentSocket);

                    // keep the socket open until we get a cancellation request
                    while (true)
                    {
                        if (ct.IsCancellationRequested)
                        {
                            break;
                        }
                    }
                }
                else // return an HTTP bad request status code if anything other a web socket request is made on this URI
                { 
                    context.Response.StatusCode = 400;
                }
            }

            // clean up the socket
            if (!string.IsNullOrWhiteSpace(currentSubscriberId))
            {
                NotificationManager.Instance.RemoveSubscriber(currentSubscriberId);
                if (currentSocket != null)
                {
                    await currentSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                    currentSocket.Dispose();
                }
            }

            // call the next delegate in the pipeline
            await _next(context);
            return;
        }
    }
}

NotificationManager.cs - интерфейс / класс с тремя функциями для добавления и удаления подписчиков, а также для фактической отправки уведомления. Для этого используется промежуточное программное обеспечение WebSocket:

using SC.UI.MVC.Models;
//using NotificationsApi.Persistence;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace NotificationsApi.Notifications
{
    // interface for NotificationManager for dependency injection
    public interface INotificationManager
    {
        string AddSubscriber(WebSocket subscriber);
        void RemoveSubscriber(string subscriberId);
        Task SendNotificationAsync(Notification notification);
    }

    public class NotificationManager : INotificationManager 
    {
        // static instance of the NotificationManager class
        private static INotificationManager _instance;
        public static INotificationManager Instance { get { return _instance ?? (_instance = new NotificationManager()); } set { _instance = value; } }

        // static dictionary to keep track of all notification subscribers
        private static ConcurrentDictionary<string, WebSocket> _subscribers = new ConcurrentDictionary<string, WebSocket>();

        // adds a subscriber to receive notifications
        public string AddSubscriber(WebSocket subscriber)
        {
            var subscriberId = Guid.NewGuid().ToString();
            _subscribers.TryAdd(subscriberId, subscriber);
            return subscriberId.ToString();
        }

        // removes a notifications subscriber
        public void RemoveSubscriber(string subscriberId)
        {
            WebSocket empty;
            _subscribers.TryRemove(subscriberId, out empty);
        }

        // sends a notification to all subscribers
        public async Task SendNotificationAsync(Notification notification)
        {
            // add the notification to the persistence store
            //await PersistenceManager.Instance.AddNotificationAsync(notification);

            // send the notification to all subscribers
            foreach (var s in _subscribers)
            {
                if (s.Value.State == WebSocketState.Open)
                {
                    var jsonNotification = JsonConvert.SerializeObject(notification);
                    await SendStringAsync(s.Value, jsonNotification);
                }
            }
        }

        // sends a string via web socket communication
        private async Task SendStringAsync(WebSocket socket, string data, CancellationToken ct = default(CancellationToken))
        {
            var buffer = Encoding.UTF8.GetBytes(data);
            var segment = new ArraySegment<byte>(buffer);
            await socket.SendAsync(segment, WebSocketMessageType.Text, true, ct);
        }
    }
}

Client.cs - клиент, получающий уведомление pu sh. Я не думаю, что проблема здесь:

    /* WEBSOCKET PART */
    //Variables for websocket
    private static object consoleLock = new object();
    private const int sendChunkSize = 256;
    private const int receiveChunkSize = 256;
    private const bool verbose = true;
    private static readonly TimeSpan delay = TimeSpan.FromMilliseconds(30000);

    //Function to check if a ticket from this client is closed/solved
    public void checkTicketSolved() {
        Thread.Sleep(1000);
        Connect("ws://localhost:5050/notifications/ws").Wait();
        Console.WriteLine("Press any key to exit...");
    }

    public static async Task Connect(string uri)
    {
        ClientWebSocket webSocket = null;

        try
        {
            webSocket = new ClientWebSocket();
            await webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
            await Task.WhenAll(Receive(webSocket), Send(webSocket));
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex);
        }
        finally
        {
            if (webSocket != null)
                webSocket.Dispose();
            Console.WriteLine();

            lock (consoleLock)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("WebSocket closed.");
                Console.ResetColor();
            }
        }
    }
   static UTF8Encoding encoder = new UTF8Encoding();

    private static async Task Send(ClientWebSocket webSocket)
    {

        //byte[] buffer = encoder.GetBytes("{\"op\":\"blocks_sub\"}"); //"{\"op\":\"unconfirmed_sub\"}");
        byte[] buffer = encoder.GetBytes("{\"op\":\"unconfirmed_sub\"}");
        await webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);

      while (webSocket.State == WebSocketState.Open)
        {
            LogStatus(false, buffer, buffer.Length);
            await Task.Delay(delay);
        }
    }

    private static async Task Receive(ClientWebSocket webSocket)
    {
        byte[] buffer = new byte[receiveChunkSize];
        while (webSocket.State == WebSocketState.Open)
        {
            var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            if (result.MessageType == WebSocketMessageType.Close)
            {
                await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
            }
            else
            {
                LogStatus(true, buffer, result.Count);
            }
        }
    }

    private static void LogStatus(bool receiving, byte[] buffer, int length)
    {
        lock (consoleLock)
        {
            Console.ForegroundColor = receiving ? ConsoleColor.Green : ConsoleColor.Gray;
            //Console.WriteLine("{0} ", receiving ? "Received" : "Sent");

            if (verbose)
                Console.WriteLine(encoder.GetString(buffer));

            Console.ResetColor();
        }
    }
}

Вы также можете найти этот код на Github . Соответствующие части кода находятся в:

  • WebServer / UI-MVC / Controllers / TicketController.cs -> Содержит функцию, запускаемую при нажатии кнопки.
  • WebServer / UI -MVC / Уведомления -> Содержит NotificationManager.cs и WebSocketsMiddleware.cs
  • WebServer / UI-MVC / Models -> Содержит Notification.cs
  • Клиент / содержит весь код клиентского консольного приложения

Чтобы дать вам некоторый контекст о приложении: Это приложение представляет собой систему тикетов, которая позволяет клиентам / клиентам, использующим мое программное обеспечение, открывать тикеты поддержки. Часть WebServer предназначена для администраторов и сотрудников, которые отвечают за тикеты и управляют ими. Консольное приложение - это то, что нужно установить моим клиентам / клиентам, чтобы связаться со службой поддержки и открыть заявку в службу поддержки. Когда администратор закрывает заявку клиента, нажимая кнопку, это означает, что заявка и, таким образом, проблема клиента была решена и закрыта. В результате клиент получает уведомление о * pu sh.

Я не ищу ссылки на другие учебные пособия по WebSockets или предложения, использующие SignalR вместо этого или что-то подобное, я уже прочитал их все, и я Я уже использовал SignalR, но сейчас заинтересован в чистых WebSockets. Я был бы очень благодарен за кого-то, кто мог бы помочь мне разработать первую часть кода, размещенного в этом вопросе (функция Close), и объяснить, что он сделал. Спасибо!

1 Ответ

0 голосов
/ 08 марта 2020

Я сам нашел решение.

Сначала я создал новый контроллер с именем NotificationsController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SC.UI.MVC.Models;
using NotificationsApi.Notifications;
//using NotificationsApi.Persistence;
using System.Net.Http;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace NotificationsApi.Controllers
{
    [Route("api/notifications")]
    public class NotificationsController : Controller
    {
        // GET api/notifications
        [HttpGet]
        public ActionResult Get()
        {
            try
            {
                var notifications = new List<Notification>();
                //notifications = PersistenceManager.Instance.GetNotifications();
                return Ok(notifications);
            }
            catch (Exception exception)
            {
                // log exception
                // TODO: implement logging

                // return a 500
                return StatusCode(500);
            }
        }

        // POST api/notifications
        [HttpPost]
        public async Task<ActionResult> Post(string message)
        {

            Notification notification = new Notification();
            notification.message = message;

            Console.WriteLine(message);

            try
            {
                // return a 400 if we didn't get a valid json payload in the body
                if (notification == null)
                    return BadRequest();

                await NotificationManager.Instance.SendNotificationAsync(notification);

                // we aren't returning the object to reference because POSTing a notification is fire and forget
                return Created(string.Empty, null);
            }
            catch (Exception exception)
            {
                // log the error
                // TODO: implement logging

                // return a 500
                return StatusCode(500);
            }

            return Ok();
        }
    }
}

Затем я прокомментировал ненужные свойства в Уведомлении. cs так что только Guid и сообщение остаются. Теперь я могу просто отправить уведомление, вызвав NotificationsController с запросом POST, содержащим параметр сообщения в качестве данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...