.NET 6 attach to windows session switch - c#

I am migration an app from .NET Framework to .NET 6. It starts Kestrel web server internally and it is intended to run as a Windows Service - in the old version that was achieved using Topshelf library, so I continued using it. One of the requirements is to listen for session switch events and send messages to third-party application.
In the old version of the application it was achieved again with the help of Topshelf. However, in the callback there was used static DI container to get access to services:
return HostFactory.New((hostConfigurator) =>
{
// ...
hostConfigurator.EnableSessionChanged();
hostConfigurator.Service<WindowService>((serviceConfigurator) =>
{
serviceConfigurator.ConstructUsing(hostSettings => new WindowService());
// ...
serviceConfigurator
.WhenSessionChanged((serviceHelper, arguments) => serviceHelper.SessionChange(arguments));
// ...
});
// ...
});
And this is the callback:
private void SessionChange(SessionChangedArguments changeDescription)
{
var sessionService =
DependencyInjectionContainer.GetRequiredService<IUserSessionMonitoringService>();
if (sessionService == null)
{
LOG.Warning(...);
return;
}
sessionService.SessionChanged(...);
}
So, here's the problem: in the callback a static DependencyInjectionContainer class is used to get the needed service/s. However in the new version of the app this approach won't work as this static class was abandoned. Also, I don't even have access to built-in .NET DI Container, because during the time the Topshelf is configured, the container isn't build.
As further steps I investigated what is Topshelf using under the hood for this functionality - that's SystemEvents.SessinSwitch event. I tried to attach an event handler to it from a hosted service, in which the needed services are injected. For the sake of the test, I just wanted to log some text in a file from the event handler. However, when sign out/in there's nothing in the file from those logs.
public class SystemSessionMonitoringHostedService : IHostedService
{
private readonly IUserSessionMonitoringService userSessionMonitoring;
private readonly ILogger<SystemSessionMonitoringHostedService> logger;
public SystemSessionMonitoringHostedService(
IUserSessionMonitoringService userSessionMonitoring,
ILogger<SystemSessionMonitoringHostedService> logger)
{
this.userSessionMonitoring = userSessionMonitoring;
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
SystemEvents.SessionSwitch += OnSessionChanged;
return Task.CompletedTask;
}
private void OnSessionChanged(object sender, SessionSwitchEventArgs eventArgs)
{
var json = JsonConvert.SerializeObject(eventArgs);
logger.LogCritical($"Object: {json}");
}
}
Does anyone know if those SystemEvents works in .NET Core/5+? Am I missing something? Suggestions for workarounds are also welcome.

Related

Implementing Airbrake into a ASP.NET MVC 5 project

I have an older .NET 4.8 project that needs to use Airbrake. The project is using Unity for its IoC container, implementing the standard Repository Service pattern.
There's very little in the way of ASP.NET examples.
I am looking to do something like this:
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType(typeof(ILogger<>), typeof(ILogger<>));
container.RegisterType<IMyService, MyService();
}
public class MyController
{
private readonly ILogger<MyController> _logger;
private readonly IMyService _myService;
public MyController(ILogger<MyController> logger, IMyService _myService)
{
_logger = logger;
_myService = myService;
}
public MyMethod()
{
try
{
var x = _myService.DoThis();
}
catch (Exception ex)
{
_logger.LogError(e, e.Message);
}
}
}
I believe I need to either somehow register Airbrake with ILogger or perhaps create my own logging service.
public class Logging : ILogging
{
public void LogError(Exception e, string message)
{
var airbrake = new AirbrakeNotifier(new AirbrakeConfig
{
ProjectId = // pulled from web.config somehow
ProjectKey = // pulled from web.config somehow
});
var notice = airbrake.BuildNotice(ex);
airbrake.NotifyAsync(notice).Result;
}
}
I have tried using this as starting point: https://github.com/airbrake/sharpbrake/blob/master/docs/asp-net-http-module.md
This is excellent, but I need to extend it somehow to be able to use it within my services and not just the .Web project.
I know there's the ASP.NET module that will automatically capture the errors but I am wanting to manually log when I see fit, and avoid having to call the airbrake client every time I want to log an error.
Is there a way to do this or am I completely misunderstanding how this should be working?
You don't actually need to wire it up as part of the .NET ILogger. I am sure there is a way (probably via OWIN) but you nothing stops you from writing a basic logging service as you would any other service and using that via bog standard DI. The answer was pretty much in the question to begin with.

How to configure Logging via DI in Azure WebJob

