'How do I add SignalR to a .NET Core Windows Service?
I can find tutorials to add it to MVC, and even from 2014, an article explaining how to add it to .NET 4.7 windows service.
However with a .NET Core 3.1 windows service, I just cannot figure out how to do it.
Most tutorials seem to revolve around a Startup.cs file which does not exist in a windows service. This is the latest tutorial I could find from Microsoft but it uses a Web App rather than a windows service.
The windows service runs using this code:
var builder = new HostBuilder() .ConfigureServices((hostContext, services) => { services.AddHostedService<MyWindowsService>(); });
I assume that SignalR needs to be set up around here.
I found some evidence you can do WebApp.Start("http://localhost:8080"); but this is OWIN. The example I found (an older version of the one above) then has a Startup class but there's no indication how this class is called. It takes an IAppBuilder and there's a method on it to add SignalR. However IAppBuilder does not appear to be .NET Core, nor could I find any SignalR methods of any kind.
I wonder if anyone could point me in the right direction?
Solution 1:[1]
SignalR Server-side requires a server that receives web requests, Kestrel or IIS normally. So you need a web app, you can still add hosted services to your webapp, there is even an example showing a web app with SignalR and a hosted service: https://docs.microsoft.com/aspnet/core/signalr/background-services?view=aspnetcore-5.0
Solution 2:[2]
For people wanting to develop a self-hosted ASPNETCore SignalR hub running in a Windows service, here's my barebones code. (Disclaimer: I'm new to ASPNET Core, and I don't know whether this approach would be approved by more knowledgeable folk.) The magic is in the ".UseStartup();" call.
Create a new service project using the VS 2019 "Worker Service" C# template.
Edit the service's .csproj file and insert the lines:
<ItemGroup> <FrameworkReference Include="Microsoft.aspNetCore.App" /> </ItemGroup>Create a Startup class:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Text; namespace My.SignalRCore.Service { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); services.AddHostedService<Worker>(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseRouting(); // pre-requisite for app.UseEndpoints() app.UseEndpoints(endpoints => { string url = $"/ServerHub"; endpoints.MapHub<MyHub>(url); }); } } }Create a MyHub : Hub class:
using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; namespace My.SignalRCore.Service { public class MyHub : Hub { public ILogger<Worker> _logger = null; public MyHub(ILogger<Worker> logger) { _logger = logger; //_logger.LogInformation($"{DateTimeOffset.Now} MyHub.Constructor()"); } public async Task ProcessClientMessage(string user, string message) { // process an incoming message from a connected client _logger.LogInformation($"{DateTime.Now.ToString("hh:mm:ss.fff")} MyHub.ProcessClientMessage({user}, {message})"); } } }Amend the Program class to use a "UseStartup" call:
using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using System; namespace My.SignalRCore.Service { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); webBuilder.UseUrls("http://*:12457"); }); } }Add a hub reference (if needed) in the Worker class:
using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace My.SignalRCore.Service { public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; private readonly IHubContext<MyHub> _signalRHub; public Worker(ILogger<Worker> logger, IHubContext<MyHub> signalRHub) { _logger = logger; _signalRHub = signalRHub; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await Task.Delay(15000, stoppingToken); _logger.LogInformation($"{DateTime.Now.ToString("hh:mm:ss.fff")} Sending ping to all clients"); await _signalRHub.Clients.All.SendAsync("ReceiveMessage", "Server", "ping"); } } } }
That's it for the server code. I've not yet installed it as a service, but it works as a console app. On a non-dev machine, you might need to install the APSNET CORE 3.1 runtime, it's available here: https://dotnet.microsoft.com/download/dotnet/3.1
For the client:
Install nuget package: Microsoft.AspNetCore.SignalR.Client
Create a client class along the lines of (note: the reconnect code here isn't working):
using Microsoft.AspNetCore.SignalR.Client; using System; using System.Threading.Tasks; namespace My.SignalRCoreClientLib { public class SignalRCoreClientLib { public EventHandler<string> MessageEvent; private HubConnection _connection; public async Task Connect(string serverIp, int port) { if (_connection == null) { _connection = new HubConnectionBuilder() .WithUrl($"http://{serverIp}:{port}/ServerHub") .Build(); _connection.Closed += async (error) => { await Task.Delay(new Random().Next(0, 5) * 1000); await _connection.StartAsync(); }; _connection.On<string, string>("ReceiveMessage", (user, message) => { string fullMessage = $"{user}: {message}"; MessageEvent?.Invoke(this, fullMessage); }); } try { await _connection.StartAsync(); } catch (Exception ex) { MessageEvent?.Invoke(this, $"{ex.Message}; base Exception: {ex.GetBaseException().Message}"); await Task.Delay(new Random().Next(0, 5) * 1000); await Connect(serverIp, port); } } public async Task SendMessage(string user, string message) { try { await _connection.InvokeAsync("ProcessClientMessage", user, message); } catch (Exception ex) { MessageEvent?.Invoke(this, ex.Message); } } } }
That's it. Hope this is helpful.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Brennan |
| Solution 2 | TimA_12345 |
