Live updates with SignalR - stefffdev/NubeSync GitHub Wiki

When a client pushes its changes to the server, all other clients can be notified via SignalR to trigger a sync, which gives the user a "realtime" experience when updating data.

The payload in this notification can be kept very simple, as the notification only triggers a sync on the client.

Add the notification hub to the server

  1. Create a new file UpdateHub.cs with the following contents:
using Microsoft.AspNetCore.SignalR;

namespace Nube.SampleService.Hubs
{
    public class UpdateHub : Hub
    {
    }
}
  1. In the Startup.cs file in the server project, add the following code to the ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSignalR();
}
  1. Configure the hub routing in the Configure method of Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapHub<UpdateHub>("/updateHub");
    });
}
  1. Trigger a update to that hub whenever a client synced operations, make your OperationsController.cs look like this:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Nube.SampleService.Hubs;
using Nube.Server;
using Nube.Server.Data;
using NubeSync.Service.Data;

namespace NubeSync.Service.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class OperationsController : ControllerBase
    {
        private readonly DataContext _context;
        private readonly IOperationService _operationService;
        private readonly IHubContext<UpdateHub> _hubContext;

        public OperationsController(
            DataContext context,
            IOperationService operationService,
            IHubContext<UpdateHub> hubContext)
        {
            _context = context;
            _operationService = operationService;
            _hubContext = hubContext;
        }

    [HttpPost]
    public async Task<IActionResult> PostOperationsAsync(List<NubeOperation> operations)
    {
        try
        {
            await _operationService.ProcessOperationsAsync(_context, operations);
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);
        }

        await _hubContext.Clients.All.SendAsync("Update", "user", "message");
        return Ok();
    }
}

Add a notification listener to the Xamarin Forms client:

  1. Install the nuget package Microsoft.AspNetCore.SignalR.Client in all your projects
  2. Add the following method to MainPage.xaml.cs:
private async Task ConfigureSignalRAsync()
{
    string hubUrl = "https://localhost:5001/updateHub";

    if (DeviceInfo.Platform == DevicePlatform.Android)
    {
        hubUrl = "https://10.0.2.2:5001/updateHub";
    }

    var hubConnection = new HubConnectionBuilder()
        .WithUrl(hubUrl, (opts) =>
        {
            opts.HttpMessageHandlerFactory = (message) =>
            {
                if (message is HttpClientHandler clientHandler2)
                    // bypass the SSL certificate for local debugging
                    clientHandler2.ServerCertificateCustomValidationCallback +=
                        (sender, certificate, chain, sslPolicyErrors) => { return true; };
                   return message;
                };
            }).Build();

            hubConnection.Closed += async (error) =>
            {
                await Task.Delay(new Random().Next(0, 5) * 1000);
                await hubConnection.StartAsync();
            };

            hubConnection.On<string, string>("Update", async (user, message) =>
            {
                await SyncAsync();
            });
            
            await hubConnection.StartAsync();
        }
}
  1. Call the method ConfigureSignalRAsync(); in ContentPage_Appearing

That's it, if you run the app on multiple devices, changes made on one device should appear on the other devices almost immediatly!

Mind that this is a very primitive implementation, that should be refined for the use in production, as for example a sync by a certain client also triggers a unnecessary sync on that client itself.

⚠️ **GitHub.com Fallback** ⚠️