skip to Main Content

I have a function that is used to store data. This data is monitored in a frontend and when there is new data, I want to notify my frontend about that.
I have achieved that by using SignalR, which to work requires me to return SignalRMessageAction from my function instead of typical HttpResponseData.

This is functioning, however there are some problems and questions that I have.

  1. I have error-handling middleware that is configured to catch all of my custom exception, however when I throw an exception from a function that returns SignalRMessageAction, it is not caught and my server return 500 instead of meaningful exception.

  2. Imagine that I want to have GET request that when triggered, will also notify subscribers and will return requested data and not the SignalRMessageAction.

Here is simplified example how I use it now.

[Function("StoreGeofenceMessage")]
[SignalROutput(HubName = "myhub")] //Hub name is not actually used anywhere
public async Task<SignalRMessageAction> RunStoreMessage(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "v1/messages")] HttpRequestData req,
        FunctionContext executionContext)
    {
        var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);

        MessageType messageType = data.type;;

        int geofenceId = data.geofenceId;
        DateTime timestamp = data.timestamp;

        var geofenceMessage = new GeofenceMessage(geofenceId, timestamp, messageType);
        messagesService.StoreMessage(geofenceMessage);

        return new SignalRMessageAction("dashboardUpdate")
        {
            Arguments = new object[] { }
        };
    }

This is the Negotiate function

[Function("Negotiate")]
public SignalRConnectionInfo Negotiate(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "v1/negotiate")]
        HttpRequestData req,
        [SignalRConnectionInfoInput(HubName = "myhub")]
        SignalRConnectionInfo connectionInfo)
    {
        return connectionInfo;
    }

I have tried to search for more information, but couldn’t find something useful.
I expect that I will have control over the response from my server and will be able to notify my subscribers in some other form.

2

Answers


  1. Chosen as BEST ANSWER

    I have managed to solve my problem with a little bit more elegant solution that the one provided by Sampath (which I think that also should work).

    I have used ServiceHubContext to send messages instead of the output binding, so I have made this service:

    public class SignalRService: IHostedService
    {
        public ServiceHubContext MessageHubContext { get; private set; }
    
        private const string MESSAGE_HUB = "myhub";
        private readonly IConfiguration configuration;
        private readonly ILoggerFactory loggerFactory;
    
        public SignalRService(IConfiguration configuration, ILoggerFactory 
        loggerFactory)
        {
            this.configuration = configuration;
            this.loggerFactory = loggerFactory;
        }
    
        async Task IHostedService.StartAsync(CancellationToken cancellationToken)
        {
             using var serviceManager = new ServiceManagerBuilder()
                .WithOptions(o => o.ConnectionString = 
     Environment.GetEnvironmentVariable("AzureSignalRConnectionString"))
                .WithLoggerFactory(loggerFactory)
                .BuildServiceManager();
            MessageHubContext = await 
    serviceManager.CreateHubContextAsync(MESSAGE_HUB, cancellationToken);
        }
    
        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }
    
        public async Task SendMessage(string target)
        {
            await MessageHubContext.Clients.All.SendAsync(target);
        }
    }
    

    and then just inject that service like:

    s.AddSingleton<SignalRService>();
    s.AddHostedService(sp => sp.GetRequiredService<SignalRService>());
    

    Credits to hannes neukermans in this post.


  2. To achieve returning HttpResponseData instead of SignalRMessageAction from your Azure Functions Isolated while still notifying subscribers through SignalR, you can follow these steps:

    • Separate SignalR Invocation and HTTP Response:

      • Instead of directly returning SignalRMessageAction, you can notify subscribers through SignalR and then return an appropriate HTTP response.
    • Handle SignalR Invocation:

      • After storing the message, notify subscribers through SignalR.
    • Return HttpResponseData:

      • Once the message is stored and subscribers are notified, return the desired HTTP response. Code was taken from git.
    • The sample code below uses HttpResponseData.

    Code:

     [Function("index")]
     public HttpResponseData GetWebPage([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req)
     {
         var response = req.CreateResponse(HttpStatusCode.OK);
         response.WriteString(File.ReadAllText("content/index.html"));
         response.Headers.Add("Content-Type", "text/html");
         return response;
     }
    
     [Function("negotiate")]
     public async Task<HttpResponseData> Negotiate([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
     {
         _logger.LogInformation("C# HTTP trigger function processed a request.");
    
         var negotiateResponse = await MessageHubContext.NegotiateAsync(new() { UserId = req.Headers.GetValues("userId").FirstOrDefault() });
         var response = req.CreateResponse();
         // We need to make sure the response JSON naming is camelCase, otherwise SignalR client can't recognize it.
         await response.WriteAsJsonAsync(negotiateResponse, JsonObjectSerializer);
         return response;
     }
    
    
    
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search