Клиент на Xamarin Forms

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

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

Xamarin Forms позволяет создавать клиентские приложения, которые могут взаимодействовать с хабом SignalR на стороне сервера. Рассмотрим, как это сделать.

Создание сервера

Сначала определим код сервера, с которым будет взаимодействовать клиент на Xamarin. Для этого создадим проект ASP.NET Core по типу Empty.

Определим в проекте следующий простейший класс хаба:

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

namespace SignalRService
{
    public class ChatHub : Hub
    {
        public async Task Send(string username, string message)
        {
            await this.Clients.All.SendAsync("Receive", username, message);
        }
    }
}

В методе Send хаб будет принимать имя пользователя и его сообщение и транслировать его на функцию Receive всех подключенных клиентов.

В классе Startup определим следующий код:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace SignalRService
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseDefaultFiles();
            app.UseStaticFiles();

            app.UseRouting();
 
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHub<ChatHub>("/chat");
            });
        }
    }
}

И также для теста определим в папке wwwroot простейшую веб-страницу index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>SignalR Chat</title>
</head>
<body>
    <div id="loginBlock">
        Введите логин:<br />
        <input id="userName" type="text" />
        <input id="loginBtn" type="button" value="Войти" />
    </div><br />

    <div id="header"></div><br />

    <div id="inputForm">
        <input type="text" id="message" />
        <input type="button" id="sendBtn" value="Отправить" />
    </div>
    <div id="chatroom"></div>

    <script src="js/signalr.min.js"></script>
    <script>
        let hubUrl = "http://localhost:62432/chat";
        const hubConnection = new signalR.HubConnectionBuilder()
            .withUrl(hubUrl)
            .configureLogging(signalR.LogLevel.Information)
            .build();
        let userName = "";
        // получение сообщения от сервера
        hubConnection.on("Receive", function (userName, message) {

            // создаем элемент <b> для имени пользователя
            let userNameElem = document.createElement("b");
            userNameElem.appendChild(document.createTextNode(userName + ": "));

            // создает элемент <p> для сообщения пользователя
            let elem = document.createElement("p");
            elem.appendChild(userNameElem);
            elem.appendChild(document.createTextNode(message));

            var firstElem = document.getElementById("chatroom").firstChild;
            document.getElementById("chatroom").insertBefore(elem, firstElem);

        });

        // установка имени пользователя
        document.getElementById("loginBtn").addEventListener("click", function (e) {
            userName = document.getElementById("userName").value;
            document.getElementById("header").innerHTML = "<h3>Welcome " + userName + "</h3>";
        });
        // отправка сообщения на сервер
        document.getElementById("sendBtn").addEventListener("click", function (e) {
            let message = document.getElementById("message").value;
            hubConnection.invoke("Send", userName, message);
        });

        hubConnection.start();
    </script>
</body>
</html>

Общий проект сервера:

SignalR Hub для работы с Xamarin Forms

Создание клиента Xamarin Forms

Теперь создадим клиентское приложение на Xamarin. Для этого опеделим новый проект Xamarin Forms. И прежде всего добавим во все проекты решения через пакетный менеджер Nuget пакет Microsoft.AspNetCore.SignalR.Client.

Microsoft.AspNetCore.SignalR.Client в Xamarin Forms

В главном проекте определим класс MessageData, который будет представляет полученные с сервера данные:

public class MessageData
{
	public string Message { get; set; }
	public string User { get; set; }
}

То есть объект MessageData будет содержать данные о сообщении и отправившем его пользователе.

Также добавим в главный проект класс ChatViewModel, который будет выполнять роль модели представления и через который будет идти взаимодействие с сервером:

