Рисовалка на SignalR

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 31.10.2015

Чат - наиболее показательное решение, которое можно сделать с помощью SignalR. Однако чатами все не ограничивается. И попробуем создать графическое приложение с использованием SignalR, в котром есть общее полотно, и каждый подключившийся к приложению может что-то нарисовать, и все будут это видеть.

Итак, создадим проект ASP.NET MVC 5 с типом аутентификации No Authentication. Я назвал свой проект SignalRDraw. И сразу же через менеджер NuGet добавим в проект библиотеку SignalR.

Сразу же создадим клиента для приложения. Для этого добавим в проект новый файл index.html прямо в корень проекта. Затем нажмем на этот файл правой кнопкой мыши и в контекстном меню выберем Set as Start Page (Сделать начальной страницей). Таким образом, мы не будем использовать ни контроллеры, ни их представления. Весь функционал клиента у нас будет содержать эта веб-страничка. А каталоги Controllers и Views в принципе в обще можно удалить.

Теперь изменим код файла index.html на следующий:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <style>
        canvas {
            position: relative;
            background-color:#ffd800;
        }
    </style>

</head>
<body>
    <canvas id='drawingpad' width='400' height='300'></canvas>

    <script src="/Scripts/jquery-1.10.2.min.js"></script>
    <!--Ссылка на библиотеку SignalR -->
    <script src="/Scripts/jquery.signalR-2.1.0.min.js"></script>
    <!--Ссылка на автоматически сгенерированный скрипт хаба SignalR -->
    <script src="/signalr/hubs"></script>
    <script>
        
        $(function () {

            var drawGame = {
                // указывает, происходит ли отрисовка
                isDrawing: false,
                // начальная точка следующей линии
                startX: 0,
                startY: 0,
            };
            // модель линий
            var data = {
                startX:0,
                startY:0,
                endX:0,
                endY:0
            };
            // контекст элемента canvas
            var canvas = document.getElementById('drawingpad');
            var ctx = canvas.getContext('2d');
            
             // Ссылка на автоматически-сгенерированный прокси хаба
             var chat = $.connection.drawHub;
            // Объявление функции, которая хаб вызывает при получении сообщений
            chat.client.addLine = function (data) {

                // Добавление линий
                drawLine(ctx, data.startX, data.startY, data.endX, data.endY, 1);
            };

            // Открываем соединение
            $.connection.hub.start().done(function () {
                    // после открытия соединения устанавливаем обработчики мыши
                    canvas.addEventListener("mousedown", mousedown, false);
                    canvas.addEventListener("mousemove", mousemove, false);
                    canvas.addEventListener("mouseup", mouseup, false);
            });
            // просто рисуем линию
            function drawLine(ctx, x1, y1, x2, y2, thickness) {
                ctx.beginPath();
                ctx.moveTo(x1, y1);
                ctx.lineTo(x2, y2);
                ctx.lineWidth = thickness;
                ctx.strokeStyle = "#444";
                ctx.stroke();
            }
            // нажите мыши
            function mousedown(e) {

                // получаем позиции x и y относительно верхнего левого угла элемента canvas
                var mouseX = e.layerX || 0;
                var mouseY = e.layerY || 0;
                drawGame.startX = mouseX;
                drawGame.startY = mouseY;
                drawGame.isDrawing = true;
            };

            // перемещение мыши
            function mousemove(e) {

                // рисуем линию, если isdrawing==true
                if (drawGame.isDrawing) {

                    // получаем позиции x и y относительно верхнего левого угла элемента canvas
                    var mouseX = e.layerX || 0;
                    var mouseY = e.layerY || 0;
                    if (!(mouseX == drawGame.startX &&
                          mouseY == drawGame.startY)) {
                            drawLine(ctx, drawGame.startX,
                            drawGame.startY, mouseX, mouseY, 1);

                            data.startX = drawGame.startX;
                            data.startY = drawGame.startY;
                            data.endX = mouseX;
                            data.endY = mouseY;
                            chat.server.send(data);

                            drawGame.startX = mouseX;
                            drawGame.startY = mouseY;
                        }
                    }
                };

                function mouseup(e) {
                    drawGame.isDrawing = false;
                }
        });
    </script>
</body>
</html>

Разметка html содержит всего один элемент canvas - полотно для рисования, больше нам ничего не надо.

В коде javascript вначале определяются две модели: drawGame - для использования внутри клиента и data - для взаимодействия с сервером.

Чтобы непоседственно рисовать примитивы на полотне, нам надо получить canvas и его контекст:

var canvas = document.getElementById('drawingpad');
var ctx = canvas.getContext('2d');

Далее получаем ссылку на хаб. В данном случае класс хаба на сервере, который мы позже создадим, будет называться DrawHub. А с помощью функции addLine клиент будет получать с сервера данные и по ним рисовать линии.

var chat = $.connection.drawHub;
chat.client.addLine = function (data) {

	drawLine(ctx, data.startX, data.startY, data.endX, data.endY, 1);
};

Затем в функции $.connection.hub.start().done(function ()... добавляются обработчики событий мыши, с поомщью которых можно рисовать линии и отправлять сообщения об этих линиях на сервер.

Теперь определим на уровне сервера модель, которая будет представлять данные, получаемые от клиента. Добавим в папку Models новый класс Data:

using System;
using Newtonsoft.Json;

namespace SignalRDraw.Models
{
    public class Data
    {
        [JsonProperty("startX")]
        public float StartX { get; set; }
        [JsonProperty("startY")]
        public float StartY { get; set; }
        [JsonProperty("endX")]
        public float EndX { get; set; }
        [JsonProperty("endY")]
        public float EndY { get; set; }
    }
}

Каждое свойство для связи с моделью data, определенной в javscript, имеет атрибут JsonProperty.

Теперь добавим в проект прямо в корень новый класс хаба:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using SignalRDraw.Models;

namespace SignalRDraw
{
    public class DrawHub : Hub
    {
        public void Send(Data data)
        {
            Clients.AllExcept(Context.ConnectionId).addLine(data);
        }
    }
}

С помощью метода Clients.AllExcept() мы можем исключить из рассылки сообщений того клиента, который собственно и прислал сообщений, и таким образом, избежать ситуации, когда у него будет дублироваться нарисованная линия. А метод addLine собственно и выполняет рыссылку всем подключенным клиентам и задействует на клиенте одноименный метод addLine, который выполняет рисование.

Ну и также добавим в проект класс Startup, чтобы задействовать SignalR:

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(SignalRDraw.Startup))]

namespace SignalRDraw
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

В итоге получится следующая структура

И теперь мы можем запустить проект и открыть запущенную страницу в разных браузерах и протестировать приложение:

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850