Бекенд на C# Web Api

To Kaiten

Дополнение: SignalR

Для чего нужен

SignalR — это библиотека для ASP.NET, которая упрощает процесс добавления функциональности реального времени в приложения. Она дает возможность реализовать модель «publisher-subscriber» на стороне сервера. Клиенты подписываются на так называемый хаб, а он, в свою очередь, может отправлять им обновления.

Таким образом, наш сервер на C# WebApi может отправлять уведомления всем клиентам, или каким-то определенным клиентам, в реальном времени. Это особенно может быть полезно в бекенде для игровых приложений.

Установка и настройка

  • Сперва необходимо установить пакет Microsoft.AspNetCore.SignalR.Common.

  • Далее создаем наш хаб. Это класс, унаследованный от Hub:

using Microsoft.AspNetCore.SignalR;

public class NotificationHub : Hub
{
    public async Task SendMessageToUser(string userId, string message)
    {
        await Clients.User(userId).SendAsync("ChatMessage", message);
    }

    public async Task SendMessageToAll(string message)
    {
        await Clients.All.SendAsync("ChatMessage", message);
    }
}

Этот класс может быть пустой, методы SendMessageToUser и SendMessageToAll представлены для примера.

  • Затем регистрируем хаб в Program:

// хаб доступен по адресу https://myservice.com/notificationHub
app.MapHub<NotificationHub>("/notificationHub");
  • Чтобы отправить уведомление пользователям необходимо воспользоваться созданным классом NotificationHub. В этом примере контроллер получает ссылку на хаб (с помощью DI) и внутри определенного запроса отправляет уведомление всем клиентам:

[ApiController]
[Route("api/[controller]")]
public class GameEventController : ControllerBase
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public GameEventController(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }

    [HttpPost]
    public async Task<IActionResult> StartEvent([FromBody] MessageData data)
    {
        await _service.ChangeEventStatus(eventId, EventStatus.InProgress);
        await _hubContext.Clients.All.SendAsync("ChatMessage", new NotificationData()
        {
            chatId = data.chatId,
            message = data.Message,
        });

        return Ok();
    }
}

Первый параметр "ChatMessage" — название уведомления, второй — произвольные данные, которые отправляются вместе с этим ивентом.

Теперь рассмотрим как клиенты могут получить и обработать уведомление.

Интеграция с Unity

В качестве примера рассмотрим получение уведомлений клиентами на Unity.

  • Для этого необходимо написать небольшой JS плагин, а также класс, который использует плагин из Unity.

const library = {

    // Class definition.

    $signalRSdk: {
	
      connection: null,

      initializeSignalR: function(url, chatMessageCallbackPtr) {
          var hubUrl = UTF8ToString(url);
	  var tmpConnection = new signalR.HubConnectionBuilder().withUrl(hubUrl);
		
	  connection = tmpConnection.build();
		
          connection.on("ChatMessage", function (message) {
	      signalRSdk.jsonCallback(message, chatMessageCallbackPtr);
	  });
		
          connection.start().catch(function (err) {
	      console.error(err.toString());
	  });
      },
		
      sendMessageToUser: function(userId, message) {
          connection.invoke("SendMessageToUser", userId, message).catch(function (err) {
	      console.error(err.toString());
          });
      },
		
      jsonCallback: function (result, callbackPtr) {
          const entriesJson = JSON.stringify(result);
          const stringBufferSize = lengthBytesUTF8(entriesJson) + 1;
          const stringBufferPtr = _malloc(stringBufferSize);
          stringToUTF8(entriesJson, stringBufferPtr, stringBufferSize);
          dynCall('vi', callbackPtr, [stringBufferPtr]);
          _free(stringBufferPtr);
      },
    },

    // External C# calls.

    InitializeSignalR: function(url, chatMessageCallbackPtr) {
        signalRSdk.initializeSignalR(url, chatMessageCallbackPtr);
    },
    
    SendMessageToUser: function(userId, message) {
        signalRSdk.sendMessageToUser(userId, message);
    },
}

autoAddDeps(library, '$signalRSdk');
mergeInto(LibraryManager.library, library);
public static class SignalRNotifications
{
    private static event Action<NotificationData> s_onChatMessageReceived;

    public static void Initialize(string url, Action<NotificationData> onChatMessageReceived = null)
    {
        s_onChatMessageReceived = onChatMessageReceived;
#if UNITY_WEBGL && !UNITY_EDITOR
        InitializeSignalR(url, OnChatMessageReceived);
#else
        Debug.LogError($"{nameof(SignalRNotifications)} is only supported on WebGL.");
#endif
    }

    [DllImport("__Internal")]
    private static extern void InitializeSignalR(string url, Action<string> onChatMessageReceived);

    [DllImport("__Internal")]
    private static extern void SendMessageToUser(string userId, string message);

    [MonoPInvokeCallback(typeof(Action<string>))]
    private static void OnChatMessageReceived(string message)
    {
        NotificationData notification = JsonUtility.FromJson<NotificationData>(message);
        s_onChatMessageReceived?.Invoke(notification);
    }
}
  • Если плагин используется в WebGL, то в WebGL шаблон нужно добавить такую строчку внутри тега <head>:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.18/signalr.min.js"></script>
  • В результате можно пользоваться классом SignalRNotifications и подписываться на события, которые могут отправлять другие клиенты.


Ссылки

Хабр — SignalR Core. «Hello Habr!»


Автор документа: Артём Ветик