Skip to content

Commit 78587be

Browse files
committed
Merge branch 'develop' into main
2 parents 1b14723 + a96a0ee commit 78587be

8 files changed

Lines changed: 119 additions & 16 deletions

CodeCaster.WindowsServiceExtensions.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Workflows", "GitHub
1616
.github\workflows\Publish-Package.yml = .github\workflows\Publish-Package.yml
1717
EndProjectSection
1818
EndProject
19+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestServiceThatThrows", "test\TestServiceThatThrows\TestServiceThatThrows.csproj", "{A426812E-CB26-47F2-B914-17D87312C51F}"
20+
EndProject
1921
Global
2022
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2123
Debug|Any CPU = Debug|Any CPU
@@ -26,6 +28,10 @@ Global
2628
{CC230E5D-8600-4048-8F10-E84A80F77DDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
2729
{CC230E5D-8600-4048-8F10-E84A80F77DDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
2830
{CC230E5D-8600-4048-8F10-E84A80F77DDC}.Release|Any CPU.Build.0 = Release|Any CPU
31+
{A426812E-CB26-47F2-B914-17D87312C51F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32+
{A426812E-CB26-47F2-B914-17D87312C51F}.Debug|Any CPU.Build.0 = Debug|Any CPU
33+
{A426812E-CB26-47F2-B914-17D87312C51F}.Release|Any CPU.ActiveCfg = Release|Any CPU
34+
{A426812E-CB26-47F2-B914-17D87312C51F}.Release|Any CPU.Build.0 = Release|Any CPU
2935
EndGlobalSection
3036
GlobalSection(SolutionProperties) = preSolution
3137
HideSolutionNode = FALSE

src/CodeCaster.WindowsServiceExtensions/CodeCaster.WindowsServiceExtensions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<Company />
1515
<Copyright>CodeCaster</Copyright>
1616
<Description>Makes your .NET 5 BackgroundServices power-event-aware.</Description>
17+
<Version>2.0.0</Version>
1718
</PropertyGroup>
1819

1920
<ItemGroup>

src/CodeCaster.WindowsServiceExtensions/IPowerEventAwareHostedService.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33

44
namespace CodeCaster.WindowsServiceExtensions
55
{
6+
/// <summary>
7+
/// An <see cref="IHostedService"/> that runs as a Windows Service that can react to power state changes.
8+
/// </summary>
69
public interface IPowerEventAwareHostedService : IHostedService
710
{
8-
bool OnPowerEvent(PowerBroadcastStatus powerStatus);
11+
/// <summary>
12+
/// Called when a power event is sent by the OS.
13+
/// </summary>
14+
void OnPowerEvent(PowerBroadcastStatus powerStatus);
915
}
1016
}

src/CodeCaster.WindowsServiceExtensions/PowerEventAwareBackgroundService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33

44
namespace CodeCaster.WindowsServiceExtensions
55
{
6+
/// <summary>
7+
/// Base class for implementing a long running <see cref="IHostedService"/> as a Windows Service that can react to power state changes.
8+
/// </summary>
69
public abstract class PowerEventAwareBackgroundService : BackgroundService, IPowerEventAwareHostedService
710
{
811
/// <summary>
912
/// Override this method to react to power state changes.
1013
/// </summary>
11-
/// <returns>Return false to deny the power change status request.</returns>
12-
public virtual bool OnPowerEvent(PowerBroadcastStatus powerStatus)
14+
public virtual void OnPowerEvent(PowerBroadcastStatus powerStatus)
1315
{
14-
return true;
1516
}
1617
}
1718
}

src/CodeCaster.WindowsServiceExtensions/PowerEventAwareWindowsServiceLifetime.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212

1313
namespace CodeCaster.WindowsServiceExtensions
1414
{
15+
/// <summary>
16+
/// Basically the same as <see cref="WindowsServiceLifetime"/>, but states it can handle power events, and forwards those to the IHostedServices that claim to be capable of the same.
17+
/// </summary>
1518
public class PowerEventAwareWindowsServiceLifetime : WindowsServiceLifetime, IHostLifetime
1619
{
1720
private readonly CancellationTokenSource _starting = new();
@@ -64,6 +67,7 @@ public PowerEventAwareWindowsServiceLifetime(IServiceProvider serviceProvider, I
6467
}
6568
}
6669

70+
/// <inheritdoc />
6771
protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
6872
{
6973
_logger.LogInformation($"Windows Service power event: {powerStatus}");
@@ -74,18 +78,14 @@ protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
7478
return true;
7579
}
7680

77-
var acceptEvent = true;
78-
81+
// Forward the event to all registered I(PowerEventAware)HostedServices.
7982
foreach (var service in _hostedServices)
8083
{
81-
if (!service.OnPowerEvent(powerStatus))
82-
{
83-
// If any of the hosted services returns false, we deny the power status change request by returning false from this method (does that even work?)
84-
acceptEvent = false;
85-
}
84+
service.OnPowerEvent(powerStatus);
8685
}
8786

88-
return acceptEvent;
87+
// Ignored anyway.
88+
return true;
8989
}
9090

9191
protected override void OnStart(string[] args)
@@ -101,6 +101,7 @@ protected override void OnStart(string[] args)
101101
if (!_applicationLifetime.ApplicationStarted.IsCancellationRequested)
102102
{
103103
// Usually very early in the startup process, so this may not be logged nor reported at all.
104+
// But it's here to prevent the service from happily reporting successful startup, while the .NET Core ApplicationHost isn't started at all.
104105
const string errorString = "Windows Service failed to start";
105106
_logger.LogError(errorString);
106107
throw new Exception(errorString);

src/CodeCaster.WindowsServiceExtensions/WindowsServiceLifetimeHostBuilderExtensionsAdapter.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,40 @@
66

77
namespace CodeCaster.WindowsServiceExtensions
88
{
9+
/// <summary>
10+
/// Extension method for setting up <see cref="PowerEventAwareWindowsServiceLifetime"/>.
11+
/// </summary>
912
public static class WindowsServiceLifetimeHostBuilderExtensionsAdapter
1013
{
11-
public static IHostBuilder UsePowerEventAwareWindowsService(this IHostBuilder builder)
14+
/// <summary>
15+
/// Sets the host lifetime to <see cref="PowerEventAwareWindowsServiceLifetime"/> and does whatever <see cref="WindowsServiceLifetimeHostBuilderExtensions.UseWindowsService(IHostBuilder)"/> does.
16+
/// </summary>
17+
/// <param name="hostBuilder">The Microsoft.Extensions.Hosting.IHostBuilder to operate on.</param>
18+
/// <returns>The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.</returns>
19+
/// <remarks>This is context aware and will only activate if it detects the process is running as a Windows Service.</remarks>
20+
public static IHostBuilder UsePowerEventAwareWindowsService(this IHostBuilder hostBuilder)
21+
{
22+
return UsePowerEventAwareWindowsService(hostBuilder, _ => { });
23+
}
24+
25+
/// <summary>
26+
/// Sets the host lifetime to <see cref="PowerEventAwareWindowsServiceLifetime"/> and does whatever <see cref="WindowsServiceLifetimeHostBuilderExtensions.UseWindowsService(IHostBuilder)"/> does.
27+
/// </summary>
28+
/// <param name="hostBuilder">The Microsoft.Extensions.Hosting.IHostBuilder to operate on.</param>
29+
/// <param name="configure">An action to configure the lifetime's options.</param>
30+
/// <returns>The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.</returns>
31+
/// <remarks>This is context aware and will only activate if it detects the process is running as a Windows Service.</remarks>
32+
public static IHostBuilder UsePowerEventAwareWindowsService(this IHostBuilder hostBuilder, Action<WindowsServiceLifetimeOptions> configure)
1233
{
1334
if (!WindowsServiceHelpers.IsWindowsService())
1435
{
15-
return builder;
36+
return hostBuilder;
1637
}
1738

1839
// Call MS's one
19-
builder.UseWindowsService();
40+
hostBuilder.UseWindowsService(configure);
2041

21-
return builder.ConfigureServices(services =>
42+
return hostBuilder.ConfigureServices(services =>
2243
{
2344
// Replace UseWindowsService()'s IHostLifetime lifetime with our own
2445
var lifetime = services.FirstOrDefault(s => s.ImplementationType == typeof(WindowsServiceLifetime));
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using CodeCaster.WindowsServiceExtensions;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Hosting;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace TestServiceThatThrows
11+
{
12+
public class MyFaultyService : IHostedService
13+
{
14+
public Task StartAsync(CancellationToken cancellationToken)
15+
{
16+
throw new InvalidOperationException("This service is not supposed to start.");
17+
}
18+
19+
public Task StopAsync(CancellationToken cancellationToken)
20+
{
21+
return Task.CompletedTask;
22+
}
23+
}
24+
25+
public class MyFaultyBackgroundService : BackgroundService
26+
{
27+
protected override Task ExecuteAsync(CancellationToken stoppingToken)
28+
{
29+
// This works: gets logged in the event log, and prevents service startup.
30+
throw new InvalidOperationException("This service is not supposed to start.");
31+
}
32+
}
33+
34+
public static class Program
35+
{
36+
public static async Task Main(string[] args)
37+
{
38+
Thread.Sleep(5000);
39+
Debugger.Break();
40+
41+
await new HostBuilder()
42+
.ConfigureLogging(l => l.AddConsole())
43+
.ConfigureServices((s) =>
44+
{
45+
//throw new InvalidOperationException("Heh");
46+
//s.AddHostedService<MyFaultyService>();
47+
s.AddHostedService<MyFaultyBackgroundService>();
48+
})
49+
.UseWindowsService()
50+
//.UsePowerEventAwareWindowsService()
51+
.Build()
52+
.RunAsync();
53+
}
54+
}
55+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net5.0</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<ProjectReference Include="..\..\src\CodeCaster.WindowsServiceExtensions\CodeCaster.WindowsServiceExtensions.csproj" />
10+
</ItemGroup>
11+
12+
</Project>

0 commit comments

Comments
 (0)