using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace XamSignalRClient
{
    public class ChatViewModel : INotifyPropertyChanged
    {
        HubConnection hubConnection;

        public string UserName { get; set; }
        public string Message { get; set; }
		// список всех полученных сообщений
        public ObservableCollection<MessageData> Messages { get; }
		
		// идет ли отправка сообщений
        bool isBusy;
        public bool IsBusy
        {
            get => isBusy;
            set
            {
                if (isBusy != value)
                {
                    isBusy = value;
                    OnPropertyChanged("IsBusy");
                }
            }
        }
		// осуществлено ли подключение
        bool isConnected;
        public bool IsConnected
        {
            get => isConnected;
            set
            {
                if (isConnected != value)
                {
                    isConnected = value;
                    OnPropertyChanged("IsConnected");
                }
            }
        }
		// команда отправки сообщений
        public Command SendMessageCommand { get; }

        public ChatViewModel()
        {
            // создание подключения
            hubConnection = new HubConnectionBuilder()
                .WithUrl("http://192.168.0.103:3000/chat")
                .Build();

            Messages = new ObservableCollection<MessageData>();

            IsConnected = false;	// по умолчанию не подключены
            IsBusy = false;			// отправка сообщения не идет

            SendMessageCommand = new Command(async () => await SendMessage(), () => IsConnected);

            hubConnection.Closed += async (error) =>
            {
                SendLocalMessage(String.Empty, "Подключение закрыто...");
                IsConnected = false;
                await Task.Delay(5000);
                await Connect();
            };

            hubConnection.On<string, string>("Receive", (user, message) =>
            {
                SendLocalMessage(user, message);
            });
        }
        // подключение к чату
        public async Task Connect()
        {
            if (IsConnected)
                return;
            try
            {
                await hubConnection.StartAsync();
                SendLocalMessage(String.Empty, "Вы вошли в чат...");

                IsConnected = true;
            }
            catch (Exception ex)
            {
                SendLocalMessage(String.Empty, $"Ошибка подключения: {ex.Message}");
            }
        }

        // Отключение от чата
        public async Task Disconnect()
        {
            if (!IsConnected)
                return;
            
            await hubConnection.StopAsync();
            IsConnected = false;
            SendLocalMessage(String.Empty, "Вы покинули чат...");
        }

        // Отправка сообщения
        async Task SendMessage()
        {
            try
            {
                IsBusy = true;
                await hubConnection.InvokeAsync("Send", UserName, Message);
            }
            catch (Exception ex)
            {
                SendLocalMessage(String.Empty, $"Ошибка отправки: {ex.Message}");
            }
            finally
            {
                IsBusy = false;
            }
        }
        // Добавление сообщения
        private void SendLocalMessage(string user, string message)
        {
            Messages.Insert(0, new MessageData
            {
                Message = message,
                User = user
            });
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string prop = "")
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }
}

Раберем код этого класса. Для взаимодействия с хабом нам потребует класс HubConnection, который предоставляет нам функционал для подключения к хабу и отправки сообщений.

Свойства UserName и Message представляют соответственно имя пользователя и текст сообщения, которые будут отправляться на сервер. Свойство Messages представляет объект ObservableCollection<MessageData> - полученные с сервера сообщения.

Чтобы извещать пользователя о процессе отправки, определено свойство IsBusy - если оно равно true, то приложение находится в процессе оправки сообщения.

Свойство IsConnected указывает, подключено ли приложение к хабу.

Непосредственно для отправки сообщений определена команда SendMessageCommand.

В конструкторе ChatViewModel с помощью класса HubConnectionBuilder создается объект HubConnection. Для его инициализации через метод WithUrl() передается адрес хаба:

hubConnection = new HubConnectionBuilder()
	.WithUrl("http://192.168.0.103:3000/chat")
	.Build();

В каждом конктретном случае адрес будет отличаться.

Затем определяется команда отправки сообщений:

SendMessageCommand = new Command(async () => await SendMessage(), () => IsConnected);

При выполнении команды будет вызываться метод SendMessage. Кроме того, команда будет доступна, если свойство IsConnected равно true, то есть если мы подключены к хабу.

Получив объект HubConnection, мы можем выполнить его настройку. Так, далее устанавливается обрабатчик события завершения подключения:

