Using SignalR in Worker Service Project - c#

I have been searching high and low on how to configure this properly and nothing seems to be working for me.
I have been mostly following the following article from Microsoft and adjusting for my own needs as I go.
Using .Net 5.0
For starters, what I am trying to do is create a SignalR connection between my Web UI and my BackgroundService. When creating the background service I used File -> New Project -> Worker Service template to create this service. I need to be able to both send data to clients and recieve data from clients within the BackgroundService.
Program.cs
Here I add SignalR. Which seems pretty straightforward.
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
IConfiguration configuration = hostContext.Configuration;
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("myContext")));
services.AddSignalR();
services.AddHostedService<Worker>();
});
}
Interface:
This is where my understanding starts to break down. I think this is an interface to send data to the client?
public interface IDesoutterController
{
Task SendData(DateTime currentTime);
}
Controller Hub:
Here is my hub contained in the worker service project with a function to send data to all clients.
public class ControllerHub : Hub<IDesoutterController>
{
public async Task SendDataToClients(DateTime dateTime)
{
await Clients.All.SendData(dateTime);
}
}
Controller Hub Client:
This is where I am the most confused. Why is my service using a hub, and a hub client in this case or is it all poor wording? Is this just the portion to receive data from my Web UI? Would my web UI project call the endpoint /hubs/controller??
public partial class ControllerHubClient : IDesoutterController, IHostedService
{
private readonly ILogger<ControllerHubClient> _logger;
private HubConnection _connection;
public ControllerHubClient(ILogger<ControllerHubClient> logger)
{
_logger = logger;
_connection = new HubConnectionBuilder()
.WithUrl("/hubs/controller")
.Build();
_connection.On<DateTime>("onSend", SendData);
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// Loop is here to wait until the server is running
while (true)
{
try
{
await _connection.StartAsync(cancellationToken);
break;
}
catch
{
await Task.Delay(1000);
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _connection.StopAsync();
}
public Task SendData(DateTime currentTime)
{
_logger.LogInformation("{CurrentTime}", currentTime.ToShortTimeString());
return Task.CompletedTask;
}
}
Worker Service:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IHubContext<ControllerHub, IDesoutterController> _controllerHub;
public Worker(ILogger<Worker> logger, IHubContext<ControllerHub, IDesoutterController> controllerHub)
{
_logger = logger;
_controllerHub = controllerHub;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//Other Stuff
}
}
}
Really all I am trying to do is have a SignalR connection between by Web UI and Worker Service project to allow events to be sent (quickly). Each instance of my background service needs its own hub. Granted I am FAIRLY certain I am doing this all wrong. I do however get the following error whenever I start my worker service project.
System.TypeLoadException: 'Method 'GetStreamItemType' in type 'Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1' from assembly 'Microsoft.AspNetCore.SignalR.Core, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' does not have an implementation.'
Any help or direction would be greatly appreciated as I am completely new to background services and signalr.
EDIT
Here is a diagram of essentially the system I am trying to create. After reading some of the comments I wanted to clarify my worker service project does NOT have a startup.cs (a big part of why I was confused reading the Microsoft article). I think because I used the worker service template? In my case I "think" I was trying to put both the client and the hub in the same project. After another suggestion in the comment I believe I would want the hub in my web UI project and make my background service the client connecting to the hub?
So in the picture switch around the UI to be the Hub and the Background Worker service(s) to be the client(s)? When I say worker services, I mean I am creating a task for each device. So 1 "worker" thread per device.

Related

.NET 6 Windows Service Logs Two Duplicate Events At Shutdown

Let me start by stating that this issue does not seem to cause any problems but it certainly doesn't look correct so I thought I would inquire about a fix.
I have created a Windows service with .NET 6 loosely following the guide here. The biggest difference is that I have stripped out all of the code in the ExecuteAsync method so, basically, my service does not actually do anything at the moment.
When I stop the service, I notice that two service stopped events are written to the event log (see image). The two events logged at 9:28:48 PM seem to be duplicates. Can anyone explain why this event is duplicated and how to resolve?
UPDATE
Here are the two code files in my project:
Program.cs
namespace WindowsServiceSandbox
{
public class Program
{
public static void Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options => {options.ServiceName = "Test Windows Service";})
.ConfigureServices(services => {services.AddHostedService<Worker>();})
.Build();
host.Run();
}
}
}
Worker.cs
namespace WindowsServiceSandbox
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
}
}

