#1163 Implement the generic host
This commit is contained in:
parent
8b30efbe75
commit
ae9da9290e
60
Hosting.sln
60
Hosting.sln
|
|
@ -55,6 +55,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
Directory.Build.targets = Directory.Build.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenericHostSample", "samples\GenericHostSample\GenericHostSample.csproj", "{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Hosting", "src\Microsoft.Extensions.Hosting\Microsoft.Extensions.Hosting.csproj", "{1DA77D55-5DB9-4426-87DC-758579335944}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenericWebHost", "samples\GenericWebHost\GenericWebHost.csproj", "{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Hosting.Tests", "test\Microsoft.Extensions.Hosting.Tests\Microsoft.Extensions.Hosting.Tests.csproj", "{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -237,6 +245,54 @@ Global
|
|||
{F894D8C5-B760-4734-AD31-3CA6FC557CCF}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{F894D8C5-B760-4734-AD31-3CA6FC557CCF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F894D8C5-B760-4734-AD31-3CA6FC557CCF}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE}.Release|x86.Build.0 = Release|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944}.Release|x86.Build.0 = Release|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF}.Release|x86.Build.0 = Release|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -258,6 +314,10 @@ Global
|
|||
{EDFF02F0-A8A4-4EB1-A179-94D7500FB266} = {FA7D2012-C1B4-4AF7-9ADD-381B2004EA16}
|
||||
{58194285-5891-464A-A96B-0FE043029E8A} = {FA7D2012-C1B4-4AF7-9ADD-381B2004EA16}
|
||||
{F894D8C5-B760-4734-AD31-3CA6FC557CCF} = {FA7D2012-C1B4-4AF7-9ADD-381B2004EA16}
|
||||
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE} = {9C7520A0-F2EB-411C-8BB2-80B39C937217}
|
||||
{1DA77D55-5DB9-4426-87DC-758579335944} = {E0497F39-AFFB-4819-A116-E39E361915AB}
|
||||
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF} = {9C7520A0-F2EB-411C-8BB2-80B39C937217}
|
||||
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {AABD536D-E05F-409B-A716-535E0C478076}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||
<StartupObject>GenericHostSample.ProgramHelloWorld</StartupObject>
|
||||
<OutputType>Exe</OutputType>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Extensions.Hosting\Microsoft.Extensions.Hosting.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace GenericHostSample
|
||||
{
|
||||
internal class MyContainer
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace GenericHostSample
|
||||
{
|
||||
internal class MyContainerFactory : IServiceProviderFactory<MyContainer>
|
||||
{
|
||||
public MyContainer CreateBuilder(IServiceCollection services)
|
||||
{
|
||||
return new MyContainer();
|
||||
}
|
||||
|
||||
public IServiceProvider CreateServiceProvider(MyContainer containerBuilder)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericHostSample
|
||||
{
|
||||
public class MyServiceA : IHostedService
|
||||
{
|
||||
private bool _stopping;
|
||||
private Task _backgroundTask;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine("MyServiceA is starting.");
|
||||
_backgroundTask = BackgroundTask();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task BackgroundTask()
|
||||
{
|
||||
while (!_stopping)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
Console.WriteLine("MyServiceA is doing background work.");
|
||||
}
|
||||
|
||||
Console.WriteLine("MyServiceA background task is stopping.");
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine("MyServiceA is stopping.");
|
||||
_stopping = true;
|
||||
if (_backgroundTask != null)
|
||||
{
|
||||
// TODO: cancellation
|
||||
await _backgroundTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericHostSample
|
||||
{
|
||||
public class MyServiceB : IHostedService
|
||||
{
|
||||
private bool _stopping;
|
||||
private Task _backgroundTask;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine("MyServiceB is starting.");
|
||||
_backgroundTask = BackgroundTask();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task BackgroundTask()
|
||||
{
|
||||
while (!_stopping)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(7));
|
||||
Console.WriteLine("MyServiceB is doing background work.");
|
||||
}
|
||||
|
||||
Console.WriteLine("MyServiceB background task is stopping.");
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine("MyServiceB is stopping.");
|
||||
_stopping = true;
|
||||
if (_backgroundTask != null)
|
||||
{
|
||||
// TODO: cancellation
|
||||
await _backgroundTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericHostSample
|
||||
{
|
||||
public class ProgramExternallyControlled
|
||||
{
|
||||
private IHost _host;
|
||||
|
||||
public ProgramExternallyControlled()
|
||||
{
|
||||
_host = new HostBuilder()
|
||||
.UseServiceProviderFactory<MyContainer>(new MyContainerFactory())
|
||||
.ConfigureContainer<MyContainer>((hostContext, container) =>
|
||||
{
|
||||
})
|
||||
.ConfigureAppConfiguration((hostContext, config) =>
|
||||
{
|
||||
config.AddEnvironmentVariables();
|
||||
config.AddJsonFile("appsettings.json", optional: true);
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddScoped<IHostedService, MyServiceA>();
|
||||
services.AddScoped<IHostedService, MyServiceB>();
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_host.Start();
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
await _host.StopAsync(TimeSpan.FromSeconds(5));
|
||||
_host.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericHostSample
|
||||
{
|
||||
public class ProgramFullControl
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var host = new HostBuilder()
|
||||
.UseServiceProviderFactory<MyContainer>(new MyContainerFactory())
|
||||
.ConfigureContainer<MyContainer>((hostContext, container) =>
|
||||
{
|
||||
})
|
||||
.ConfigureAppConfiguration((hostContext, config) =>
|
||||
{
|
||||
config.AddEnvironmentVariables();
|
||||
config.AddJsonFile("appsettings.json", optional: true);
|
||||
config.AddCommandLine(args);
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddScoped<IHostedService, MyServiceA>();
|
||||
services.AddScoped<IHostedService, MyServiceB>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
var s = host.Services;
|
||||
|
||||
using (host)
|
||||
{
|
||||
Console.WriteLine("Starting!");
|
||||
|
||||
await host.StartAsync();
|
||||
|
||||
Console.WriteLine("Started! Press <enter> to stop.");
|
||||
|
||||
Console.ReadLine();
|
||||
|
||||
Console.WriteLine("Stopping!");
|
||||
|
||||
await host.StopAsync();
|
||||
|
||||
Console.WriteLine("Stopped!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericHostSample
|
||||
{
|
||||
public class ProgramHelloWorld
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var builder = new HostBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddScoped<IHostedService, MyServiceA>();
|
||||
services.AddScoped<IHostedService, MyServiceB>();
|
||||
});
|
||||
|
||||
await builder.RunConsoleAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#if NET461
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericHostSample
|
||||
{
|
||||
public class ServiceBaseControlled
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var builder = new HostBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddScoped<IHostedService, MyServiceA>();
|
||||
services.AddScoped<IHostedService, MyServiceB>();
|
||||
});
|
||||
|
||||
await builder.RunAsServiceAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
#if NET461
|
||||
using System;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericHostSample
|
||||
{
|
||||
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 Action<object> _startCallback;
|
||||
private Action<object> _stopCallback;
|
||||
private object _startState;
|
||||
private object _stopState;
|
||||
|
||||
public void RegisterDelayStartCallback(Action<object> callback, object state)
|
||||
{
|
||||
_startCallback = callback ?? throw new ArgumentNullException(nameof(callback));
|
||||
_startState = state ?? throw new ArgumentNullException(nameof(state));
|
||||
|
||||
Run(this);
|
||||
}
|
||||
|
||||
public void RegisterStopCallback(Action<object> callback, object state)
|
||||
{
|
||||
_stopCallback = callback ?? throw new ArgumentNullException(nameof(callback));
|
||||
_stopState = state ?? throw new ArgumentNullException(nameof(state));
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Stop();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
_startCallback(_startState);
|
||||
base.OnStart(args);
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
_stopCallback(_stopState);
|
||||
base.OnStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericWebHost
|
||||
{
|
||||
// We can't reference real servers in this sample without creating a circular repo dependency.
|
||||
// This fake server lets us at least run the code.
|
||||
public class FakeServer : IServer
|
||||
{
|
||||
public IFeatureCollection Features => new FeatureCollection();
|
||||
|
||||
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class FakeServerWebHostBuilderExtensions
|
||||
{
|
||||
public static IHostBuilder UseFakeServer(this IHostBuilder builder)
|
||||
{
|
||||
return builder.ConfigureServices((builderContext, services) => services.AddSingleton<IServer, FakeServer>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Hosting\Microsoft.AspNetCore.Hosting.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Extensions.Hosting\Microsoft.Extensions.Hosting.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericWebHost
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var host = new HostBuilder()
|
||||
.ConfigureAppConfiguration((hostContext, config) =>
|
||||
{
|
||||
config.AddEnvironmentVariables();
|
||||
config.AddJsonFile("appsettings.json", optional: true);
|
||||
config.AddCommandLine(args);
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
})
|
||||
.UseFakeServer()
|
||||
.ConfigureWebHost((hostContext, app) =>
|
||||
{
|
||||
app.Run(async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello World!");
|
||||
});
|
||||
})
|
||||
.UseConsoleLifetime()
|
||||
.Build();
|
||||
|
||||
var s = host.Services;
|
||||
|
||||
await host.RunAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Internal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace GenericWebHost
|
||||
{
|
||||
public static class WebHostExtensions
|
||||
{
|
||||
public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<HostBuilderContext, IApplicationBuilder> configureApp)
|
||||
{
|
||||
return builder.ConfigureServices((bulderContext, services) =>
|
||||
{
|
||||
services.Configure<WebHostServiceOptions>(options =>
|
||||
{
|
||||
options.ConfigureApp = configureApp;
|
||||
});
|
||||
services.AddSingleton<IHostedService, WebHostService>();
|
||||
|
||||
var listener = new DiagnosticListener("Microsoft.AspNetCore");
|
||||
services.AddSingleton<DiagnosticListener>(listener);
|
||||
services.AddSingleton<DiagnosticSource>(listener);
|
||||
|
||||
services.AddTransient<IHttpContextFactory, HttpContextFactory>();
|
||||
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
|
||||
|
||||
// Conjure up a RequestServices
|
||||
services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
|
||||
services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
|
||||
|
||||
// Ensure object pooling is available everywhere.
|
||||
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder.Internal;
|
||||
using Microsoft.AspNetCore.Hosting.Internal;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace GenericWebHost
|
||||
{
|
||||
internal class WebHostService : IHostedService
|
||||
{
|
||||
public WebHostService(IOptions<WebHostServiceOptions> options, IServiceProvider services, HostBuilderContext hostBuilderContext, IServer server,
|
||||
ILogger<WebHostService> logger, DiagnosticListener diagnosticListener, IHttpContextFactory httpContextFactory)
|
||||
{
|
||||
Options = options?.Value ?? throw new System.ArgumentNullException(nameof(options));
|
||||
|
||||
if (Options.ConfigureApp == null)
|
||||
{
|
||||
throw new ArgumentException(nameof(Options.ConfigureApp));
|
||||
}
|
||||
|
||||
Services = services ?? throw new ArgumentNullException(nameof(services));
|
||||
HostBuilderContext = hostBuilderContext ?? throw new ArgumentNullException(nameof(hostBuilderContext));
|
||||
Server = server ?? throw new ArgumentNullException(nameof(server));
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
DiagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
|
||||
HttpContextFactory = httpContextFactory ?? throw new ArgumentNullException(nameof(httpContextFactory));
|
||||
}
|
||||
|
||||
public WebHostServiceOptions Options { get; }
|
||||
public IServiceProvider Services { get; }
|
||||
public HostBuilderContext HostBuilderContext { get; }
|
||||
public IServer Server { get; }
|
||||
public ILogger<WebHostService> Logger { get; }
|
||||
public DiagnosticListener DiagnosticListener { get; }
|
||||
public IHttpContextFactory HttpContextFactory { get; }
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Server.Features.Get<IServerAddressesFeature>()?.Addresses.Add("http://localhost:5000");
|
||||
|
||||
var builder = new ApplicationBuilder(Services, Server.Features);
|
||||
Options.ConfigureApp(HostBuilderContext, builder);
|
||||
var app = builder.Build();
|
||||
|
||||
var httpApp = new HostingApplication(app, Logger, DiagnosticListener, HttpContextFactory);
|
||||
return Server.StartAsync(httpApp, cancellationToken);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Server.StopAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GenericWebHost
|
||||
{
|
||||
public class WebHostServiceOptions
|
||||
{
|
||||
public Action<HostBuilderContext, IApplicationBuilder> ConfigureApp { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
CancellationToken ApplicationStopped { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Requests termination the current application.
|
||||
/// Requests termination of the current application.
|
||||
/// </summary>
|
||||
void StopApplication();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
public interface IHostingEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the environment. This property is automatically set by the host to the value
|
||||
/// Gets or sets the name of the environment. The host automatically sets this property to the value
|
||||
/// of the "ASPNETCORE_ENVIRONMENT" environment variable, or "environment" as specified in any other configuration source.
|
||||
/// </summary>
|
||||
string EnvironmentName { get; set; }
|
||||
|
|
|
|||
|
|
@ -15,10 +15,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Hosting.Abstractions\Microsoft.AspNetCore.Hosting.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Hosting.Server.Abstractions\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a delegate for configuring the provided <see cref="LoggerFactory"/>. This may be called multiple times.
|
||||
/// Adds a delegate for configuring the provided <see cref="ILoggingBuilder"/>. This may be called multiple times.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">The <see cref="IWebHostBuilder" /> to configure.</param>
|
||||
/// <param name="configureLogging">The delegate that configures the <see cref="ILoggingBuilder"/>.</param>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Commonly used environment names.
|
||||
/// </summary>
|
||||
public static class EnvironmentName
|
||||
{
|
||||
public static readonly string Development = "Development";
|
||||
public static readonly string Staging = "Staging";
|
||||
public static readonly string Production = "Production";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Context containing the common services on the <see cref="IHost" />. Some properties may be null until set by the <see cref="IHost" />.
|
||||
/// </summary>
|
||||
public class HostBuilderContext
|
||||
{
|
||||
public HostBuilderContext(IDictionary<object, object> properties)
|
||||
{
|
||||
Properties = properties ?? throw new System.ArgumentNullException(nameof(properties));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IHostingEnvironment" /> initialized by the <see cref="IHost" />.
|
||||
/// </summary>
|
||||
public IHostingEnvironment HostingEnvironment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IConfiguration" /> containing the merged configuration of the application and the <see cref="IHost" />.
|
||||
/// </summary>
|
||||
public IConfiguration Configuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A central location for sharing state between components during the host building process.
|
||||
/// </summary>
|
||||
public IDictionary<object, object> Properties { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for HostBuilder configuration keys.
|
||||
/// </summary>
|
||||
public static class HostDefaults
|
||||
{
|
||||
/// <summary>
|
||||
/// The configuration key used to set <see cref="IHostingEnvironment.ApplicationName"/>.
|
||||
/// </summary>
|
||||
public static readonly string ApplicationKey = "applicationName";
|
||||
|
||||
/// <summary>
|
||||
/// The configuration key used to set <see cref="IHostingEnvironment.EnvironmentName"/>.
|
||||
/// </summary>
|
||||
public static readonly string EnvironmentKey = "environment";
|
||||
|
||||
/// <summary>
|
||||
/// The configuration key used to set <see cref="IHostingEnvironment.ContentRootPath"/>
|
||||
/// and <see cref="IHostingEnvironment.ContentRootFileProvider"/>.
|
||||
/// </summary>
|
||||
public static readonly string ContentRootKey = "contentRoot";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// 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.Threading;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
public static class HostingAbstractionsHostBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Start the web host and listen on the specified urls.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">The <see cref="IHostBuilder"/> to start.</param>
|
||||
/// <returns>The <see cref="IHostBuilder"/>.</returns>
|
||||
public static IHost Start(this IHostBuilder hostBuilder)
|
||||
{
|
||||
var host = hostBuilder.Build();
|
||||
host.StartAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
return host;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
public static class HostingAbstractionsHostExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts the host synchronously.
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
public static void Start(this IHost host)
|
||||
{
|
||||
host.StartAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to gracefully stop the host with the given timeout.
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <param name="timeout">The timeout for stopping gracefully. Once expired the
|
||||
/// server may terminate any remaining active connections.</param>
|
||||
/// <returns></returns>
|
||||
public static Task StopAsync(this IHost host, TimeSpan timeout)
|
||||
{
|
||||
return host.StopAsync(new CancellationTokenSource(timeout).Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Block the calling thread until shutdown is triggered via Ctrl+C or SIGTERM.
|
||||
/// </summary>
|
||||
/// <param name="host">The running <see cref="IHost"/>.</param>
|
||||
public static void WaitForShutdown(this IHost host)
|
||||
{
|
||||
host.WaitForShutdownAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a web application and block the calling thread until host shutdown.
|
||||
/// </summary>
|
||||
/// <param name="host">The <see cref="IHost"/> to run.</param>
|
||||
public static void Run(this IHost host)
|
||||
{
|
||||
host.RunAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a web application and returns a Task that only completes when the token is triggered or shutdown is triggered.
|
||||
/// </summary>
|
||||
/// <param name="host">The <see cref="IHost"/> to run.</param>
|
||||
/// <param name="token">The token to trigger shutdown.</param>
|
||||
public static async Task RunAsync(this IHost host, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
using (host)
|
||||
{
|
||||
await host.StartAsync(token);
|
||||
|
||||
await host.WaitForShutdownAsync(token);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Task that completes when shutdown is triggered via the given token.
|
||||
/// </summary>
|
||||
/// <param name="host">The running <see cref="IHost"/>.</param>
|
||||
/// <param name="token">The token to trigger shutdown.</param>
|
||||
public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
|
||||
|
||||
token.Register(state =>
|
||||
{
|
||||
((IApplicationLifetime)state).StopApplication();
|
||||
},
|
||||
applicationLifetime);
|
||||
|
||||
var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
applicationLifetime.ApplicationStopping.Register(obj =>
|
||||
{
|
||||
var tcs = (TaskCompletionSource<object>)obj;
|
||||
tcs.TrySetResult(null);
|
||||
}, waitForStop);
|
||||
|
||||
await waitForStop.Task;
|
||||
|
||||
// WebHost will use its default ShutdownTimeout if none is specified.
|
||||
await host.StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IHostingEnvironment"/>.
|
||||
/// </summary>
|
||||
public static class HostingEnvironmentExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the current hosting environment name is <see cref="EnvironmentName.Development"/>.
|
||||
/// </summary>
|
||||
/// <param name="hostingEnvironment">An instance of <see cref="IHostingEnvironment"/>.</param>
|
||||
/// <returns>True if the environment name is <see cref="EnvironmentName.Development"/>, otherwise false.</returns>
|
||||
public static bool IsDevelopment(this IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
if (hostingEnvironment == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
}
|
||||
|
||||
return hostingEnvironment.IsEnvironment(EnvironmentName.Development);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current hosting environment name is <see cref="EnvironmentName.Staging"/>.
|
||||
/// </summary>
|
||||
/// <param name="hostingEnvironment">An instance of <see cref="IHostingEnvironment"/>.</param>
|
||||
/// <returns>True if the environment name is <see cref="EnvironmentName.Staging"/>, otherwise false.</returns>
|
||||
public static bool IsStaging(this IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
if (hostingEnvironment == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
}
|
||||
|
||||
return hostingEnvironment.IsEnvironment(EnvironmentName.Staging);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current hosting environment name is <see cref="EnvironmentName.Production"/>.
|
||||
/// </summary>
|
||||
/// <param name="hostingEnvironment">An instance of <see cref="IHostingEnvironment"/>.</param>
|
||||
/// <returns>True if the environment name is <see cref="EnvironmentName.Production"/>, otherwise false.</returns>
|
||||
public static bool IsProduction(this IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
if (hostingEnvironment == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
}
|
||||
|
||||
return hostingEnvironment.IsEnvironment(EnvironmentName.Production);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the current hosting environment name against the specified value.
|
||||
/// </summary>
|
||||
/// <param name="hostingEnvironment">An instance of <see cref="IHostingEnvironment"/>.</param>
|
||||
/// <param name="environmentName">Environment name to validate against.</param>
|
||||
/// <returns>True if the specified name is the same as the current environment, otherwise false.</returns>
|
||||
public static bool IsEnvironment(
|
||||
this IHostingEnvironment hostingEnvironment,
|
||||
string environmentName)
|
||||
{
|
||||
if (hostingEnvironment == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
}
|
||||
|
||||
return string.Equals(
|
||||
hostingEnvironment.EnvironmentName,
|
||||
environmentName,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// 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.Threading;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows consumers to perform cleanup during a graceful shutdown.
|
||||
/// </summary>
|
||||
public interface 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; }
|
||||
|
||||
/// <summary>
|
||||
/// Requests termination of the current application.
|
||||
/// </summary>
|
||||
void StopApplication();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// A program abstraction.
|
||||
/// </summary>
|
||||
public interface IHost : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The programs configured services.
|
||||
/// </summary>
|
||||
IServiceProvider Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Start the program.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Used to abort program start.</param>
|
||||
/// <returns></returns>
|
||||
Task StartAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to gracefully stop the program.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
|
||||
/// <returns></returns>
|
||||
Task StopAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// 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 Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// A program initialization abstraction.
|
||||
/// </summary>
|
||||
public interface IHostBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A central location for sharing state between components during the host building process.
|
||||
/// </summary>
|
||||
IDictionary<object, object> Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Set up the configuration for the builder itself. This will be used to initialize the <see cref="IHostingEnvironment"/>
|
||||
/// for use later in the build process. This can be called multiple times and the results will be additive.
|
||||
/// </summary>
|
||||
/// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
|
||||
/// to construct the <see cref="IConfiguration"/> for the host.</param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate);
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the configuration for the remainder of the build process and application. This can be called multiple times and
|
||||
/// the results will be additive. The results will be available at <see cref="HostBuilderContext.Configuration"/> for
|
||||
/// subsequent operations, as well as in <see cref="IHost.Services"/>.
|
||||
/// </summary>
|
||||
/// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
|
||||
/// to construct the <see cref="IConfiguration"/> for the application.</param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate);
|
||||
|
||||
/// <summary>
|
||||
/// Adds services to the container. This can be called multiple times and the results will be additive.
|
||||
/// </summary>
|
||||
/// <param name="configureDelegate">The delegate for configuring the <see cref="IServiceCollection"/> that will be used
|
||||
/// to construct the <see cref="IServiceProvider"/>.</param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate);
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the factory used to create the service provider.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContainerBuilder"></typeparam>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
|
||||
|
||||
/// <summary>
|
||||
/// Enables configuring the instantiated dependency container. This can be called multiple times and
|
||||
/// the results will be additive.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContainerBuilder"></typeparam>
|
||||
/// <param name="configureDelegate"></param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate);
|
||||
|
||||
/// <summary>
|
||||
/// Run the given actions to initialize the host. This can only be called once.
|
||||
/// </summary>
|
||||
/// <returns>An initialized <see cref="IHost"/></returns>
|
||||
IHost Build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
public interface IHostLifetime
|
||||
{
|
||||
/// <summary>
|
||||
/// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> which will wait until the callback is invoked before
|
||||
/// continuing. This can be used to delay startup until signaled by an external event.
|
||||
/// </summary>
|
||||
/// <param name="callback">A callback that will be invoked when the host should continue.</param>
|
||||
/// <param name="state">State to pass to the callback.</param>
|
||||
void RegisterDelayStartCallback(Action<object> callback, object state);
|
||||
|
||||
/// <summary>
|
||||
/// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> to register the given callback for initiating the
|
||||
/// application shutdown process.
|
||||
/// </summary>
|
||||
/// <param name="callback">A callback to invoke when an external signal indicates the application should stop.</param>
|
||||
/// <param name="state">State to pass to the callback.</param>
|
||||
void RegisterStopCallback(Action<object> callback, object state);
|
||||
|
||||
/// <summary>
|
||||
/// Called from <see cref="IHost.StopAsync(CancellationToken)"/> to indicate that the host as stopped and clean up resources.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
|
||||
/// <returns></returns>
|
||||
Task StopAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,11 +14,13 @@ namespace Microsoft.Extensions.Hosting
|
|||
/// <summary>
|
||||
/// Triggered when the application host is ready to start the service.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
|
||||
Task StartAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host is performing a graceful shutdown.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
|
||||
Task StopAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about the web hosting environment an application is running in.
|
||||
/// </summary>
|
||||
public interface IHostingEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the environment. The host automatically sets this property to the value of the
|
||||
/// of the "environment" key as specified in configuration.
|
||||
/// </summary>
|
||||
string EnvironmentName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the application. This property is automatically set by the host to the assembly containing
|
||||
/// the application entry point.
|
||||
/// </summary>
|
||||
string ApplicationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the absolute path to the directory that contains the application content files.
|
||||
/// </summary>
|
||||
string ContentRootPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an <see cref="IFileProvider"/> pointing at <see cref="ContentRootPath"/>.
|
||||
/// </summary>
|
||||
IFileProvider ContentRootFileProvider { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,14 @@
|
|||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>hosting</PackageTags>
|
||||
<EnableApiCheck>false</EnableApiCheck>
|
||||
<RootNamespace>Microsoft.Extensions.Hosting</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
public class ConsoleLifetimeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if host lifetime status messages should be written to the console such as on startup.
|
||||
/// The default is true.
|
||||
/// </summary>
|
||||
public bool WriteStatusMessages { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
// 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.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting.Internal;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// A program initialization utility.
|
||||
/// </summary>
|
||||
public class HostBuilder : IHostBuilder
|
||||
{
|
||||
private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
|
||||
private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();
|
||||
private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>();
|
||||
private List<IConfigureContainerAdapter> _configureContainerActions = new List<IConfigureContainerAdapter>();
|
||||
private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
|
||||
private bool _hostBuilt;
|
||||
private IConfiguration _hostConfiguration;
|
||||
private IConfiguration _appConfiguration;
|
||||
private HostBuilderContext _hostBuilderContext;
|
||||
private IHostingEnvironment _hostingEnvironment;
|
||||
private IServiceProvider _appServices;
|
||||
|
||||
/// <summary>
|
||||
/// A central location for sharing state between components during the host building process.
|
||||
/// </summary>
|
||||
public IDictionary<object, object> Properties => new Dictionary<object, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Set up the configuration for the builder itself. This will be used to initialize the <see cref="IHostingEnvironment"/>
|
||||
/// for use later in the build process. This can be called multiple times and the results will be additive.
|
||||
/// </summary>
|
||||
/// <param name="configureDelegate"></param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
|
||||
{
|
||||
_configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the configuration for the remainder of the build process and application. This can be called multiple times and
|
||||
/// the results will be additive. The results will be available at <see cref="HostBuilderContext.Configuration"/> for
|
||||
/// subsequent operations, as well as in <see cref="IHost.Services"/>.
|
||||
/// </summary>
|
||||
/// <param name="configureDelegate"></param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
|
||||
{
|
||||
_configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds services to the container. This can be called multiple times and the results will be additive.
|
||||
/// </summary>
|
||||
/// <param name="configureDelegate"></param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
|
||||
{
|
||||
_configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the factory used to create the service provider.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContainerBuilder"></typeparam>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
|
||||
{
|
||||
_serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables configuring the instantiated dependency container. This can be called multiple times and
|
||||
/// the results will be additive.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContainerBuilder"></typeparam>
|
||||
/// <param name="configureDelegate"></param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
|
||||
{
|
||||
_configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate
|
||||
?? throw new ArgumentNullException(nameof(configureDelegate))));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the given actions to initialize the host. This can only be called once.
|
||||
/// </summary>
|
||||
/// <returns>An initialized <see cref="IHost"/></returns>
|
||||
public IHost Build()
|
||||
{
|
||||
if (_hostBuilt)
|
||||
{
|
||||
throw new InvalidOperationException("Build can only be called once.");
|
||||
}
|
||||
_hostBuilt = true;
|
||||
|
||||
BuildHostConfiguration();
|
||||
CreateHostingEnvironment();
|
||||
CreateHostBuilderContext();
|
||||
BuildAppConfiguration();
|
||||
CreateServiceProvider();
|
||||
|
||||
return new Host(_appServices);
|
||||
}
|
||||
|
||||
private void BuildHostConfiguration()
|
||||
{
|
||||
var configBuilder = new ConfigurationBuilder();
|
||||
foreach (var buildAction in _configureHostConfigActions)
|
||||
{
|
||||
buildAction(configBuilder);
|
||||
}
|
||||
_hostConfiguration = configBuilder.Build();
|
||||
}
|
||||
|
||||
private void CreateHostingEnvironment()
|
||||
{
|
||||
_hostingEnvironment = new HostingEnvironment()
|
||||
{
|
||||
ApplicationName = _hostConfiguration[HostDefaults.ApplicationKey],
|
||||
EnvironmentName = _hostConfiguration[HostDefaults.EnvironmentKey] ?? EnvironmentName.Production,
|
||||
ContentRootPath = ResolveContentRootPath(_hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory),
|
||||
};
|
||||
_hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath);
|
||||
}
|
||||
|
||||
private string ResolveContentRootPath(string contentRootPath, string basePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentRootPath))
|
||||
{
|
||||
return basePath;
|
||||
}
|
||||
if (Path.IsPathRooted(contentRootPath))
|
||||
{
|
||||
return contentRootPath;
|
||||
}
|
||||
return Path.Combine(Path.GetFullPath(basePath), contentRootPath);
|
||||
}
|
||||
|
||||
private void CreateHostBuilderContext()
|
||||
{
|
||||
_hostBuilderContext = new HostBuilderContext(Properties)
|
||||
{
|
||||
HostingEnvironment = _hostingEnvironment,
|
||||
Configuration = _hostConfiguration
|
||||
};
|
||||
}
|
||||
|
||||
private void BuildAppConfiguration()
|
||||
{
|
||||
var configBuilder = new ConfigurationBuilder();
|
||||
configBuilder.AddConfiguration(_hostConfiguration);
|
||||
foreach (var buildAction in _configureAppConfigActions)
|
||||
{
|
||||
buildAction(_hostBuilderContext, configBuilder);
|
||||
}
|
||||
_appConfiguration = configBuilder.Build();
|
||||
_hostBuilderContext.Configuration = _appConfiguration;
|
||||
}
|
||||
|
||||
private void CreateServiceProvider()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(_hostingEnvironment);
|
||||
services.AddSingleton(_hostBuilderContext);
|
||||
services.AddSingleton(_appConfiguration);
|
||||
services.AddSingleton<IApplicationLifetime, ApplicationLifetime>();
|
||||
services.AddSingleton<IHostLifetime, HostProcessLifetime>();
|
||||
services.AddOptions();
|
||||
services.AddLogging();
|
||||
|
||||
foreach (var configureServicesAction in _configureServicesActions)
|
||||
{
|
||||
configureServicesAction(_hostBuilderContext, services);
|
||||
}
|
||||
|
||||
var containerBuilder = _serviceProviderFactory.CreateBuilder(services);
|
||||
|
||||
foreach (var containerAction in _configureContainerActions)
|
||||
{
|
||||
containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
|
||||
}
|
||||
|
||||
_appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
|
||||
|
||||
if (_appServices == null)
|
||||
{
|
||||
throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
public static class HostingHostBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Specify the environment to be used by the web host.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
|
||||
/// <param name="environment">The environment to host the application in.</param>
|
||||
/// <returns>The <see cref="IHostBuilder"/>.</returns>
|
||||
public static IHostBuilder UseEnvironment(this IHostBuilder hostBuilder, string environment)
|
||||
{
|
||||
return hostBuilder.ConfigureHostConfiguration(configBuilder =>
|
||||
{
|
||||
configBuilder.AddInMemoryCollection(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HostDefaults.EnvironmentKey,
|
||||
environment ?? throw new ArgumentNullException(nameof(environment)))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the content root directory to be used by the web host.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
|
||||
/// <param name="contentRoot">Path to root directory of the application.</param>
|
||||
/// <returns>The <see cref="IHostBuilder"/>.</returns>
|
||||
public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder, string contentRoot)
|
||||
{
|
||||
return hostBuilder.ConfigureHostConfiguration(configBuilder =>
|
||||
{
|
||||
configBuilder.AddInMemoryCollection(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HostDefaults.ContentRootKey,
|
||||
contentRoot ?? throw new ArgumentNullException(nameof(contentRoot)))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a delegate for configuring the provided <see cref="ILoggingBuilder"/>. This may be called multiple times.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">The <see cref="IHostBuilder" /> to configure.</param>
|
||||
/// <param name="configureLogging">The delegate that configures the <see cref="ILoggingBuilder"/>.</param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
public static IHostBuilder ConfigureLogging(this IHostBuilder hostBuilder, Action<HostBuilderContext, ILoggingBuilder> configureLogging)
|
||||
{
|
||||
return hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listens for Ctrl+C or SIGTERM and calls <see cref="IApplicationLifetime.StopApplication"/> to start the shutdown process.
|
||||
/// This will unblock extensions like RunAsync and WaitForShutdownAsync.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">The <see cref="IHostBuilder" /> to configure.</param>
|
||||
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
|
||||
public static IHostBuilder UseConsoleLifetime(this IHostBuilder hostBuilder)
|
||||
{
|
||||
return hostBuilder.ConfigureServices((context, collection) => collection.AddSingleton<IHostLifetime, ConsoleLifetime>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables console support, builds and starts the host, and waits for Ctrl+C or SIGTERM to shut down.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">The <see cref="IHostBuilder" /> to configure.</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task RunConsoleAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return hostBuilder.UseConsoleLifetime().Build().RunAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
// 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.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows consumers to perform cleanup during a graceful shutdown.
|
||||
/// </summary>
|
||||
public class ApplicationLifetime : IApplicationLifetime
|
||||
{
|
||||
private readonly CancellationTokenSource _startedSource = new CancellationTokenSource();
|
||||
private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource();
|
||||
private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource();
|
||||
private readonly ILogger<ApplicationLifetime> _logger;
|
||||
|
||||
public ApplicationLifetime(ILogger<ApplicationLifetime> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host has fully started and is about to wait
|
||||
/// for a graceful shutdown.
|
||||
/// </summary>
|
||||
public CancellationToken ApplicationStarted => _startedSource.Token;
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host is performing a graceful shutdown.
|
||||
/// Request may still be in flight. Shutdown will block until this event completes.
|
||||
/// </summary>
|
||||
public CancellationToken ApplicationStopping => _stoppingSource.Token;
|
||||
|
||||
/// <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>
|
||||
public CancellationToken ApplicationStopped => _stoppedSource.Token;
|
||||
|
||||
/// <summary>
|
||||
/// Signals the ApplicationStopping event and blocks until it completes.
|
||||
/// </summary>
|
||||
public void StopApplication()
|
||||
{
|
||||
// Lock on CTS to synchronize multiple calls to StopApplication. This guarantees that the first call
|
||||
// to StopApplication and its callbacks run to completion before subsequent calls to StopApplication,
|
||||
// which will no-op since the first call already requested cancellation, get a chance to execute.
|
||||
lock (_stoppingSource)
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecuteHandlers(_stoppingSource);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ApplicationError(LoggerEventIds.ApplicationStoppingException,
|
||||
"An error occurred stopping the application",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals the ApplicationStarted event and blocks until it completes.
|
||||
/// </summary>
|
||||
public void NotifyStarted()
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecuteHandlers(_startedSource);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ApplicationError(LoggerEventIds.ApplicationStartupException,
|
||||
"An error occurred starting the application",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals the ApplicationStopped event and blocks until it completes.
|
||||
/// </summary>
|
||||
public void NotifyStopped()
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecuteHandlers(_stoppedSource);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ApplicationError(LoggerEventIds.ApplicationStoppedException,
|
||||
"An error occurred stopping the application",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteHandlers(CancellationTokenSource cancel)
|
||||
{
|
||||
// Noop if this is already cancelled
|
||||
if (cancel.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Run the cancellation token callbacks
|
||||
cancel.Cancel(throwOnFirstException: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (exceptions == null)
|
||||
{
|
||||
exceptions = new List<Exception>();
|
||||
}
|
||||
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
|
||||
// Throw an aggregate exception if there were any exceptions
|
||||
if (exceptions != null)
|
||||
{
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
internal class ConfigureContainerAdapter<TContainerBuilder> : IConfigureContainerAdapter
|
||||
{
|
||||
private Action<HostBuilderContext, TContainerBuilder> _action;
|
||||
|
||||
public ConfigureContainerAdapter(Action<HostBuilderContext, TContainerBuilder> action)
|
||||
{
|
||||
_action = action ?? throw new ArgumentNullException(nameof(action));
|
||||
}
|
||||
|
||||
public void ConfigureContainer(HostBuilderContext hostContext, object containerBuilder)
|
||||
{
|
||||
_action(hostContext, (TContainerBuilder)containerBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Listens for Ctrl+C or SIGTERM and initiates shutdown.
|
||||
/// </summary>
|
||||
public class ConsoleLifetime : IHostLifetime
|
||||
{
|
||||
public ConsoleLifetime(IOptions<ConsoleLifetimeOptions> options, IHostingEnvironment environment, IApplicationLifetime applicationLifetime)
|
||||
{
|
||||
Options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
Environment = environment ?? throw new ArgumentNullException(nameof(environment));
|
||||
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
|
||||
}
|
||||
|
||||
private ConsoleLifetimeOptions Options { get; }
|
||||
|
||||
private IHostingEnvironment Environment { get; }
|
||||
|
||||
private IApplicationLifetime ApplicationLifetime { get; }
|
||||
|
||||
public void RegisterDelayStartCallback(Action<object> callback, object state)
|
||||
{
|
||||
if (Options.WriteStatusMessages)
|
||||
{
|
||||
ApplicationLifetime.ApplicationStarted.Register(() =>
|
||||
{
|
||||
Console.WriteLine("Application started. Press Ctrl+C to shut down.");
|
||||
Console.WriteLine($"Hosting environment: {Environment.EnvironmentName}");
|
||||
Console.WriteLine($"Content root path: {Environment.ContentRootPath}");
|
||||
});
|
||||
}
|
||||
|
||||
// Console applications start immediately.
|
||||
callback(state);
|
||||
}
|
||||
|
||||
public void RegisterStopCallback(Action<object> callback, object state)
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => callback(state);
|
||||
Console.CancelKeyPress += (sender, e) =>
|
||||
{
|
||||
e.Cancel = true;
|
||||
callback(state);
|
||||
};
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// There's nothing to do here
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// 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.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
internal class Host : IHost
|
||||
{
|
||||
private ILogger<Host> _logger;
|
||||
private IHostLifetime _hostLifetime;
|
||||
private ApplicationLifetime _applicationLifetime;
|
||||
private IEnumerable<IHostedService> _hostedServices;
|
||||
|
||||
internal Host(IServiceProvider services)
|
||||
{
|
||||
Services = services ?? throw new ArgumentNullException(nameof(services));
|
||||
_applicationLifetime = Services.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
|
||||
_logger = Services.GetRequiredService<ILogger<Host>>();
|
||||
_hostLifetime = Services.GetRequiredService<IHostLifetime>();
|
||||
}
|
||||
|
||||
public IServiceProvider Services { get; }
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
_logger.Starting();
|
||||
|
||||
var delayStart = new TaskCompletionSource<object>();
|
||||
cancellationToken.Register(obj => ((TaskCompletionSource<object>)obj).TrySetCanceled(), delayStart);
|
||||
_hostLifetime.RegisterDelayStartCallback(obj => ((TaskCompletionSource<object>)obj).TrySetResult(null), delayStart);
|
||||
_hostLifetime.RegisterStopCallback(obj => (obj as IApplicationLifetime)?.StopApplication(), _applicationLifetime);
|
||||
|
||||
await delayStart.Task;
|
||||
|
||||
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
|
||||
|
||||
foreach (var hostedService in _hostedServices)
|
||||
{
|
||||
// Fire IHostedService.Start
|
||||
await hostedService.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Fire IApplicationLifetime.Started
|
||||
_applicationLifetime?.NotifyStarted();
|
||||
|
||||
_logger.Started();
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
_logger.Stopping();
|
||||
|
||||
// Trigger IApplicationLifetime.ApplicationStopping
|
||||
_applicationLifetime?.StopApplication();
|
||||
|
||||
IList<Exception> exceptions = new List<Exception>();
|
||||
if (_hostedServices != null) // Started?
|
||||
{
|
||||
foreach (var hostedService in _hostedServices.Reverse())
|
||||
{
|
||||
try
|
||||
{
|
||||
await hostedService.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _hostLifetime.StopAsync(cancellationToken);
|
||||
|
||||
// Fire IApplicationLifetime.Stopped
|
||||
_applicationLifetime?.NotifyStopped();
|
||||
|
||||
if (exceptions.Count > 0)
|
||||
{
|
||||
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
|
||||
_logger.StoppedWithException(ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
_logger.Stopped();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(Services as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
public class HostProcessLifetime : IHostLifetime
|
||||
{
|
||||
public void RegisterDelayStartCallback(Action<object> callback, object state)
|
||||
{
|
||||
// Never delays start.
|
||||
callback(state);
|
||||
}
|
||||
|
||||
public void RegisterStopCallback(Action<object> callback, object state)
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => callback(state);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// 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 Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
public class HostingEnvironment : IHostingEnvironment
|
||||
{
|
||||
public string EnvironmentName { get; set; }
|
||||
|
||||
public string ApplicationName { get; set; }
|
||||
|
||||
public string ContentRootPath { get; set; }
|
||||
|
||||
public IFileProvider ContentRootFileProvider { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
internal static class HostingLoggerExtensions
|
||||
{
|
||||
public static void ApplicationError(this ILogger logger, EventId eventId, string message, Exception exception)
|
||||
{
|
||||
var reflectionTypeLoadException = exception as ReflectionTypeLoadException;
|
||||
if (reflectionTypeLoadException != null)
|
||||
{
|
||||
foreach (var ex in reflectionTypeLoadException.LoaderExceptions)
|
||||
{
|
||||
message = message + Environment.NewLine + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogCritical(
|
||||
eventId: eventId,
|
||||
message: message,
|
||||
exception: exception);
|
||||
}
|
||||
|
||||
public static void Starting(this ILogger logger)
|
||||
{
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.LogDebug(
|
||||
eventId: LoggerEventIds.Starting,
|
||||
message: "Hosting starting");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Started(this ILogger logger)
|
||||
{
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.LogDebug(
|
||||
eventId: LoggerEventIds.Started,
|
||||
message: "Hosting started");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stopping(this ILogger logger)
|
||||
{
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.LogDebug(
|
||||
eventId: LoggerEventIds.Stopping,
|
||||
message: "Hosting stopping");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stopped(this ILogger logger)
|
||||
{
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.LogDebug(
|
||||
eventId: LoggerEventIds.Stopped,
|
||||
message: "Hosting stopped");
|
||||
}
|
||||
}
|
||||
|
||||
public static void StoppedWithException(this ILogger logger, Exception ex)
|
||||
{
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.LogDebug(
|
||||
eventId: LoggerEventIds.StoppedWithException,
|
||||
exception: ex,
|
||||
message: "Hosting shutdown exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
internal interface IConfigureContainerAdapter
|
||||
{
|
||||
void ConfigureContainer(HostBuilderContext hostContext, object containerBuilder);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// 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 Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
internal interface IServiceFactoryAdapter
|
||||
{
|
||||
object CreateBuilder(IServiceCollection services);
|
||||
|
||||
IServiceProvider CreateServiceProvider(object containerBuilder);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
internal static class LoggerEventIds
|
||||
{
|
||||
public const int Starting = 1;
|
||||
public const int Started = 2;
|
||||
public const int Stopping = 3;
|
||||
public const int Stopped = 4;
|
||||
public const int StoppedWithException = 5;
|
||||
public const int ApplicationStartupException = 6;
|
||||
public const int ApplicationStoppingException = 7;
|
||||
public const int ApplicationStoppedException = 8;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// 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 Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Internal
|
||||
{
|
||||
internal class ServiceFactoryAdapter<TContainerBuilder> : IServiceFactoryAdapter
|
||||
{
|
||||
private IServiceProviderFactory<TContainerBuilder> _serviceProviderFactory;
|
||||
|
||||
public ServiceFactoryAdapter(IServiceProviderFactory<TContainerBuilder> serviceProviderFactory)
|
||||
{
|
||||
_serviceProviderFactory = serviceProviderFactory ?? throw new System.ArgumentNullException(nameof(serviceProviderFactory));
|
||||
}
|
||||
|
||||
public object CreateBuilder(IServiceCollection services)
|
||||
{
|
||||
return _serviceProviderFactory.CreateBuilder(services);
|
||||
}
|
||||
|
||||
public IServiceProvider CreateServiceProvider(object containerBuilder)
|
||||
{
|
||||
return _serviceProviderFactory.CreateServiceProvider((TContainerBuilder)containerBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>.NET Core hosting and startup infrastructures for applications.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>hosting</PackageTags>
|
||||
<EnableApiCheck>false</EnableApiCheck>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Extensions.Hosting.Abstractions\Microsoft.Extensions.Hosting.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Tests.Fakes
|
||||
{
|
||||
public class FakeHostLifetime : IHostLifetime
|
||||
{
|
||||
public int StartCount { get; internal set; }
|
||||
public int StoppingCount { get; internal set; }
|
||||
public int StopCount { get; internal set; }
|
||||
|
||||
public Action<Action<object>, object> StartAction { get; set; }
|
||||
public Action<Action<object>, object> StoppingAction { get; set; }
|
||||
public Action StopAction { get; set; }
|
||||
|
||||
public void RegisterDelayStartCallback(Action<object> callback, object state)
|
||||
{
|
||||
StartCount++;
|
||||
StartAction?.Invoke(callback, state);
|
||||
}
|
||||
|
||||
public void RegisterStopCallback(Action<object> callback, object state)
|
||||
{
|
||||
StoppingCount++;
|
||||
StoppingAction?.Invoke(callback, state);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
StopCount++;
|
||||
StopAction?.Invoke();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Tests.Fakes
|
||||
{
|
||||
public class FakeHostedService : IHostedService, IDisposable
|
||||
{
|
||||
public int StartCount { get; internal set; }
|
||||
public int StopCount { get; internal set; }
|
||||
public int DisposeCount { get; internal set; }
|
||||
|
||||
public Action<CancellationToken> StartAction { get; set; }
|
||||
public Action<CancellationToken> StopAction { get; set; }
|
||||
public Action DisposeAction { get; set; }
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
StartCount++;
|
||||
StartAction?.Invoke(cancellationToken);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
StopCount++;
|
||||
StopAction?.Invoke(cancellationToken);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeCount++;
|
||||
DisposeAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Fakes
|
||||
{
|
||||
public class FakeOptions
|
||||
{
|
||||
public bool Configured { get; set; }
|
||||
public string Environment { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Fakes
|
||||
{
|
||||
public class FakeService : IFakeEveryService, IDisposable
|
||||
{
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// 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 Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Fakes
|
||||
{
|
||||
public class FakeServiceCollection : IServiceProvider
|
||||
{
|
||||
private IServiceProvider _inner;
|
||||
private IServiceCollection _services;
|
||||
|
||||
public bool FancyMethodCalled { get; private set; }
|
||||
|
||||
public IServiceCollection Services => _services;
|
||||
|
||||
public string State { get; set; }
|
||||
|
||||
public object GetService(Type serviceType)
|
||||
{
|
||||
return _inner.GetService(serviceType);
|
||||
}
|
||||
|
||||
public void Populate(IServiceCollection services)
|
||||
{
|
||||
_services = services;
|
||||
_services.AddSingleton<FakeServiceCollection>(this);
|
||||
}
|
||||
|
||||
public void Build()
|
||||
{
|
||||
_inner = _services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
public void MyFancyContainerMethod()
|
||||
{
|
||||
FancyMethodCalled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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 Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Fakes
|
||||
{
|
||||
public class FakeServiceProviderFactory : IServiceProviderFactory<FakeServiceCollection>
|
||||
{
|
||||
public FakeServiceCollection CreateBuilder(IServiceCollection services)
|
||||
{
|
||||
var container = new FakeServiceCollection();
|
||||
container.Populate(services);
|
||||
return container;
|
||||
}
|
||||
|
||||
public IServiceProvider CreateServiceProvider(FakeServiceCollection containerBuilder)
|
||||
{
|
||||
containerBuilder.Build();
|
||||
return containerBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Fakes
|
||||
{
|
||||
interface IFakeEveryService :
|
||||
IFakeScopedService,
|
||||
IFakeServiceInstance,
|
||||
IFakeSingletonService
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Fakes
|
||||
{
|
||||
public interface IFakeScopedService : IFakeService
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Fakes
|
||||
{
|
||||
public interface IFakeService { }
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Fakes
|
||||
{
|
||||
interface IFakeServiceInstance : IFakeService
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Hosting.Fakes
|
||||
{
|
||||
interface IFakeSingletonService : IFakeService
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,459 @@
|
|||
// 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.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting.Fakes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
public class HostBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConfigureHostConfigurationPropagated()
|
||||
{
|
||||
var host = new HostBuilder()
|
||||
.ConfigureHostConfiguration(configBuilder =>
|
||||
{
|
||||
configBuilder.AddInMemoryCollection(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("key1", "value1")
|
||||
});
|
||||
})
|
||||
.ConfigureHostConfiguration(configBuilder =>
|
||||
{
|
||||
configBuilder.AddInMemoryCollection(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("key2", "value2")
|
||||
});
|
||||
})
|
||||
.ConfigureHostConfiguration(configBuilder =>
|
||||
{
|
||||
configBuilder.AddInMemoryCollection(new[]
|
||||
{
|
||||
// Hides value2
|
||||
new KeyValuePair<string, string>("key2", "value3")
|
||||
});
|
||||
})
|
||||
.ConfigureAppConfiguration((context, configBuilder) =>
|
||||
{
|
||||
Assert.Equal("value1", context.Configuration["key1"]);
|
||||
Assert.Equal("value3", context.Configuration["key2"]);
|
||||
var config = configBuilder.Build();
|
||||
Assert.Equal("value1", config["key1"]);
|
||||
Assert.Equal("value3", config["key2"]);
|
||||
})
|
||||
.Build();
|
||||
|
||||
using (host)
|
||||
{
|
||||
var config = host.Services.GetRequiredService<IConfiguration>();
|
||||
Assert.Equal("value1", config["key1"]);
|
||||
Assert.Equal("value3", config["key2"]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanConfigureAppConfigurationAndRetrieveFromDI()
|
||||
{
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureAppConfiguration((_, configBuilder) =>
|
||||
{
|
||||
configBuilder.AddInMemoryCollection(
|
||||
new KeyValuePair<string, string>[]
|
||||
{
|
||||
new KeyValuePair<string, string>("key1", "value1")
|
||||
});
|
||||
})
|
||||
.ConfigureAppConfiguration((_, configBuilder) =>
|
||||
{
|
||||
configBuilder.AddInMemoryCollection(
|
||||
new KeyValuePair<string, string>[]
|
||||
{
|
||||
new KeyValuePair<string, string>("key2", "value2")
|
||||
});
|
||||
})
|
||||
.ConfigureAppConfiguration((_, configBuilder) =>
|
||||
{
|
||||
configBuilder.AddInMemoryCollection(
|
||||
new KeyValuePair<string, string>[]
|
||||
{
|
||||
// Hides value2
|
||||
new KeyValuePair<string, string>("key2", "value3")
|
||||
});
|
||||
});
|
||||
|
||||
using (var host = hostBuilder.Build())
|
||||
{
|
||||
var config = host.Services.GetService<IConfiguration>();
|
||||
Assert.NotNull(config);
|
||||
Assert.Equal("value1", config["key1"]);
|
||||
Assert.Equal("value3", config["key2"]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultIHostingEnvironmentValues()
|
||||
{
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureAppConfiguration((hostContext, appConfig) =>
|
||||
{
|
||||
var env = hostContext.HostingEnvironment;
|
||||
Assert.Equal(EnvironmentName.Production, env.EnvironmentName);
|
||||
Assert.Null(env.ApplicationName);
|
||||
Assert.Equal(AppContext.BaseDirectory, env.ContentRootPath);
|
||||
Assert.IsAssignableFrom<PhysicalFileProvider>(env.ContentRootFileProvider);
|
||||
});
|
||||
|
||||
using (var host = hostBuilder.Build())
|
||||
{
|
||||
var env = host.Services.GetRequiredService<IHostingEnvironment>();
|
||||
Assert.Equal(EnvironmentName.Production, env.EnvironmentName);
|
||||
Assert.Null(env.ApplicationName);
|
||||
Assert.Equal(AppContext.BaseDirectory, env.ContentRootPath);
|
||||
Assert.IsAssignableFrom<PhysicalFileProvider>(env.ContentRootFileProvider);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfigBasedSettingsConfigBasedOverride()
|
||||
{
|
||||
var settings = new Dictionary<string, string>
|
||||
{
|
||||
{ HostDefaults.EnvironmentKey, "EnvA" }
|
||||
};
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(settings)
|
||||
.Build();
|
||||
|
||||
var overrideSettings = new Dictionary<string, string>
|
||||
{
|
||||
{ HostDefaults.EnvironmentKey, "EnvB" }
|
||||
};
|
||||
|
||||
var overrideConfig = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(overrideSettings)
|
||||
.Build();
|
||||
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureHostConfiguration(configBuilder => configBuilder.AddConfiguration(config))
|
||||
.ConfigureHostConfiguration(configBuilder => configBuilder.AddConfiguration(overrideConfig));
|
||||
|
||||
using (var host = hostBuilder.Build())
|
||||
{
|
||||
Assert.Equal("EnvB", host.Services.GetRequiredService<IHostingEnvironment>().EnvironmentName);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseEnvironmentIsNotOverriden()
|
||||
{
|
||||
var vals = new Dictionary<string, string>
|
||||
{
|
||||
{ "ENV", "Dev" },
|
||||
};
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(vals);
|
||||
var config = builder.Build();
|
||||
|
||||
var expected = "MY_TEST_ENVIRONMENT";
|
||||
|
||||
|
||||
using (var host = new HostBuilder()
|
||||
.ConfigureHostConfiguration(configBuilder => configBuilder.AddConfiguration(config))
|
||||
.UseEnvironment(expected)
|
||||
.Build())
|
||||
{
|
||||
Assert.Equal(expected, host.Services.GetService<IHostingEnvironment>().EnvironmentName);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAndDispose()
|
||||
{
|
||||
using (var host = new HostBuilder()
|
||||
.Build()) { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseBasePathConfiguresBasePath()
|
||||
{
|
||||
var vals = new Dictionary<string, string>
|
||||
{
|
||||
{ "ENV", "Dev" },
|
||||
};
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(vals);
|
||||
var config = builder.Build();
|
||||
|
||||
using (var host = new HostBuilder()
|
||||
.ConfigureHostConfiguration(configBuilder => configBuilder.AddConfiguration(config))
|
||||
.UseContentRoot("/")
|
||||
.Build())
|
||||
{
|
||||
Assert.Equal("/", host.Services.GetService<IHostingEnvironment>().ContentRootPath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostConfigParametersReadCorrectly()
|
||||
{
|
||||
var parameters = new Dictionary<string, string>()
|
||||
{
|
||||
{ "applicationName", "MyProjectReference"},
|
||||
{ "environment", EnvironmentName.Development},
|
||||
{ "contentRoot", Path.GetFullPath(".") }
|
||||
};
|
||||
|
||||
var host = new HostBuilder()
|
||||
.ConfigureHostConfiguration(config =>
|
||||
{
|
||||
config.AddInMemoryCollection(parameters);
|
||||
}).Build();
|
||||
|
||||
var env = host.Services.GetRequiredService<IHostingEnvironment>();
|
||||
|
||||
Assert.Equal("MyProjectReference", env.ApplicationName);
|
||||
Assert.Equal(EnvironmentName.Development, env.EnvironmentName);
|
||||
Assert.Equal(Path.GetFullPath("."), env.ContentRootPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RelativeContentRootIsResolved()
|
||||
{
|
||||
using (var host = new HostBuilder()
|
||||
.UseContentRoot("testroot")
|
||||
.Build())
|
||||
{
|
||||
var basePath = host.Services.GetRequiredService<IHostingEnvironment>().ContentRootPath;
|
||||
Assert.True(Path.IsPathRooted(basePath));
|
||||
Assert.EndsWith(Path.DirectorySeparatorChar + "testroot", basePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultContentRootIsApplicationBasePath()
|
||||
{
|
||||
using (var host = new HostBuilder()
|
||||
.Build())
|
||||
{
|
||||
var appBase = AppContext.BaseDirectory;
|
||||
Assert.Equal(appBase, host.Services.GetService<IHostingEnvironment>().ContentRootPath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultServicesAreAvailable()
|
||||
{
|
||||
using (var host = new HostBuilder()
|
||||
.Build())
|
||||
{
|
||||
Assert.NotNull(host.Services.GetRequiredService<IHostingEnvironment>());
|
||||
Assert.NotNull(host.Services.GetRequiredService<IConfiguration>());
|
||||
Assert.NotNull(host.Services.GetRequiredService<HostBuilderContext>());
|
||||
Assert.NotNull(host.Services.GetRequiredService<IApplicationLifetime>());
|
||||
Assert.NotNull(host.Services.GetRequiredService<ILoggerFactory>());
|
||||
Assert.NotNull(host.Services.GetRequiredService<IOptions<FakeOptions>>());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultCreatesLoggerFactory()
|
||||
{
|
||||
var hostBuilder = new HostBuilder();
|
||||
|
||||
using (var host = hostBuilder.Build())
|
||||
{
|
||||
Assert.NotNull(host.Services.GetService<ILoggerFactory>());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleConfigureLoggingInvokedInOrder()
|
||||
{
|
||||
var callCount = 0; //Verify ordering
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureLogging((hostContext, loggerFactory) =>
|
||||
{
|
||||
Assert.Equal(0, callCount++);
|
||||
})
|
||||
.ConfigureLogging((hostContext, loggerFactory) =>
|
||||
{
|
||||
Assert.Equal(1, callCount++);
|
||||
});
|
||||
|
||||
using (hostBuilder.Build())
|
||||
{
|
||||
Assert.Equal(2, callCount);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostingContextContainsAppConfigurationDuringConfigureServices()
|
||||
{
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureAppConfiguration((context, configBuilder) =>
|
||||
configBuilder.AddInMemoryCollection(
|
||||
new KeyValuePair<string, string>[]
|
||||
{
|
||||
new KeyValuePair<string, string>("key1", "value1")
|
||||
}))
|
||||
.ConfigureServices((context, factory) =>
|
||||
{
|
||||
Assert.Equal("value1", context.Configuration["key1"]);
|
||||
});
|
||||
|
||||
using (hostBuilder.Build()) { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfigureDefaultServiceProvider()
|
||||
{
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureServices((hostContext, s) =>
|
||||
{
|
||||
s.AddTransient<ServiceD>();
|
||||
s.AddScoped<ServiceC>();
|
||||
})
|
||||
.UseServiceProviderFactory(new DefaultServiceProviderFactory(new ServiceProviderOptions()
|
||||
{
|
||||
ValidateScopes = true,
|
||||
}));
|
||||
var host = hostBuilder.Build();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => { host.Services.GetRequiredService<ServiceC>(); });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfigureCustomServiceProvider()
|
||||
{
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureServices((hostContext, s) =>
|
||||
{
|
||||
s.AddTransient<ServiceD>();
|
||||
s.AddScoped<ServiceC>();
|
||||
})
|
||||
.UseServiceProviderFactory(new FakeServiceProviderFactory())
|
||||
.ConfigureContainer<FakeServiceCollection>((hostContext, container) =>
|
||||
{
|
||||
Assert.Null(container.State);
|
||||
container.State = "1";
|
||||
})
|
||||
.ConfigureContainer<FakeServiceCollection>((hostContext, container) =>
|
||||
{
|
||||
Assert.Equal("1", container.State);
|
||||
container.State = "2";
|
||||
});
|
||||
var host = hostBuilder.Build();
|
||||
var fakeServices = host.Services.GetRequiredService<FakeServiceCollection>();
|
||||
Assert.Equal("2", fakeServices.State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CustomContainerTypeMismatchThrows()
|
||||
{
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureServices((hostContext, s) =>
|
||||
{
|
||||
s.AddTransient<ServiceD>();
|
||||
s.AddScoped<ServiceC>();
|
||||
})
|
||||
.UseServiceProviderFactory(new FakeServiceProviderFactory())
|
||||
.ConfigureContainer<IServiceCollection>((hostContext, container) =>
|
||||
{
|
||||
});
|
||||
Assert.Throws<InvalidCastException>(() => hostBuilder.Build());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostingContextContainsAppConfigurationDuringConfigureLogging()
|
||||
{
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureAppConfiguration((context, configBuilder) =>
|
||||
configBuilder.AddInMemoryCollection(
|
||||
new KeyValuePair<string, string>[]
|
||||
{
|
||||
new KeyValuePair<string, string>("key1", "value1")
|
||||
}))
|
||||
.ConfigureLogging((context, factory) =>
|
||||
{
|
||||
Assert.Equal("value1", context.Configuration["key1"]);
|
||||
});
|
||||
|
||||
using (hostBuilder.Build()) { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfigureServices_CanBeCalledMultipleTimes()
|
||||
{
|
||||
var callCount = 0; // Verify ordering
|
||||
var hostBuilder = new HostBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
Assert.Equal(0, callCount++);
|
||||
services.AddTransient<ServiceA>();
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
Assert.Equal(1, callCount++);
|
||||
services.AddTransient<ServiceB>();
|
||||
});
|
||||
|
||||
using (var host = hostBuilder.Build())
|
||||
{
|
||||
Assert.Equal(2, callCount);
|
||||
|
||||
Assert.NotNull(host.Services.GetRequiredService<ServiceA>());
|
||||
Assert.NotNull(host.Services.GetRequiredService<ServiceB>());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_DoesNotAllowBuildingMuiltipleTimes()
|
||||
{
|
||||
var builder = new HostBuilder();
|
||||
using (builder.Build())
|
||||
{
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => builder.Build());
|
||||
Assert.Equal("Build can only be called once.", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetsFullPathToContentRoot()
|
||||
{
|
||||
var host = new HostBuilder()
|
||||
.ConfigureHostConfiguration(config =>
|
||||
{
|
||||
config.AddInMemoryCollection(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HostDefaults.ContentRootKey, Path.GetFullPath("."))
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
var env = host.Services.GetRequiredService<IHostingEnvironment>();
|
||||
|
||||
Assert.Equal(Path.GetFullPath("."), env.ContentRootPath);
|
||||
Assert.IsAssignableFrom<PhysicalFileProvider>(env.ContentRootFileProvider);
|
||||
}
|
||||
|
||||
private class ServiceC
|
||||
{
|
||||
public ServiceC(ServiceD serviceD) { }
|
||||
}
|
||||
|
||||
internal class ServiceD { }
|
||||
|
||||
internal class ServiceA { }
|
||||
|
||||
internal class ServiceB { }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,936 @@
|
|||
// 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.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting.Fakes;
|
||||
using Microsoft.Extensions.Hosting.Tests.Fakes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
public class HostTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task HostInjectsHostingEnvironment()
|
||||
{
|
||||
using (var host = CreateBuilder()
|
||||
.UseEnvironment("WithHostingEnvironment")
|
||||
.Build())
|
||||
{
|
||||
await host.StartAsync();
|
||||
var env = host.Services.GetService<IHostingEnvironment>();
|
||||
Assert.Equal("WithHostingEnvironment", env.EnvironmentName);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCreateApplicationServicesWithAddedServices()
|
||||
{
|
||||
using (var host = CreateBuilder().ConfigureServices((hostContext, services) => services.AddSingleton<IFakeService, FakeService>()).Build())
|
||||
{
|
||||
Assert.NotNull(host.Services.GetRequiredService<IFakeService>());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnvDefaultsToProductionIfNoConfig()
|
||||
{
|
||||
using (var host = CreateBuilder().Build())
|
||||
{
|
||||
var env = host.Services.GetService<IHostingEnvironment>();
|
||||
Assert.Equal(EnvironmentName.Production, env.EnvironmentName);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnvDefaultsToConfigValueIfSpecified()
|
||||
{
|
||||
var vals = new Dictionary<string, string>
|
||||
{
|
||||
{ "Environment", EnvironmentName.Staging }
|
||||
};
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(vals);
|
||||
var config = builder.Build();
|
||||
|
||||
using (var host = CreateBuilder(config).Build())
|
||||
{
|
||||
var env = host.Services.GetService<IHostingEnvironment>();
|
||||
Assert.Equal(EnvironmentName.Staging, env.EnvironmentName);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsEnvironment_Extension_Is_Case_Insensitive()
|
||||
{
|
||||
using (var host = CreateBuilder().Build())
|
||||
{
|
||||
await host.StartAsync();
|
||||
var env = host.Services.GetRequiredService<IHostingEnvironment>();
|
||||
Assert.True(env.IsEnvironment(EnvironmentName.Production));
|
||||
Assert.True(env.IsEnvironment("producTion"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostCanBeStarted()
|
||||
{
|
||||
FakeHostedService service;
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IHostedService, FakeHostedService>();
|
||||
})
|
||||
.Start())
|
||||
{
|
||||
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
Assert.NotNull(host);
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(0, service.DisposeCount);
|
||||
}
|
||||
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(1, service.DisposeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostedServiceStartNotCalledIfHostNotStarted()
|
||||
{
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IHostedService, TestHostedService>();
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
||||
lifetime.StopApplication();
|
||||
|
||||
var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
Assert.False(svc.StartCalled);
|
||||
await host.StopAsync();
|
||||
Assert.False(svc.StopCalled);
|
||||
host.Dispose();
|
||||
Assert.False(svc.StopCalled);
|
||||
Assert.True(svc.DisposeCalled);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostCanBeStoppedWhenNotStarted()
|
||||
{
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IHostedService, TestHostedService>();
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
Assert.False(svc.StartCalled);
|
||||
await host.StopAsync();
|
||||
Assert.False(svc.StopCalled);
|
||||
host.Dispose();
|
||||
Assert.False(svc.StopCalled);
|
||||
Assert.True(svc.DisposeCalled);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AppCrashesOnStartWhenFirstHostedServiceThrows()
|
||||
{
|
||||
bool[] events1 = null;
|
||||
bool[] events2 = null;
|
||||
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
events1 = RegisterCallbacksThatThrow(services);
|
||||
events2 = RegisterCallbacksThatThrow(services);
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => host.StartAsync());
|
||||
Assert.True(events1[0]);
|
||||
Assert.False(events2[0]);
|
||||
host.Dispose();
|
||||
// Stopping
|
||||
Assert.False(events1[1]);
|
||||
Assert.False(events2[1]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartCanBeCancelled()
|
||||
{
|
||||
var serviceStarting = new ManualResetEvent(false);
|
||||
var startCancelled = new ManualResetEvent(false);
|
||||
FakeHostedService service;
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IHostedService>(_ => new FakeHostedService()
|
||||
{
|
||||
StartAction = ct =>
|
||||
{
|
||||
Assert.False(ct.IsCancellationRequested);
|
||||
serviceStarting.Set();
|
||||
Assert.True(startCancelled.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
});
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
var startTask = Task.Run(() => host.StartAsync(cts.Token));
|
||||
Assert.True(serviceStarting.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
cts.Cancel();
|
||||
startCancelled.Set();
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(() => startTask);
|
||||
|
||||
Assert.NotNull(host);
|
||||
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(0, service.DisposeCount);
|
||||
}
|
||||
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(1, service.DisposeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostLifetimeOnStartedDelaysStart()
|
||||
{
|
||||
var serviceStarting = new ManualResetEvent(false);
|
||||
var lifetimeStart = new ManualResetEvent(false);
|
||||
var lifetimeContinue = new ManualResetEvent(false);
|
||||
FakeHostedService service;
|
||||
FakeHostLifetime lifetime;
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IHostedService>(_ => new FakeHostedService()
|
||||
{
|
||||
StartAction = ct =>
|
||||
{
|
||||
serviceStarting.Set();
|
||||
}
|
||||
});
|
||||
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
|
||||
{
|
||||
StartAction = (callback, state) =>
|
||||
{
|
||||
lifetimeStart.Set();
|
||||
Assert.True(lifetimeContinue.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
callback(state);
|
||||
}
|
||||
});
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var startTask = Task.Run(() => host.StartAsync());
|
||||
Assert.True(lifetimeStart.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
Assert.False(serviceStarting.WaitOne(0));
|
||||
|
||||
lifetimeContinue.Set();
|
||||
Assert.True(serviceStarting.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
|
||||
await startTask;
|
||||
|
||||
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(0, service.DisposeCount);
|
||||
|
||||
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
|
||||
Assert.Equal(1, lifetime.StartCount);
|
||||
Assert.Equal(1, lifetime.StoppingCount);
|
||||
Assert.Equal(0, lifetime.StopCount);
|
||||
}
|
||||
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(1, service.DisposeCount);
|
||||
|
||||
Assert.Equal(1, lifetime.StartCount);
|
||||
Assert.Equal(1, lifetime.StoppingCount);
|
||||
Assert.Equal(0, lifetime.StopCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostLifetimeOnStartedCanBeCancelled()
|
||||
{
|
||||
var serviceStarting = new ManualResetEvent(false);
|
||||
var lifetimeStart = new ManualResetEvent(false);
|
||||
var lifetimeContinue = new ManualResetEvent(false);
|
||||
FakeHostedService service;
|
||||
FakeHostLifetime lifetime;
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IHostedService>(_ => new FakeHostedService()
|
||||
{
|
||||
StartAction = ct =>
|
||||
{
|
||||
serviceStarting.Set();
|
||||
}
|
||||
});
|
||||
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
|
||||
{
|
||||
StartAction = (callback, state) =>
|
||||
{
|
||||
lifetimeStart.Set();
|
||||
}
|
||||
});
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
var startTask = Task.Run(() => host.StartAsync(cts.Token));
|
||||
|
||||
Assert.True(lifetimeStart.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
Assert.False(serviceStarting.WaitOne(0));
|
||||
|
||||
cts.Cancel();
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(() => startTask);
|
||||
Assert.False(serviceStarting.WaitOne(0));
|
||||
|
||||
lifetimeContinue.Set();
|
||||
Assert.False(serviceStarting.WaitOne(0));
|
||||
|
||||
Assert.NotNull(host);
|
||||
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
Assert.Equal(0, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(0, service.DisposeCount);
|
||||
|
||||
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
|
||||
Assert.Equal(1, lifetime.StartCount);
|
||||
Assert.Equal(1, lifetime.StoppingCount);
|
||||
Assert.Equal(0, lifetime.StopCount);
|
||||
}
|
||||
|
||||
Assert.Equal(0, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(1, service.DisposeCount);
|
||||
|
||||
Assert.Equal(1, lifetime.StartCount);
|
||||
Assert.Equal(1, lifetime.StoppingCount);
|
||||
Assert.Equal(0, lifetime.StopCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostLifetimeOnStoppingTriggersIApplicationLifetime()
|
||||
{
|
||||
var lifetimeRegistered = new ManualResetEvent(false);
|
||||
Action<object> stoppingAction = null;
|
||||
object stoppingState = null;
|
||||
FakeHostedService service;
|
||||
FakeHostLifetime lifetime;
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IHostedService, FakeHostedService>();
|
||||
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
|
||||
{
|
||||
StartAction = (callback, state) => callback(state),
|
||||
StoppingAction = (callback, state) =>
|
||||
{
|
||||
stoppingAction = callback;
|
||||
stoppingState = state;
|
||||
lifetimeRegistered.Set();
|
||||
}
|
||||
});
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
await host.StartAsync();
|
||||
Assert.True(lifetimeRegistered.WaitOne(0));
|
||||
|
||||
var appLifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
||||
|
||||
stoppingAction(stoppingState);
|
||||
|
||||
Assert.True(appLifetime.ApplicationStopping.WaitHandle.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
|
||||
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(0, service.DisposeCount);
|
||||
|
||||
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
|
||||
Assert.Equal(1, lifetime.StartCount);
|
||||
Assert.Equal(1, lifetime.StoppingCount);
|
||||
Assert.Equal(0, lifetime.StopCount);
|
||||
}
|
||||
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(1, service.DisposeCount);
|
||||
|
||||
Assert.Equal(1, lifetime.StartCount);
|
||||
Assert.Equal(1, lifetime.StoppingCount);
|
||||
Assert.Equal(0, lifetime.StopCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostStopAsyncCallsHostLifetimeStopAsync()
|
||||
{
|
||||
FakeHostedService service;
|
||||
FakeHostLifetime lifetime;
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IHostedService, FakeHostedService>();
|
||||
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
|
||||
{
|
||||
StartAction = (callback, state) => callback(state),
|
||||
});
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
await host.StartAsync();
|
||||
|
||||
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(0, service.DisposeCount);
|
||||
|
||||
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
|
||||
Assert.Equal(1, lifetime.StartCount);
|
||||
Assert.Equal(1, lifetime.StoppingCount);
|
||||
Assert.Equal(0, lifetime.StopCount);
|
||||
|
||||
await host.StopAsync();
|
||||
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(1, service.StopCount);
|
||||
Assert.Equal(0, service.DisposeCount);
|
||||
|
||||
Assert.Equal(1, lifetime.StartCount);
|
||||
Assert.Equal(1, lifetime.StoppingCount);
|
||||
Assert.Equal(1, lifetime.StopCount);
|
||||
}
|
||||
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(1, service.StopCount);
|
||||
Assert.Equal(1, service.DisposeCount);
|
||||
|
||||
Assert.Equal(1, lifetime.StartCount);
|
||||
Assert.Equal(1, lifetime.StoppingCount);
|
||||
Assert.Equal(1, lifetime.StopCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostShutsDownWhenTokenTriggers()
|
||||
{
|
||||
FakeHostedService service;
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) => services.AddSingleton<IHostedService, FakeHostedService>())
|
||||
.Build())
|
||||
{
|
||||
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
||||
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
var runInBackground = host.RunAsync(cts.Token);
|
||||
|
||||
// Wait on the host to be started
|
||||
lifetime.ApplicationStarted.WaitHandle.WaitOne();
|
||||
|
||||
Assert.Equal(1, service.StartCount);
|
||||
Assert.Equal(0, service.StopCount);
|
||||
Assert.Equal(0, service.DisposeCount);
|
||||
|
||||
cts.Cancel();
|
||||
|
||||
// Wait on the host to shutdown
|
||||
lifetime.ApplicationStopped.WaitHandle.WaitOne();
|
||||
|
||||
// Wait for RunAsync to finish to guarantee Disposal of Host
|
||||
await runInBackground;
|
||||
|
||||
Assert.Equal(1, service.StopCount);
|
||||
Assert.Equal(1, service.DisposeCount);
|
||||
}
|
||||
Assert.Equal(1, service.DisposeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostStopAsyncCanBeCancelledEarly()
|
||||
{
|
||||
var service = new Mock<IHostedService>();
|
||||
service.Setup(s => s.StopAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns<CancellationToken>(token =>
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
token.WaitHandle.WaitOne();
|
||||
});
|
||||
});
|
||||
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton(service.Object);
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
await host.StartAsync();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
var task = host.StopAsync(cts.Token);
|
||||
cts.Cancel();
|
||||
|
||||
Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(8))));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HostApplicationLifetimeEventsOrderedCorrectlyDuringShutdown()
|
||||
{
|
||||
using (var host = CreateBuilder()
|
||||
.Build())
|
||||
{
|
||||
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
||||
var applicationStartedEvent = new ManualResetEventSlim(false);
|
||||
var applicationStoppingEvent = new ManualResetEventSlim(false);
|
||||
var applicationStoppedEvent = new ManualResetEventSlim(false);
|
||||
var applicationStartedCompletedBeforeApplicationStopping = false;
|
||||
var applicationStoppingCompletedBeforeApplicationStopped = false;
|
||||
var applicationStoppedCompletedBeforeRunCompleted = false;
|
||||
|
||||
lifetime.ApplicationStarted.Register(() =>
|
||||
{
|
||||
applicationStartedEvent.Set();
|
||||
});
|
||||
|
||||
lifetime.ApplicationStopping.Register(() =>
|
||||
{
|
||||
// Check whether the applicationStartedEvent has been set
|
||||
applicationStartedCompletedBeforeApplicationStopping = applicationStartedEvent.IsSet;
|
||||
|
||||
// Simulate work.
|
||||
Thread.Sleep(1000);
|
||||
|
||||
applicationStoppingEvent.Set();
|
||||
});
|
||||
|
||||
lifetime.ApplicationStopped.Register(() =>
|
||||
{
|
||||
// Check whether the applicationStoppingEvent has been set
|
||||
applicationStoppingCompletedBeforeApplicationStopped = applicationStoppingEvent.IsSet;
|
||||
applicationStoppedEvent.Set();
|
||||
});
|
||||
|
||||
var runHostAndVerifyApplicationStopped = Task.Run(async () =>
|
||||
{
|
||||
await host.RunAsync();
|
||||
// Check whether the applicationStoppingEvent has been set
|
||||
applicationStoppedCompletedBeforeRunCompleted = applicationStoppedEvent.IsSet;
|
||||
});
|
||||
|
||||
// Wait until application has started to shut down the host
|
||||
Assert.True(applicationStartedEvent.Wait(5000));
|
||||
|
||||
// Trigger host shutdown on a separate thread
|
||||
Task.Run(() => lifetime.StopApplication());
|
||||
|
||||
// Wait for all events and host.Run() to complete
|
||||
Assert.True(runHostAndVerifyApplicationStopped.Wait(5000));
|
||||
|
||||
// Verify Ordering
|
||||
Assert.True(applicationStartedCompletedBeforeApplicationStopping);
|
||||
Assert.True(applicationStoppingCompletedBeforeApplicationStopped);
|
||||
Assert.True(applicationStoppedCompletedBeforeRunCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostDisposesServiceProvider()
|
||||
{
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, s) =>
|
||||
{
|
||||
s.AddTransient<IFakeService, FakeService>();
|
||||
s.AddSingleton<IFakeSingletonService, FakeService>();
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
await host.StartAsync();
|
||||
|
||||
var singleton = (FakeService)host.Services.GetService<IFakeSingletonService>();
|
||||
var transient = (FakeService)host.Services.GetService<IFakeService>();
|
||||
|
||||
Assert.False(singleton.Disposed);
|
||||
Assert.False(transient.Disposed);
|
||||
|
||||
await host.StopAsync();
|
||||
|
||||
Assert.False(singleton.Disposed);
|
||||
Assert.False(transient.Disposed);
|
||||
|
||||
host.Dispose();
|
||||
|
||||
Assert.True(singleton.Disposed);
|
||||
Assert.True(transient.Disposed);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostNotifiesApplicationStarted()
|
||||
{
|
||||
using (var host = CreateBuilder()
|
||||
.Build())
|
||||
{
|
||||
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
|
||||
|
||||
Assert.False(applicationLifetime.ApplicationStarted.IsCancellationRequested);
|
||||
|
||||
await host.StartAsync();
|
||||
Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostNotifiesAllIApplicationLifetimeCallbacksEvenIfTheyThrow()
|
||||
{
|
||||
using (var host = CreateBuilder()
|
||||
.Build())
|
||||
{
|
||||
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
|
||||
|
||||
var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted);
|
||||
var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping);
|
||||
var stopped = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopped);
|
||||
|
||||
await host.StartAsync();
|
||||
Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested);
|
||||
Assert.True(started.All(s => s));
|
||||
await host.StopAsync();
|
||||
Assert.True(stopping.All(s => s));
|
||||
host.Dispose();
|
||||
Assert.True(stopped.All(s => s));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostStopApplicationDoesNotFireStopOnHostedService()
|
||||
{
|
||||
var stoppingCalls = 0;
|
||||
var disposingCalls = 0;
|
||||
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
Action started = () =>
|
||||
{
|
||||
};
|
||||
|
||||
Action stopping = () =>
|
||||
{
|
||||
stoppingCalls++;
|
||||
};
|
||||
|
||||
Action disposing = () =>
|
||||
{
|
||||
disposingCalls++;
|
||||
};
|
||||
|
||||
services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
||||
lifetime.StopApplication();
|
||||
|
||||
await host.StartAsync();
|
||||
|
||||
Assert.Equal(0, stoppingCalls);
|
||||
Assert.Equal(0, disposingCalls);
|
||||
}
|
||||
Assert.Equal(0, stoppingCalls);
|
||||
Assert.Equal(1, disposingCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostedServiceCanInjectApplicationLifetime()
|
||||
{
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddSingleton<IHostedService, TestHostedService>();
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
||||
lifetime.StopApplication();
|
||||
|
||||
await host.StartAsync();
|
||||
var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
|
||||
Assert.True(svc.StartCalled);
|
||||
|
||||
await host.StopAsync();
|
||||
Assert.True(svc.StopCalled);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostStopApplicationFiresStopOnHostedService()
|
||||
{
|
||||
var stoppingCalls = 0;
|
||||
var startedCalls = 0;
|
||||
var disposingCalls = 0;
|
||||
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
Action started = () =>
|
||||
{
|
||||
startedCalls++;
|
||||
};
|
||||
|
||||
Action stopping = () =>
|
||||
{
|
||||
stoppingCalls++;
|
||||
};
|
||||
|
||||
Action disposing = () =>
|
||||
{
|
||||
disposingCalls++;
|
||||
};
|
||||
|
||||
services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
||||
|
||||
Assert.Equal(0, startedCalls);
|
||||
|
||||
await host.StartAsync();
|
||||
Assert.Equal(1, startedCalls);
|
||||
Assert.Equal(0, stoppingCalls);
|
||||
Assert.Equal(0, disposingCalls);
|
||||
|
||||
await host.StopAsync();
|
||||
|
||||
Assert.Equal(1, startedCalls);
|
||||
Assert.Equal(1, stoppingCalls);
|
||||
Assert.Equal(0, disposingCalls);
|
||||
|
||||
host.Dispose();
|
||||
|
||||
Assert.Equal(1, startedCalls);
|
||||
Assert.Equal(1, stoppingCalls);
|
||||
Assert.Equal(1, disposingCalls);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostDisposeApplicationDoesNotFireStopOnHostedService()
|
||||
{
|
||||
var stoppingCalls = 0;
|
||||
var startedCalls = 0;
|
||||
var disposingCalls = 0;
|
||||
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
Action started = () =>
|
||||
{
|
||||
startedCalls++;
|
||||
};
|
||||
|
||||
Action stopping = () =>
|
||||
{
|
||||
stoppingCalls++;
|
||||
};
|
||||
|
||||
Action disposing = () =>
|
||||
{
|
||||
disposingCalls++;
|
||||
};
|
||||
|
||||
services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
||||
|
||||
Assert.Equal(0, startedCalls);
|
||||
await host.StartAsync();
|
||||
Assert.Equal(1, startedCalls);
|
||||
Assert.Equal(0, stoppingCalls);
|
||||
Assert.Equal(0, disposingCalls);
|
||||
host.Dispose();
|
||||
|
||||
Assert.Equal(0, stoppingCalls);
|
||||
Assert.Equal(1, disposingCalls);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HostDoesNotNotifyIApplicationLifetimeCallbacksIfIHostedServicesThrow()
|
||||
{
|
||||
bool[] events1 = null;
|
||||
bool[] events2 = null;
|
||||
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
events1 = RegisterCallbacksThatThrow(services);
|
||||
events2 = RegisterCallbacksThatThrow(services);
|
||||
})
|
||||
.Build())
|
||||
{
|
||||
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
|
||||
|
||||
var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted);
|
||||
var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => host.StartAsync());
|
||||
Assert.True(events1[0]);
|
||||
Assert.False(events2[0]);
|
||||
Assert.False(started.All(s => s));
|
||||
host.Dispose();
|
||||
Assert.False(events1[1]);
|
||||
Assert.False(events2[1]);
|
||||
Assert.False(stopping.All(s => s));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Host_InvokesConfigureServicesMethodsOnlyOnce()
|
||||
{
|
||||
int configureServicesCount = 0;
|
||||
using (var host = CreateBuilder()
|
||||
.ConfigureServices((hostContext, services) => configureServicesCount++)
|
||||
.Build())
|
||||
{
|
||||
Assert.Equal(1, configureServicesCount);
|
||||
await host.StartAsync();
|
||||
var services = host.Services;
|
||||
var services2 = host.Services;
|
||||
Assert.Equal(1, configureServicesCount);
|
||||
}
|
||||
}
|
||||
|
||||
private IHostBuilder CreateBuilder(IConfiguration config = null)
|
||||
{
|
||||
return new HostBuilder().ConfigureHostConfiguration(builder => builder.AddConfiguration(config ?? new ConfigurationBuilder().Build()));
|
||||
}
|
||||
|
||||
private static bool[] RegisterCallbacksThatThrow(IServiceCollection services)
|
||||
{
|
||||
bool[] events = new bool[2];
|
||||
|
||||
Action started = () =>
|
||||
{
|
||||
events[0] = true;
|
||||
throw new InvalidOperationException();
|
||||
};
|
||||
|
||||
Action stopping = () =>
|
||||
{
|
||||
events[1] = true;
|
||||
throw new InvalidOperationException();
|
||||
};
|
||||
|
||||
services.AddSingleton<IHostedService>(new DelegateHostedService(started, stopping, () => { }));
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
private static bool[] RegisterCallbacksThatThrow(CancellationToken token)
|
||||
{
|
||||
var signals = new bool[3];
|
||||
for (int i = 0; i < signals.Length; i++)
|
||||
{
|
||||
token.Register(state =>
|
||||
{
|
||||
signals[(int)state] = true;
|
||||
throw new InvalidOperationException();
|
||||
}, i);
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
private class TestHostedService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly IApplicationLifetime _lifetime;
|
||||
|
||||
public TestHostedService(IApplicationLifetime lifetime)
|
||||
{
|
||||
_lifetime = lifetime;
|
||||
}
|
||||
|
||||
public bool StartCalled { get; set; }
|
||||
public bool StopCalled { get; set; }
|
||||
public bool DisposeCalled { get; set; }
|
||||
|
||||
public Task StartAsync(CancellationToken token)
|
||||
{
|
||||
StartCalled = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken token)
|
||||
{
|
||||
StopCalled = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class DelegateHostedService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly Action _started;
|
||||
private readonly Action _stopping;
|
||||
private readonly Action _disposing;
|
||||
|
||||
public DelegateHostedService(Action started, Action stopping, Action disposing)
|
||||
{
|
||||
_started = started;
|
||||
_stopping = stopping;
|
||||
_disposing = disposing;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken token)
|
||||
{
|
||||
_started();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task StopAsync(CancellationToken token)
|
||||
{
|
||||
_stopping();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose() => _disposing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="testroot\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Extensions.Hosting\Microsoft.Extensions.Hosting.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Testing" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1 @@
|
|||
This file exists to preserve the parent directory in the GIT repo. Git does not preserve empty directories.
|
||||
Loading…
Reference in New Issue