Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
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>
Общий проект сервера:
Теперь создадим клиентское приложение на Xamarin. Для этого опеделим новый проект Xamarin Forms. И прежде всего добавим во все проекты решения через пакетный менеджер Nuget пакет Microsoft.AspNetCore.SignalR.Client.
В главном проекте определим класс 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:
Запустим сначала приложение ASP.NET Core, а затем приложение на Xamarin. Чтобы сделать приложение ASP.NET Core доступным для всех устройств, подключенных к одной и той же локальной сети, можно использовать описанный здесь второй способ.