blazor server side: How to start a thread at a specific time

I want send SMS or Email or do something else in specific date and time for example in Birthday of customer at 11 am, where Datetime format birthday stored in SQL Database, by C# Blazor Server Side app.
I have read https://www.codeproject.com/Articles/12117/Simulate-a-Windows-Service-using-ASP-NET-to-run-sc and
C#: How to start a thread at a specific time
Is there any newer or better suggestion to do this?
As Panagiotis Kanavos says, the Blazor server-side is an ASP.NET Core server application. ASP.NET Core has build-in library to run the background service. The ASP.NET Core could run both Blazor service and background service.
You could use Blazor to handle user input and then use background server to send Email at specific time.
More details, you could refer to this article and below code sample:
public class TestHostedService: BackgroundService
{
private readonly ILogger _logger;
public TestHostedService(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<TestHostedService>();
}
protected async override Task ExecuteAsync(
CancellationToken cancellationToken)
{
do
{
if (DateTime.Now.Hour == 11)
{
//do something
}
//fired every one hour
await Task.Delay(TimeSpan.FromHours(1), cancellationToken);
}
while (!cancellationToken.IsCancellationRequested);
}
}
To run it, you should add it as HostedService.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).ConfigureServices((hostcontext, service) => {
service.AddHostedService<TestHostedService>();
});

ASP.NET core app not consuming messages from RabbitMQ's queue if hosted on IIS

Setup
RabbitMQ on localhost, asp.net core app using masstransit lib on IIS which consumes events.
Problem
There are messages in RabbitMQ queue and my asp.net core app does not consume it when it is hosted on IIS, also, it is not visible as consumer in RabbitMQ Management's queue details. However it consumes messages and is visible as consumer when I "dotnet run" it or run it on IIS Express.
Code
Startup.cs fragment
...
services.AddMassTransit(x =>
{
x.AddConsumer<EventConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("localhost", c =>
{
c.Username("username");
c.Password("password");
});
cfg.ReceiveEndpoint("queue-name", e =>
{
e.ConfigureConsumer<EventConsumer>(context);
});
});
});
services.AddMassTransitHostedService();
...
EventConsumer.cs
public class EventConsumer : IConsumer<Event>
{
private readonly ILogger<EventConsumer> _logger;
public EventConsumer(ILogger<EventConsumer> logger)
{
_logger = logger;
}
public async Task Consume(ConsumeContext<Event> context)
{
var message = context.Message;
}
}
Thanks in advance for all responses.

How to use .NET Core's Built in Dependency Injection with Service Fabric

