question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Azure WebJob not sending message to Azure SignalR service

See original GitHub issue

Describe the bug

I have an Asp.Net Core Api 3.1 and an Azure WebJob, both running on an Azure App Service. Both of them have a need to send notifications and neither will be receiving messages. To break it down:

  • I have a single instance of the Azure SignalR Service in the cloud that each app is pointed to.

  • The Hub class I’ve created is in a library that is referenced by both the Api and WebJob projects.

  • Clients will only connect to the Hub via the Api.

  • Everything is working fine when the Api sends a message to connections, but the WebJob does not.

I really don’t want to run the WebJob as a client because then I’d have to deal with auth. I just want it to run as another instance of the same Hub that will send messages to the same groups that the API does. Also, this WebJob is NOT a candidate to run as an Azure Function because it takes too long.

I’m missing something in how I’m configuring the WebJob as it appears it’s not connecting to Azure SignalR.

When the WebJob tries to send out a message, I get the following error: (I’ve not published yet to Azure so this is all happening on my local machine)

To Reproduce

WebJob Main:

using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MyProject.Data;
using MyProject.Hub;   
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureKeyVault;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

 public static IConfigurationRoot Configuration { get; protected set; }

    static async Task Main(string[] args)
    {    
        string buildConfig = "Development";           
        
        var builder = new HostBuilder()  
            .ConfigureWebJobs(b =>
            {
                b.AddAzureStorageCoreServices();
                b.AddTimers();                                     
            })
            .ConfigureAppConfiguration(config =>
            {                    
                config.AddJsonFile($"appsettings.{buildConfig}.json", true, true);  
                Configuration = config.Build();
            })                
            .ConfigureServices((hostContext, services) =>
            {                    
                services.AddSignalR().AddAzureSignalR(options => 
                {
                    options.ConnectionString = Configuration["Azure:SignalR:ConnectionString"];  
                });            
                
                services.AddHostedService<ApplicationHostService>();                   
                services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyDatabase")));
                services.AddScoped<MyDbContext>();     

           **// The NotificationService expects an injected MyDbContext and IHubContext<NotificationHub>**
                services.AddScoped<INotificationService, NotificationService>();
            })
            .ConfigureLogging((context, b) =>
            {
                b.AddConsole();
            });

        var host = builder.Build();
        using (host)
        {
            host.Run();
        }
    }
}

ApplicationHostService:

public class ApplicationHostService : IHostedService
{
    readonly ILogger<ApplicationHostService> _logger;
    readonly IConfiguration _configuration;
    readonly IHostingEnvironment _hostingEnvironment;
    public ApplicationHostService(
        ILogger<ApplicationHostService> logger,
        IConfiguration configuration,
        IHostingEnvironment hostingEnvironment
        )
    {
        _logger = logger;
        _configuration = configuration;
        _hostingEnvironment = hostingEnvironment;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await Task.CompletedTask;
        _logger.LogWarning("Application Host Service started.....");           
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {            
        _logger.LogWarning("Application Host Service stopped.....");
        await Task.CompletedTask;
    }
}

WebJob trigger code:

public class MyImportTrigger
{       
    private INotificationService _notificationService;        
  
    public MyImportTrigger(INotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    
    public async Task Run([TimerTrigger("0 */1 * * * *" )] TimerInfo myTimer,  ILogger log)
    {
        ....bunch of non-relevant removed code for brevity....
        
        await _notificationService.CreateFundImportNotificationAsync(upload);
           
        ....bunch of non-relevant removed code for brevity....                  
    }
}

NotificationService:

using ProjectName.Auth;
using ProjectName.Data;
using ProjectName.Utils;
using Microsoft.AspNetCore.SignalR;
using SignalR.Mvc;
using System;
using System.ComponentModel;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;


namespace ProjectName.Hub
{   
    public interface INotificationService
    {        
        FundPublishNotification CreateFundPublishNotification(short quarter, short year, string userName);        
    }

    public class NotificationService : INotificationService
    {
        MyDbContext _context;
        IHubContext<NotificationHub> _hubContext;

        public NotificationService(MyDbContext context, IHubContext<NotificationHub> hubContext)
        {
            _context = context;
            _hubContext = hubContext;
        }

      
        public async Task<FundImportNotification> CreateFundImportNotificationAsync(Upload upload)
        {
           *** removed: do some processing and db persistence to create an object called "notif" ***
             
            **** THIS IS WHERE IT BOMBS WHEN CALLED FROM WEBJOB, BUT DOESN'T WHEN CALLED FROM API******                                   

             ** the roles value is retrieved from the db and isn't dependent on a current user **

             _hubContext.Clients.Groups(roles).SendAsync("NewNotification", notif);           

            return notif;
        }
    }
}

The Hub class:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using MyProject.Auth;


namespace SignalR.Mvc
{
    [Authorize]    
    public class NotificationHub : Hub
    {