hubConnection.Closed += async (error) =>
{
	SendLocalMessage(String.Empty, "Подключение закрыто...");
	IsConnected = false;
	await Task.Delay(5000);
	await Connect();
};

При закрытии подключения, которое может происходить по самым разным причинам, коллекцию Messages добавляется диагностическое сообщение для пользователя (поэтому вместо имени пользователя используется пустая строка String.Empty) и затем через 5 секунд мы повторно пытаемся подключиться к хабу.

Кроме того, нам надо настроить прием сообщений. Для этого применяется метод On:

hubConnection.On<string, string>("Receive", (user, message) =>
{
	SendLocalMessage(user, message);
});

В классе хаба мы транслируем всем подключенным клиентам на функцию Receive две строки: this.Clients.All.SendAsync("Receive", username, message);. Поэтому в данном случае метод On() типизирован двумя объектами string - для получения имени пользователя и его сообщения. Первый парамет метода указавает название функции - Receive, а второй параметр представляет лямбда-выражение, в котором мы получаем от сервера данные.

В методе Connect() осуществляется подключение к хабу. Для этого применяется вызов hubConnection.StartAsync(). После его успешного выполнения мы можем взаимодействовать с сервером.

В методе Disconnect() происходит отключение от сервера. Для этого применяется вызов hubConnection.StopAsync()

Метод SendMessage() предназначен для отправки сообщений хабу. Это осуществляется посредством вызова hubConnection.InvokeAsync("Send", UserName, Message); - на хабе вызывается метод Send, которому передаются значения UserName и Message.

Теперь используем этот класс. Для этого определим к следующий интерфейс на странице MainPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamSignalRClient"
             x:Class="XamSignalRClient.MainPage">
    <StackLayout>
        <ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}"
                           HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/>
        <StackLayout Padding="10">
            <Label FontSize="Small" Text="Логин" VerticalOptions="Center"/>
            <Entry x:Name="userNameBox"  Text="{Binding UserName}" HorizontalOptions="FillAndExpand"/>
            <Label FontSize="Small" Text="Сообщение" VerticalOptions="Center"/>
            <Entry HorizontalOptions="FillAndExpand" Text="{Binding Message}"/>
            <Button Text="Отправить" IsEnabled="{Binding IsConnected}" Command="{Binding SendMessageCommand}"/>
        </StackLayout>
        <ListView ItemsSource="{Binding Messages}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <ViewCell.View>
                            <StackLayout Orientation="Horizontal">
                                <Label Text="{Binding User}" FontAttributes="Bold" />
                                <Label Text="{Binding Message}" />
                            </StackLayout>
                        </ViewCell.View>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

Элемент ActivityIndicator извещает пользователя об процессе отправки сообщений. Для ввода данных определены два текстовых поля. И по нажатию на кнопку вызывается команда SendMessageCommand, которая оправляет введенные данные.

Для отображения сообщений определен элемент ListView.

В файле MainPage.xaml.cs определим привязку ChatViewModel к странице:

using Xamarin.Forms;

namespace XamSignalRClient
{
    public partial class MainPage : ContentPage
    {
        ChatViewModel viewModel;
        public MainPage()
        {
            InitializeComponent();
            viewModel = new ChatViewModel();
            this.BindingContext = viewModel;
        }

        protected override async void OnAppearing()
        {
            base.OnAppearing();
            await viewModel.Connect();
        }

        protected override async void OnDisappearing()
        {
            base.OnDisappearing();
            await viewModel.Disconnect();
        }
    }
}

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

Итоговый проект для Xamarin:

Проект Xamarin для работы с SignalR

Запустим сначала приложение ASP.NET Core, а затем приложение на Xamarin. Чтобы сделать приложение ASP.NET Core доступным для всех устройств, подключенных к одной и той же локальной сети, можно использовать описанный здесь второй способ.

Xamarin Forms и ASP.NET Core SignalR
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850