I am working on Azure WebJobs (3.0.6) using dotnet core. I referred Microsoft's Get Started Guide. Per the example I wanted to have a console logging to begin with. The scope of that guide is limited. In my application, I will be using many classes in a different dll. I am not able to figure out how can I add logging in those classes. The sample code is
// The main method
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
// The queue trigger class
public class QueueListenerService
{
public static void QueueListener([QueueTrigger("myqueue")] string message, ILogger logger)
{
logger.LogInformation("The logger works here");
// how can I pass an instance of ILogger in the constructor below
MyWorker myWorker = new MyWorker();
}
}
// MyWorker Class in a different assembly
public class MyWorker
{
public MyWorker(ILogger logger)
{
// I want to use logger here being injected
}
}
I have referred several examples of DI in dotnet core console applications and they use service collection approach. I also check this blog but to me, this is what I have done and yet my ILogger is not being resolved. It ask me to pass an instance of ILogger when I create MyWorker instance
You are close to the solution. The main thing you need to change is to let the service collection create the MyWorker instance for you.
I quickly extended my recent Webjob sample project to include console logging with dependency injection. See this commit for how I added it.
You mainly need to use constructor dependency injection for your QueueListenerService.
builder.ConfigureServices(services =>
{
services.AddScoped<QueueListenerService>();
services.AddScoped<MyWorker>();
});
public class QueueListenerService
{
public QueueListenerService(MyWorker worker){
_Worker = worker;
}
public static void QueueListener([QueueTrigger("myqueue")] string message, ILogger logger)
{
logger.LogInformation("The logger works here");
_Worker.DoStuff()
}
}

How to start HostedService in MVC Core app without http request

In my MVC .NET core 2.2 app there is HostedService which doing background work.
It is register in ConfigureServices method of Startap class
services.AddHostedService<Engines.KontolerTimer>();
Since this is background service independent of users requests I want to start my background service immediately when app starts.
Now is case to my HostedService staring after first user request.
What is proper way to start HostedService when MVC Core app start
My serivce looks like this one https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2
internal class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Looks like I have problem staring app at all.
My porgram cs looks like
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseSerilog((ctx, config) => { config.ReadFrom.Configuration(ctx.Configuration); })
.UseStartup<Startup>();
}
And I do not hit any break point before first user request.
Am I miss something, this is default .Net Core app created by VS2017
Here is my starup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
private Models.Configuration.SerialPortConfiguration serialPortConfiguration;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.Stores.MaxLengthForKeys = 128)
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddDbContext<Data.Parking.parkingContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddHostedService<Engines.KontolerTimer>();}
When you run this using Visual Studio, you are likely using IIS Express, which isn't going to run your ASP.NET Core project until the first request is made (that's really just how IIS works by default). This applies when using the InProcess hosting-model that's new with ASP.NET Core 2.2, which I expect you must be using in order to see this issue. See this GitHub issue for more.
You can prove this theory by removing the AspNetCoreHostingModel XML element from the .csproj file that you're using to host the ASP.NET Core application (which will switch it back to the OutOfProcess mode). It looks like there's a "Hosting Model" option under "Debug" in the project properties dialog of VS2017 that you can change to "Out Of Process" if you don't want to edit the .csproj directly.
If you want the hosting-model to be out-of-process only for a production site, you could use a Web.config transform, for example. If you want it to be out-of-process both during development and in production, just changing the property I called out above will be enough as this gets converted automatically into a Web.config property. If you would prefer to use the in-process model, enabling preload in the IIS application is a good option (described here).
Background services start when your application starts, then it's up to you to synchronize with it.
You can implement a background service by using the BackgroundService class from the namespace Microsoft.Extensions.Hosting(Microsoft.Extensions.Hosting.Abstractions assembly):
First the declare the interface of your service (in this case it is empty, not nice, but clean):
public interface IMyService : IHostedService
{
}
Then, declare your service. The following snippet declares a service that at startup waist for 5 seconds, and then executes a task every 2 minutes and half:
internal sealed class MyService : BackgroundService, IMyService
{
private const int InitialDelay = 5 * 1000; //5 seconds;
private const int Delay = (5 * 60 * 1000) / 2; // 2.5 minutes
private readonly ILogger<MyService> m_Logger;
public MyService(ILogger<MyService> logger, IServiceProvider serviceProvider)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (serviceProvider == null)
throw new ArgumentNullException(nameof(serviceProvider));
this.m_Logger = logger;
this.m_ServiceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
m_Logger.LogDebug($"MyService is starting.");
stoppingToken.Register(() => m_Logger.LogDebug($"MyService background task is stopping because cancelled."));
if (!stoppingToken.IsCancellationRequested)
{
m_Logger.LogDebug($"MyService is waiting to be scheduled.");
await Task.Delay(InitialDelay, stoppingToken);
}
m_Logger.LogDebug($"MyService is working.");
while (!stoppingToken.IsCancellationRequested)
{
await DoSomethingAsync();
await Task.Delay(Delay);
}
m_Logger.LogDebug($"MyService background task is stopping.");
}
catch (Exception ex)
{
m_Logger.LogDebug("MyService encountered a fatal error while w task is stopping: {Exception}.", ex.ToString());
}
}
private async Task DoSomethingAsync()
{
// do something here
await Task.Delay(1000);
}
}
As you can see, it's up to you to keep the background service "alive". Finally, you have to register it in your Startup.cs at the end of your ConfigureServices method:
services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, MyService>();
This is sufficient to have the service started. keep in mind that your application could be actually started at a later time if hosted in IIS: your application is (re)started everytime your assembly is recycled. Instead, using Kestrel, provides a single instance application which will not be recycled.
For those using .Net Core 2.1 or lower, the Background class is not available, but you can get the definition from github (I post what I used in the past as the github repository can be moved):
//borrowed from .NET Core 2.1 (we are currently targeting 2.0.3)
// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
For me... background tasks weren't starting until the first page request.
But then I noticed in my Publish / Edit, I didn't have Destination Url set. (and also I didn't have a home Index page)...
Once I added a valid Destination Url... that page would popup after publishing and be my "first" page request and background tasks would start.
If you want o have a Service doing background tasks (similar to old Windows Services) I would suggest you to use: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2 instead of a WebHost.
WebHost add a lot of stuff that probably you won't need since seems a simple background job (assuming that reading your code).
Hosted services do start when the host is starting. With the WebHost, the hosted services will be started right after the application has started. This means that if implemented correctly, your hosted service will run without requiring a request to come in.
When I try your example hosted service on a fresh ASP.NET Core application, it works just fine, so if it is not working for you, then apparently your actual implementation KontolerTimer is not correct.