Good afternoon,
I recently started experimenting with Service Fabric and .NET Core.
I created a Stateless Web API and performed some DI using:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
var connString = Configuration.GetConnectionString("DefaultConnection");
services.AddScoped<FaxLogic>();
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connString));
}
With the above I can use constructor inject on my FaxLogic class as well as my DbContext class (through the FaxLogic):
private readonly FaxLogic _faxLogic;
public FaxController(
FaxLogic faxLogic)
{
_faxLogic = faxLogic;
}
private readonly ApplicationContext _context;
public FaxLogic(ApplicationContext context)
{
_context = context;
}
I then created a non-Web API stateless service. I want to be able to access my FaxLogic and DbContext like in my WebAPI, but within the RunAsync method of the stateless service:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
// TODO: Replace the following sample code with your own logic
// or remove this RunAsync override if it's not needed in your service.
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
ServiceEventSource.Current.ServiceMessage(this.Context, "Hello!");
// do db stuff here!
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
}
I am wondering how I'd do it. I tried playing with the CreateServiceInstanceListeners() method and the Program.cs file where ServiceRuntime is is used to register but I can't seem to figure it out! Any help would be appreciated.
The solution has been already answered here: Set up Dependency Injection on Service Fabric using default ASP.NET Core DI container
In summary, you have to register the dependencies before you create a new instance of your stateless service and then create a factory method to resolve the dependencies:
i.e:
public static class Program
{
public static void Main(string[] args)
{
var provider = new ServiceCollection()
.AddLogging()
.AddSingleton<IFooService, FooService>()
.AddSingleton<IMonitor, MyMonitor>()
.BuildServiceProvider();
ServiceRuntime.RegisterServiceAsync("MyServiceType",
context => new MyService(context, provider.GetService<IMonitor>());
}).GetAwaiter().GetResult();
See the linked answer for more details.
TaeSeo,
I think what you are looking for is implemented in the project I am working on - CoherentSolutions.Extensions.Hosting.ServiceFabric.
In the terms of CoherentSolutions.Extensions.Hosting.ServiceFabric what you are looking for would look like:
private static void Main(string[] args)
{
new HostBuilder()
.DefineStatelessService(
serviceBuilder => {
serviceBuilder
.UseServiceType("ServiceName")
.DefineDelegate(
delegateBuilder => {
delegateBuilder.ConfigureDependencies(
dependencies => {
dependencies.AddScoped<FaxLogic>();
});
delegateBuilder.UseDelegate(
async (StatelessServiceContext context, FaxLogic faxLogic) => {
while (true) {
cancellationToken.ThrowIfCancellationRequested();
ServiceEventSource.Current.ServiceMessage(context, "Hello!");
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
});
})
})
.Build()
.Run();
}
If you have more questions feel free to ask or check out the project wiki
Hope it helps.

How to access IHubContext in net core web api from singleton class

ASP.NET CORE 2.1 WEB API
I have a singleton class which is a tcpip client (TcpIpSerrvice) started in Startup.cs when the API does and needs to run all the time. I cannot figure out how to access IHubContext from this singleton in order to be able to send tcp data to web front end via SignalR.
I have tried to trigger this via static Action property in sigleton class, but when this is triggered in Hub, Hub is already disposed and I get (System.ObjectDisposedException: 'Cannot access a disposed object.')
in singleton class
public static Action<string> DataArrived { get; set; }
and when in hub
TcpIpService.DataArrived = new Action<string>(MessageFromPlcReceived);
I know that this can be injected to Controllers or services, but I need this to access it from elsewhere.
I'm aware that GlobalHost is not available in new SignalR and tha IHubContext is easily injectable, but this won't work (at least so far) with singleton class instantiated separetly in Startup.cs
Any ideas how to do this?
I have found out an ugly way to do it after getting throught SignalR GitHub repo. I can do this in my Startup.cs, Configure method:
TcpIpService.HubContext = app.ApplicationServices.GetRequiredService<IHubContext<VcHub>>();
which is just creating what the old GlobalHost did before. As ugly as this is, I can't find better solution ... so far. Is there a better way doing this?
I didn't find a proper way around this, so I have rewritten the TcpService as IHostedService and then I was able to register this in Startup.cs and inject IHubContext into the service and access the context from required class.
...
using Microsoft.Extensions.Hosting;
...
public class HostedTcpIpService : IHostedService, IDisposable
{
#region Declaration(s)
private readonly ILogger<HostedTcpIpService> logger;
private readonly IHubContext<VcHub> hubContext;
#endregion
#region Constructor
public HostedTcpIpService(
ILogger<HostedTcpIpService> logger,
IHubContext<VcHub> hubContext)
{
this.logger = logger;
this.hubContext = hubContext;
}
#endregion
#region IDisposable
public void Dispose()
{
// disposing
}
#endregion
#region IHostedService
public Task StartAsync(CancellationToken cancellationToken)
{
// Start Tcp connection
}
public Task StopAsync(CancellationToken cancellationToken)
{
// Stop Tcp connection
}
#endregion
private void OnTcpMessageReceived(string tcpMessage)
{
// injected hubContext ...
// broadcast to signalR clients
this.hubContext.Clients.All.SendAsync("broadcastMessage", tcpMessage);
}
}
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, HostedTcpIpService>();
...
}

Categories