Skip to content

Commit

Permalink
Add Hosting.WindowsService package #809
Browse files Browse the repository at this point in the history
  • Loading branch information
Tratcher committed Feb 8, 2019
1 parent 6107002 commit 0501f6c
Show file tree
Hide file tree
Showing 13 changed files with 277 additions and 38 deletions.
30 changes: 30 additions & 0 deletions Extensions.sln
Expand Up @@ -299,6 +299,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{B0D89499
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{15AC3300-D335-4C5C-9E3A-22F26904AB26}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Hosting.WindowsService", "src\Hosting\WindowsService\src\Microsoft.Extensions.Hosting.WindowsService.csproj", "{452F9F79-40E8-4B1C-8857-404062886080}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Hosting.WindowsService.Tests", "src\Hosting\WindowsService\test\Microsoft.Extensions.Hosting.WindowsService.Tests.csproj", "{47D27C64-B8B7-4D10-9112-1432EACC79B3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1785,6 +1789,30 @@ Global
{DE47CB52-3E6F-4A6A-910E-AE118A00CE1A}.Release|x64.Build.0 = Release|Any CPU
{DE47CB52-3E6F-4A6A-910E-AE118A00CE1A}.Release|x86.ActiveCfg = Release|Any CPU
{DE47CB52-3E6F-4A6A-910E-AE118A00CE1A}.Release|x86.Build.0 = Release|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Debug|Any CPU.Build.0 = Debug|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Debug|x64.ActiveCfg = Debug|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Debug|x64.Build.0 = Debug|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Debug|x86.ActiveCfg = Debug|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Debug|x86.Build.0 = Debug|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Release|Any CPU.ActiveCfg = Release|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Release|Any CPU.Build.0 = Release|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Release|x64.ActiveCfg = Release|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Release|x64.Build.0 = Release|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Release|x86.ActiveCfg = Release|Any CPU
{452F9F79-40E8-4B1C-8857-404062886080}.Release|x86.Build.0 = Release|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Debug|x64.ActiveCfg = Debug|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Debug|x64.Build.0 = Debug|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Debug|x86.ActiveCfg = Debug|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Debug|x86.Build.0 = Debug|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Release|Any CPU.Build.0 = Release|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Release|x64.ActiveCfg = Release|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Release|x64.Build.0 = Release|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Release|x86.ActiveCfg = Release|Any CPU
{47D27C64-B8B7-4D10-9112-1432EACC79B3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1919,6 +1947,8 @@ Global
{89B01C5B-42DF-4E99-847E-354B8C5FBA6F} = {6A293FDC-A13B-4137-87F9-C9225CF3542B}
{DE47CB52-3E6F-4A6A-910E-AE118A00CE1A} = {B0D89499-D1FA-4113-88C7-B82AB1D98EB6}
{15AC3300-D335-4C5C-9E3A-22F26904AB26} = {36617B81-CF74-4FCB-A7CA-E95DF3CA92FC}
{452F9F79-40E8-4B1C-8857-404062886080} = {6868A014-43FD-4047-B536-30D5D159D9D4}
{47D27C64-B8B7-4D10-9112-1432EACC79B3} = {6868A014-43FD-4047-B536-30D5D159D9D4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {814BFC88-0867-451F-AC8F-20FE107809B4}
Expand Down
1 change: 1 addition & 0 deletions eng/Dependencies.props
Expand Up @@ -31,6 +31,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="$(SystemRuntimeInteropServicesRuntimeInformationPackageVersion)" />
<LatestPackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" />
<LatestPackageReference Include="System.Security.Cryptography.Xml" Version="$(SystemSecurityCryptographyXmlPackageVersion)" />
<LatestPackageReference Include="System.ServiceProcess.ServiceController" Version="$(SystemServiceProcessServiceControllerPackageVersion)" />
<LatestPackageReference Include="System.Text.Encodings.Web" Version="$(SystemTextEncodingsWebPackageVersion)" />
<LatestPackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsPackageVersion)" />
<LatestPackageReference Include="System.ValueTuple" Version="$(SystemValueTuplePackageVersion)" />
Expand Down
1 change: 1 addition & 0 deletions eng/ProjectReferences.props
Expand Up @@ -30,6 +30,7 @@
<ProjectReferenceProvider Include="Microsoft.Extensions.Diagnostics.HealthChecks" ProjectPath="$(RepoRoot)src\HealthChecks\HealthChecks\src\Microsoft.Extensions.Diagnostics.HealthChecks.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.Hosting.Abstractions" ProjectPath="$(RepoRoot)src\Hosting\Abstractions\src\Microsoft.Extensions.Hosting.Abstractions.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.Hosting" ProjectPath="$(RepoRoot)src\Hosting\Hosting\src\Microsoft.Extensions.Hosting.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.Hosting.WindowsService" ProjectPath="$(RepoRoot)src\Hosting\WindowsService\src\Microsoft.Extensions.Hosting.WindowsService.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.Http" ProjectPath="$(RepoRoot)src\HttpClientFactory\Http\src\Microsoft.Extensions.Http.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.Http.Polly" ProjectPath="$(RepoRoot)src\HttpClientFactory\Polly\src\Microsoft.Extensions.Http.Polly.csproj" />
<ProjectReferenceProvider Include="Microsoft.JSInterop" ProjectPath="$(RepoRoot)src\JSInterop\Microsoft.JSInterop\src\Microsoft.JSInterop.csproj" />
Expand Down
4 changes: 4 additions & 0 deletions eng/Version.Details.xml
Expand Up @@ -50,6 +50,10 @@
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>53b8e52dbf12355ce845230f6bbe6c5425bb73e6</Sha>
</Dependency>
<Dependency Name="System.ServiceProcess.ServiceController" Version="4.6.0-preview.19105.1">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>53b8e52dbf12355ce845230f6bbe6c5425bb73e6</Sha>
</Dependency>
<Dependency Name="System.Text.Encodings.Web" Version="4.6.0-preview.19105.1">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>53b8e52dbf12355ce845230f6bbe6c5425bb73e6</Sha>
Expand Down
1 change: 1 addition & 0 deletions eng/Versions.props
Expand Up @@ -31,6 +31,7 @@
<SystemRuntimeCompilerServicesUnsafePackageVersion>4.6.0-preview.19105.1</SystemRuntimeCompilerServicesUnsafePackageVersion>
<SystemSecurityCryptographyCngPackageVersion>4.6.0-preview.19105.1</SystemSecurityCryptographyCngPackageVersion>
<SystemSecurityCryptographyXmlPackageVersion>4.6.0-preview.19105.1</SystemSecurityCryptographyXmlPackageVersion>
<SystemServiceProcessServiceControllerPackageVersion>4.6.0-preview.19105.1</SystemServiceProcessServiceControllerPackageVersion>
<SystemTextEncodingsWebPackageVersion>4.6.0-preview.19105.1</SystemTextEncodingsWebPackageVersion>
<!-- Workaround https://github.com/dotnet/cli/issues/10528-->
<MicrosoftNETCorePlatformsPackageVersion>3.0.0-preview.19105.1</MicrosoftNETCorePlatformsPackageVersion>
Expand Down
16 changes: 14 additions & 2 deletions src/Hosting/Hosting.sln
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28306.52
# Visual Studio Version 16
VisualStudioVersion = 16.0.28527.54
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Hosting", "Hosting\src\Microsoft.Extensions.Hosting.csproj", "{1F452D39-FCB3-440C-8198-D0D1AA05E33E}"
EndProject
Expand All @@ -15,6 +15,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleMsmqHost", "samples\S
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{D754FD75-091F-48DF-B3DA-91D0DB601E8B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Hosting.WindowsService", "WindowsService\src\Microsoft.Extensions.Hosting.WindowsService.csproj", "{EC466ABD-86DF-42A8-A307-1F0B28B36A2F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Hosting.WindowsService.Tests", "WindowsService\test\Microsoft.Extensions.Hosting.WindowsService.Tests.csproj", "{1A3BC0DA-CB53-4C21-BDD0-2B2CF83655FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -41,6 +45,14 @@ Global
{50586D64-6326-4CDB-9023-3A4D027263CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50586D64-6326-4CDB-9023-3A4D027263CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50586D64-6326-4CDB-9023-3A4D027263CF}.Release|Any CPU.Build.0 = Release|Any CPU
{EC466ABD-86DF-42A8-A307-1F0B28B36A2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC466ABD-86DF-42A8-A307-1F0B28B36A2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC466ABD-86DF-42A8-A307-1F0B28B36A2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC466ABD-86DF-42A8-A307-1F0B28B36A2F}.Release|Any CPU.Build.0 = Release|Any CPU
{1A3BC0DA-CB53-4C21-BDD0-2B2CF83655FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A3BC0DA-CB53-4C21-BDD0-2B2CF83655FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A3BC0DA-CB53-4C21-BDD0-2B2CF83655FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A3BC0DA-CB53-4C21-BDD0-2B2CF83655FA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>.NET Core hosting and startup infrastructures for Windows Services.</Description>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>hosting</PackageTags>
<IsShipping>true</IsShipping>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.Extensions.Hosting.Abstractions" />
<Reference Include="System.ServiceProcess.ServiceController" />
</ItemGroup>

</Project>
@@ -1,41 +1,42 @@
#if NET461
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace GenericHostSample
namespace Microsoft.Extensions.Hosting.WindowsService
{
public static class ServiceBaseLifetimeHostExtensions
{
public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
}

public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
{
return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
}
}

public class ServiceBaseLifetime : ServiceBase, IHostLifetime
{
private TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();

public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
public ServiceBaseLifetime(IHostingEnvironment environment, IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory)
{
Environment = environment ?? throw new ArgumentNullException(nameof(environment));
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
Logger = loggerFactory.CreateLogger("Microsoft.Hosting.Lifetime");
}

public IHostingEnvironment Environment { get; }
private IApplicationLifetime ApplicationLifetime { get; }
private ILogger Logger { get; }

public Task WaitForStartAsync(CancellationToken cancellationToken)
{
cancellationToken.Register(() => _delayStart.TrySetCanceled());
ApplicationLifetime.ApplicationStopping.Register(Stop);
ApplicationLifetime.ApplicationStarted.Register(() =>
{
Logger.LogInformation("Application started. Hosting environment: {envName}; Content root path: {contentRoot}",
Environment.EnvironmentName, Environment.ContentRootPath);
});
ApplicationLifetime.ApplicationStopping.Register(() =>
{
Logger.LogInformation("Application is shutting down...");
Stop();
});

new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
return _delayStart.Task;
Expand Down Expand Up @@ -76,4 +77,3 @@ protected override void OnStop()
}
}
}
#endif
@@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting.WindowsService;

namespace Microsoft.Extensions.Hosting
{
public static class ServiceBaseLifetimeHostBuilderExtensions
{
/// <summary>
/// Sets the host lifetime to ServiceBaseLifetime.
/// </summary>
/// <remarks>
/// This is context aware and will only activate under the following circumstances:
/// - Opted in via the config setting service=true, overrides detection
/// - Not opted out via the config setting service=false, overrides detection
/// - Not running in Development
/// - Running on Windows
/// - The debugger is not attached
/// </remarks>
/// <param name="hostBuilder"></param>
/// <returns></returns>
public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices((hostContext, services) =>
{
bool? isService = null;
var config = hostContext.Configuration;
// Opt out
if (string.Equals(config["service"], "false", StringComparison.OrdinalIgnoreCase))
{
isService = false;
}
// Opt in
else if (string.Equals(config["service"], "true", StringComparison.OrdinalIgnoreCase))
{
isService = true;
}
// Guess?
else if (!hostContext.HostingEnvironment.IsDevelopment()
&& RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
&& !Debugger.IsAttached)
{
isService = true;
}
if (isService == true)
{
services.AddSingleton<IHostLifetime, ServiceBaseLifetime>();
}
});
}
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="Microsoft.Extensions.Hosting.WindowsService" />
<Reference Include="Microsoft.Extensions.Logging.Testing" />
</ItemGroup>

</Project>
105 changes: 105 additions & 0 deletions src/Hosting/WindowsService/test/UseServiceBaseLifetimeTests.cs
@@ -0,0 +1,105 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting.Internal;
using Microsoft.Extensions.Hosting.WindowsService;
using Xunit;

namespace Microsoft.Extensions.Hosting
{
public class UseServiceBaseLifetimeTests
{
[Fact]
public void CanOptOutViaConfig()
{
var host = new HostBuilder()
.ConfigureAppConfiguration(config =>
{
config.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("service", "false")
});
})
.UseServiceBaseLifetime()
.Build();

using (host)
{
var lifetime = host.Services.GetRequiredService<IHostLifetime>();
Assert.IsType<ConsoleLifetime>(lifetime);
}
}

[Fact]
public void CanOptInViaConfig()
{
var host = new HostBuilder()
.ConfigureAppConfiguration(config =>
{
config.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("service", "true")
});
})
.UseServiceBaseLifetime()
.Build();

using (host)
{
var lifetime = host.Services.GetRequiredService<IHostLifetime>();
Assert.IsType<ServiceBaseLifetime>(lifetime);
}
}

[Fact]
public void CanOptOutViaDevelopment()
{
var host = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("environment", "development")
});
})
.UseServiceBaseLifetime()
.Build();

using (host)
{
var lifetime = host.Services.GetRequiredService<IHostLifetime>();
Assert.IsType<ConsoleLifetime>(lifetime);
}
}

[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux, "Only available on Windows")]
[OSSkipCondition(OperatingSystems.MacOSX, "Only available on Windows")]
public void CanOptInViaProduction()
{
var host = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("environment", "production")
});
})
.UseServiceBaseLifetime()
.Build();

using (host)
{
var lifetime = host.Services.GetRequiredService<IHostLifetime>();
Assert.IsType<ServiceBaseLifetime>(lifetime);
}
}
}
}

0 comments on commit 0501f6c

Please sign in to comment.