        public NotificationHub()
        { }

        public override async Task OnConnectedAsync()
        {
            await AddConnectionToGroups();
            await base.OnConnectedAsync();
        }

       
        public async Task AddConnectionToGroups()
        {
            var roles = Context.User.Roles();
            foreach (RoleValues role in roles)
            {
                await Groups.AddToGroupAsync(Context.ConnectionId, role.ToString());
            }
        }

       
        public override async Task OnDisconnectedAsync(Exception exception)
        {
            await RemoveConnectionToGroups();
            await base.OnDisconnectedAsync(exception);
        }

        public async Task RemoveConnectionToGroups()
        {
            var roles = Context.User.Roles();
            foreach (RoleValues role in roles)
            {
                await Groups.RemoveFromGroupAsync(Context.ConnectionId, role.ToString());
            }
        }
    }
}

appsettings.json:

{
 "Azure": {
    "SignalR": {
      "ConnectionString": "Endpoint=https://myproject-signalr-dev.service.signalr.net;AccessKey=removed-value-before-posting;Version=1.0;"
    }
  }
}

Exceptions (if any)

Microsoft.Azure.SignalR.ServiceLifetimeManager[100] Failed to send message (null). Microsoft.Azure.SignalR.Common.AzureSignalRNotConnectedException: Azure SignalR Service is not connected yet, please try again later. at Microsoft.Azure.SignalR.ServiceConnectionManager1.WriteAsync(ServiceMessage serviceMessage) at Microsoft.Azure.SignalR.ServiceLifetimeManagerBase1.<>c__DisplayClass22_01.<WriteAsync>b__0(T m) at Microsoft.Azure.SignalR.ServiceLifetimeManagerBase1.WriteCoreAsync[T](T message, Func`2 task)

Further technical details

  <PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.1.0" />
    <PackageReference Include="Azure.Storage.Blobs" Version="12.6.0" />
    <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
    <PackageReference Include="Microsoft.Azure.SignalR" Version="1.6.0" />
    <PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.6.0" />
    <PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.23" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="4.0.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.2" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.SignalRService" Version="1.2.2" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.8" />
    <PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.8" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.8" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.8" />

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
wanlwanlcommented, Nov 17, 2020

Hi @rachael-ross , your code only add AzureSignalR as a service into the service collection of the host. But you never actually connect to SignalR service.

Looks like your scenario is suitable to use SignalR service Management SDK. You can get a HubContext easily to send messages. For example:

var serviceManager = new ServiceManagerBuilder()
                    .WithOptions(option => 
                    {
                        option.ConnectionString = "<Your Azure SignalR Service Connection String>";
                    })
                    .Build();

try
{
    var hubcontext = await serviceManager.CreateHubContextAsync(hubName);

    // Broadcast
    hubContext.Clients.All.SendAsync(callbackName, obj1, obj2, ...);

    // Send to user
    hubContext.Clients.User(userId).SendAsync(callbackName, obj1, obj2, ...);

    // Send to group
    hubContext.Clients.Group(groupId).SendAsync(callbackName, obj1, obj2, ...);

    // add user to group
    await hubContext.UserGroups.AddToGroupAsync(userId, groupName);

    // remove user from group
    await hubContext.UserGroups.RemoveFromGroupAsync(userId, groupName);
}
finally
{
    await hubContext.DisposeAsync();
}

For more details, see Quick Start. To get a sample for negotiation/publishingMessages, see here.

0reactions
rachael-rosscommented, Dec 10, 2020

Using the ServiceManager worked great! Thank you!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Azure WebJob not sending SignalR message to ...
I have an Asp.Net Core Api 3.1 and an Azure WebJob, both running on an Azure App Service. Both of them have a...
Read more >
Azure WebJobs SignalR Service client library for .NET
SignalR output binding allows : send messages to all connections, to a connection, to a user, to a group. add/remove connections/users in a ......
Read more >
Azure WebJob not sending SignalR message to Azure ...
using Microsoft.Azure.SignalR.Management;. Run task: public class ImportTrigger { private INotificationService _notificationService; private IConfiguration ...
Read more >
How YOU can learn to build real-time Web Apps that ...
Create a UI that is able to connect to our Azure Function App and send/receive messages. # Provision an Azure SignalR Service. Go...
Read more >
QR Code Pings With Azure Functions and Azure SignalR
A fun little project where a QR code gets an animated rainbow border when it's scanned. The post contains a working demo and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found