How to stop a MassTransit bus in an ASP.NET Core server?

I am studying MassTransit and ASP.NET Core, dependancy injection and successfully implemented something that works. I plan to use the Kestrel web server.
So I had to configure my ASP.NET core project this way in the Startup.cs.
public void ConfigureServices(IServiceCollection services) {
...
var bus = Bus.Factory.CreateUsingRabbitMq(sbc => {
var host = sbc.Host(new Uri(address), h => {
h.Username("guest");
h.Password("guest");
});
});
services.AddSingleton<IBus>(bus);
services.AddScoped<IRequestClient<GetTagRequest, GetTagAnswer>>(x =>
new MessageRequestClient<GetTagRequest, GetTagAnswer>(x.GetRequiredService<IBus>(), new Uri(address + "/gettagrequest"), timeOut));
bus.Start(); // <= ok. how is the bus supposed to stop ?
...
Although this works fine, no tutorial mentioned anything about setting bus.Stop() in an ASP.NET core project. I read in MassTransit documentation that a running bus could prevent a graceful exit.
Is this a major concern for a Kestrel web server? I have read about process recycling and I am afraid a running bus would compromise this.
At which place can I place that bus.Stop() in an ASP.NET Core project ?
You can use ApplicationLifetime events. Just make your IBus object class level variable.
public class Startup
{
private IBus _bus;
public void ConfigureServices(IServiceCollection services) {
/* ... */
_bus = Bus.Factory.CreateUsingRabbitMq ...
/* ... */
}
public void Configure(IApplicationLifetime appLifetime)
{
appLifetime.ApplicationStarted.Register(() => _bus.Start());
appLifetime.ApplicationStopping.Register(() => _bus.Stop());
}
}
There is IApplicationLifetime in .NET Core, which has several CancellationToken properties, including ApplicationStopped. So when you need to do something after asp.net application is shutdown and all requests are processed (like stopping your bus) - you can do it like this:
// lifetime will be injected to Configure from DI container
public void Configure(IApplicationBuilder app, IApplicationLifetime lifetime) {
// subscribe to ApplicationStopped
lifetime.ApplicationStopped.Register(OnApplicationStopped);
// the rest
}
private void OnApplicationStopped() {
_bus.Stop();
}
It's always good to explicitly release resources even on process shutdown. For example, some message might still be in transition when the process will be killed after shutdown. Doing explicit dispose will allow this transition to complete.
To add to the existing answers:
If you use the MassTransit.AspNetCore's IServiceCollection.AddMassTransit() extension method, there's no need for a class level IBus instance. Startup's Configure() supports DI, so you can do this instead:
public void Configure(IApplicationLifetime appLifetime, IBus bus)
{
appLifetime.ApplicationStarted.Register(() => bus.Start());
appLifetime.ApplicationStopping.Register(() => bus.Stop());
}
If you don't want to use the package, you can still register IBus with the DI container (like in the question) and request it from Configure().
The ASP.NET Core DI sample uses an IHostedService instead:
public class BusService : IHostedService
{
private readonly IBusControl _busControl;
public BusService(IBusControl busControl)
{
_busControl = busControl;
}
public Task StartAsync(CancellationToken cancellationToken) =>
_busControl.StartAsync(cancellationToken);
public Task StopAsync(CancellationToken cancellationToken) =>
_busControl.StopAsync(cancellationToken);
}
The service is registered as:
services.AddSingleton<IHostedService, BusService>();
For more information on IHostedService, here's the doc page. I'm not sure I like the idea of having a background service just to stop the bus. Anyway, the sample repo I picked this example from is worth referring to.
The third option is to do it yourself in Main(). Something like:
var host = CreateWebHostBuilder(args).Build();
var bus = host.Services.GetRequiredService<IBusControl>();
await bus.StartAsync();
await host.RunAsync();
await bus.StopAsync();

How to host NServiceBus in ASP.NET Core

I recently upgraded from NServiceBus 5x to 6.0.0-beta0004 to be able to host a ASP.NET Core application (whose main function is to listen to NServiceBus messages). I'm having problems with the startup of the host as the endpoint doesn't seem to subscribe to the publisher.
I am using the pubsub example to fix the problem. Apart from the projects that are in there by default, I added one extra project, a fresh ASP.NET Core project (based on the full .NET framework). I tried to use the exact same NServiceBus configuration, but instead of using the app/web.config, I am using the following configuration:
public class ConfigurationSource : IConfigurationSource
{
public T GetConfiguration<T>() where T : class, new()
{
UnicastBusConfig config = new UnicastBusConfig()
{
MessageEndpointMappings = new MessageEndpointMappingCollection()
};
var endpointMapping = new MessageEndpointMapping
{
AssemblyName = "Shared",
Endpoint = "Samples.PubSub.MyPublisher"
};
config.MessageEndpointMappings.Add(endpointMapping);
return config as T;
}
}
The Startup class was extended with the following code:
public IConfigurationRoot Configuration
{
get;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddSingleton(this.GetBus());
}
private IEndpointInstance GetBus()
{
LogManager.Use<DefaultFactory> ().Level(NServiceBus.Logging.LogLevel.Info);
var endpointConfiguration = new EndpointConfiguration("Samples.PubSub.Subscriber3");
endpointConfiguration.UseSerialization<JsonSerializer>();
endpointConfiguration.DisableFeature<AutoSubscribe>();
endpointConfiguration.UsePersistence<InMemoryPersistence>();
endpointConfiguration.SendFailedMessagesTo("error");
endpointConfiguration.EnableInstallers();
// Skip web.config settings and use programmatic approach
endpointConfiguration.CustomConfigurationSource(new ConfigurationSource());
var endpointInstance = Endpoint.Start(endpointConfiguration).Result;
endpointInstance.Subscribe<IMyEvent>().Wait();
return endpointInstance;
}
The Message Handler is identical to the other Subscriber projects in the solution:
public class EventMessageHandler : IHandleMessages<IMyEvent>
{
static ILog log = LogManager.GetLogger<EventMessageHandler>();
public Task Handle(IMyEvent message, IMessageHandlerContext context)
{
log.Info($"Subscriber 2 received IEvent with Id {message.EventId}.");
log.Info($"Message time: {message.Time}.");
log.Info($"Message duration: {message.Duration}.");
return Task.FromResult(0);
}
}
If I run the sample, I notice Subscriber1 and Subscriber2 are subscribed perfectly and they receive messages if I execute some of the commands in the Publisher console application. However Subscriber3 doesn't appear to be doing anything. No exceptions are thrown and in this sample I don't seem to find any relevant log information that could lead to misconfiguration.
Has anyone tried a similar setup with ASP.NET Core & NServiceBus 6 and if so, what should I do next?
Update 04/04/2017
There are some updates that provide some interesting insights:
https://groups.google.com/forum/#!topic/particularsoftware/AVrA1E-VHtk
https://particular.net/blog/nservicebus-on-net-core-why-not

Categories

Resources