.NET 6 Windows Service Logs Two Duplicate Events At Shutdown - c#

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;
}
}
}

Related

Using SignalR in Worker Service Project

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.

Writing logs in console in a .NET Framework 4.6 Console Application

I tried many ways, but I could not manage to write logs in the console of my .NET Framework 4.6 Console Application. I searched on Stackoverflow and found 2 possible solutions, which did not work:
Registering in ConfigureDI as
services.AddLogging(configure => configure.AddConsole());
This first possible solution I could not even test as apparently
ILoggingBuilder does not contain a definition for AddConsole [...]
Registering in ConfigureDI as
services.AddSingleton< ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
I'm using ILogger with dependency injection, like this:
public class MyApplication
{
private readonly ILogger<MyService> _logger;
public MyApplication(ILogger<MyService> logger)
{
_logger = logger;
}
public void Run()
{
_logger.LogInformation("Application Started at {dateTime}", DateTime.UtcNow);
//...
}
}
My Program.cs is like this:
public static class Program
{
public static void Main(string[] args)
{
var services = new ServiceCollection();
DependencyInjectionConfiguration.ConfigureDI(services);
var serviceProvider = services.BuildServiceProvider();
var receiver = serviceProvider.GetService<IReceiver>();
receiver.MyServiceMethod();
}
}
and my ConfigureDI method is like this:
public static class DependencyInjectionConfiguration
{
public static void ConfigureDI(IServiceCollection services)
{
services.AddScoped<IReceiver, Receiver>();
services.AddHttpClient<MyClient>();
services.AddScoped<HelperXml>();
//services.AddSingleton<ILoggerFactory, LoggerFactory>();
//services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
//services.AddLogging(configure => configure.AddConsole());
}
}
Anyone has a clue on what is wrong here? In addition: Writing on a txt file would be nice too...
Thanks
You'll need to target the .NET Standard framework if you wish to use ILogger (part of Microsoft.Extensions.Logging).

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>();
...
}

Where can I log an ASP.NET Core app's start/stop/error events?

In old ASP.NET, in the Global.asax.cs class, I would log when the app starts, stops and throws unhandled exceptions:
Application_Start()
Application_End()
Application_Error()
How do I do the same in ASP.NET Core? It has a Startup class, but it is for configuration.
Where do I hook into the app's start/stop/error events?
Note: For .NET Core 3.0 or later, this answer is obsolete. See this answer instead.
You need to use Microsoft.AspNetCore.Hosting.IApplicationLifetime
/// <summary>
/// Triggered when the application host has fully started and is about to wait
/// for a graceful shutdown.
/// </summary>
CancellationToken ApplicationStarted { get; }
/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// Requests may still be in flight. Shutdown will block until this event completes.
/// </summary>
CancellationToken ApplicationStopping { get; }
/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// All requests should be complete at this point. Shutdown will block
/// until this event completes.
/// </summary>
CancellationToken ApplicationStopped { get; }
Instance of IApplicationLifetime could be obtained in Configure method. Also add ILoggerFactory here:
public void Configure(IApplicationBuilder app, IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory)
{
// use applicationLifetime
}
Having ILoggerFactory, you can create instance of ILogger:
var logger = loggerFactory.CreateLogger("StartupLogger");
So you just need to create a property in the Startup class to persist the instance of ILogger (or ILoggerFactory, if you would like to create different ligger instance for different events). To summarize:
public class Startup
{
private ILogger _logger;
public void Configure(IApplicationBuilder app, IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory)
{
applicationLifetime.ApplicationStopping.Register(OnShutdown);
...
// add logger providers
// loggerFactory.AddConsole()
...
_logger = loggerFactory.CreateLogger("StartupLogger");
}
private void OnShutdown()
{
// use _logger here;
}
}
I didn't like #neustart47 answer as it was unnecessarily complex but he is right that IApplicationLifetime is obsolete.
Taken from the Microsoft Docs
// 1. Add the interface `IHostedService` to the class you would like
// to be called during an application event.
internal class LifetimeEventsHostedService : IHostedService
{
private readonly ILogger _logger;
private readonly IHostApplicationLifetime _appLifetime;
// 2. Inject `IHostApplicationLifetime` through dependency injection in the constructor.
public LifetimeEventsHostedService(
ILogger<LifetimeEventsHostedService> logger,
IHostApplicationLifetime appLifetime)
{
_logger = logger;
_appLifetime = appLifetime;
}
// 3. Implemented by `IHostedService`, setup here your event registration.
public Task StartAsync(CancellationToken cancellationToken)
{
_appLifetime.ApplicationStarted.Register(OnStarted);
_appLifetime.ApplicationStopping.Register(OnStopping);
_appLifetime.ApplicationStopped.Register(OnStopped);
return Task.CompletedTask;
}
// 4. Implemented by `IHostedService`, setup here your shutdown registration.
// If you have nothing to stop, then just return `Task.CompletedTask`
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void OnStarted()
{
_logger.LogInformation("OnStarted has been called.");
// Perform post-startup activities here
}
private void OnStopping()
{
_logger.LogInformation("OnStopping has been called.");
// Perform on-stopping activities here
}
private void OnStopped()
{
_logger.LogInformation("OnStopped has been called.");
// Perform post-stopped activities here
}
}
Done!
Please see CaptureStartupErrors and the method .CaptureStartupErrors(true) that will help you find issues.
This is especially handy when something runs perfect on localhost but fails in Azure.
Here is my usual config for NetCore Web Apps:
public static IWebHost BuildWebHost(string[] args) => WebHost
.CreateDefaultBuilder(args)
.CaptureStartupErrors(true)
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.UseAzureAppServices()
.Build();
In Azure App Service you can then find the logs in the log stream in Kudu Tools https://<appname>.scm.azurewebsites.net/api/logstream
Using Microsoft.AspNetCore.Hosting.IApplicationLifetime suggested in the top answer now is obsolete.
[Obsolete("This type is obsolete and will be removed in a future version. The recommended alternative is Microsoft.Extensions.Hosting.IHostApplicationLifetime.", false)]
public interface IApplicationLifetime
Use IHostApplicationLifetime to fire callback when the application shuts down.
Add somewhere:
public static async Task WaitForShutdownAsync(this IHost host)
{
// Get the lifetime object from the DI container
var applicationLifetime = host.Services.GetService<IHostApplicationLifetime>();
// Create a new TaskCompletionSource called waitForStop
var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
// Register a callback with the ApplicationStopping cancellation token
applicationLifetime.ApplicationStopping.Register(obj =>
{
var tcs = (TaskCompletionSource<object>)obj;
//PUT YOUR CODE HERE
// When the application stopping event is fired, set
// the result for the waitForStop task, completing it
tcs.TrySetResult(null);
}, waitForStop);
// Await the Task. This will block until ApplicationStopping is triggered,
// and TrySetResult(null) is called
await waitForStop.Task;
// We're shutting down, so call StopAsync on IHost
await host.StopAsync();
}
Then I use it in Program.cs:
var host = CreateHostBuilder(args).Build();
host.WaitForShutdownAsync();
The same for other callbacks.
More info you can find here
If I missed something please let me know

Categories