0
votes

I am unable to resolve why my Azure SignalR Hub method is not being triggered?

Environment: Xamarin.Forms client

Disclaimer:

My LocationHub class is in a separate project that is referenced by the project that hosts my Azure Function.

Can Azure SignalR invoke a hub method that's in a separate library?

Server: Here's the hub class:

type LocationHub()  =

    inherit Hub()

    member x.SendLocation(v:SubjectLocation) =

        async { do! (x :> Hub).Clients.All.SendAsync($"{v.SessionId}", v) |> Async.AwaitTask } |> Async.StartAsTask

Server: Here's the Azure function that is suppose to trigger the method on the hub class:

public static class LocationFn
    {
        [FunctionName(nameof(LocationFn))]
        public static async Task<IActionResult> Run(
            [HttpTrigger(
                AuthorizationLevel.Anonymous,
                "post",
                Route = "locationfn")]
            HttpRequest req,
            [SignalR(HubName = "LocationHub")]
            IAsyncCollector<SignalRMessage> signalRMessages,
            ILogger log)
        {
            log.LogInformation($"{nameof(LocationFn)} has been invoked.");

            try
            {
                using (var streamReader = new StreamReader(req.Body))
                {
                    var json = await streamReader.ReadToEndAsync();
                    var subjectLocation = JsonConvert.DeserializeObject<SubjectLocation>(json);

                    await signalRMessages.AddAsync(
                        new SignalRMessage
                        {
                            Target    = "SendLocation",
                            Arguments = new[] { subjectLocation }
                        });

                    var message = Log(log, subjectLocation);

                    return new OkObjectResult(message);
                }
            }
            catch (Exception ex)
            {
                return new BadRequestObjectResult("There was an error: " + ex.Message);
            }
        }

        static string Log(ILogger log, SubjectLocation subjectLocation)
        {
            var location  = subjectLocation.Location;
            var latitude  = location.Latitude;
            var longitude = location.Longitude;

            var message = $"Received location: {subjectLocation.SubjectId} at '({latitude},{longitude})'";
            log.LogInformation($"{nameof(LocationFn)} {message}");
            return message;
        }
    }

Appendix:

Client: I have the following client request:

var sessionId = "some_session_id";
await CourierTracking.connectOn(sessionId, locationTracking(), "negotiatefn");

Client: The bird's-eye view of establishing a connection is implemented here:

open System.Diagnostics
open OrderRequest.SignalR.Client

module CourierTracking =

    let private onConnectionChanged (_,_) = ()
    let private onMessageReceived msg = Debug.WriteLine(sprintf "%A" msg)

    let private signalR = SignalRService();

    let connectOn(sessionId:string) (serviceHost:string) (resourceName:string) =

        signalR.Connected        .Add onConnectionChanged
        signalR.ConnectionFailed .Add onConnectionChanged
        signalR.MessageReceived  .Add onMessageReceived

        async {
        
            do! signalR.ConnectOn(serviceHost, resourceName, sessionId) |> Async.AwaitTask
        
        } |> Async.StartAsTask

Client: Here's the core implementation for connecting and receiving messages:

public class SignalRService
{
    HttpClient _client = new HttpClient();

    public delegate void MessageReceivedHandler(object sender, CourierLocation message);
    public delegate void ConnectionHandler(object sender, bool successful, string message);

    public event MessageReceivedHandler MessageReceived;
    public event ConnectionHandler      Connected;
    public event ConnectionHandler      ConnectionFailed;

    public bool IsConnected { get; private set; }
    public bool IsBusy      { get; private set; }

    public async Task ConnectOn(string host, string nameOfNegotiationFn, string sessionId)
    {
        try
        {
            IsBusy = true;

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            var negotiateJson = await _client.GetStringAsync($"{host}{nameOfNegotiationFn}");
            var negotiate     = JsonConvert.DeserializeObject<NegotiateInfo>(negotiateJson);

            var connection = new HubConnectionBuilder()
                .AddNewtonsoftJsonProtocol()
                .WithUrl(negotiate.Url, options => options.AccessTokenProvider = async () => negotiate.AccessToken)
                .Build();

            connection.Closed += Connection_Closed;
            connection.On<JObject>(sessionId, OnIncomingMessage);
            await connection.StartAsync();

            IsConnected = true;
            IsBusy = false;

            Connected?.Invoke(this, true, "Connection successful.");
        }

        catch (Exception ex)
        {
            ConnectionFailed?.Invoke(this, false, ex.Message);
            IsConnected = false;
            IsBusy = false;
        }
    }

    Task Connection_Closed(Exception arg)
    {
        ConnectionFailed?.Invoke(this, false, arg.Message);
        IsConnected = false;
        IsBusy = false;
        return Task.CompletedTask;
    }

    void OnIncomingMessage(JObject message)
    {
        var courierId = message.GetValue("SubjectId").ToString();
        var location  = message.SelectToken("Location");
        var latitude  = double.Parse(location.SelectToken("Latitude").ToString());
        var longitude = double.Parse(location.SelectToken("Longitude").ToString());

        var courierLocation = new CourierLocation(courierId, new Coordinate(latitude, longitude));
        MessageReceived?.Invoke(this, courierLocation);
    }
}
1

1 Answers

0
votes

I needed the client to pass in the exact name of the hub method that it subscribes to:

var hubMethodName = "LocationUpdate";

...

var connection = new HubConnectionBuilder()
    .AddNewtonsoftJsonProtocol()
    .WithUrl(negotiate.Url, options => options.AccessTokenProvider = async () => negotiate.AccessToken)
    .Build();

connection.Closed -= Connection_Closed;
connection.Closed += Connection_Closed;
connection.On<JObject>(hubMethodName, OnIncomingMessage); //  ** REF: HUB METHOD NAME **
await connection.StartAsync();