Created
June 5, 2018 14:37
-
-
Save Antaris/760a9be19a77f078fb36ecd053351fd5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) Fresh Egg Limited. All rights reserved | |
namespace Fx.Messaging.Distributed.Host | |
{ | |
using System.Threading.Tasks; | |
using Microsoft.EntityFrameworkCore; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Logging; | |
using Fx.DistributedServices; | |
using Fx.Win32Services; | |
class Program | |
{ | |
private const string ServiceName = "Fx.Messaging.Distributed.Host"; | |
private const string ServiceDisplayName = "Fx Messaging - Distributed Host"; | |
private const string ServiceDescription = "Fx Messaging - Distributed Host"; | |
public static void Main(string[] args) | |
{ | |
// MA - Create the service host - this encapsulates many distributed services | |
var serviceHost = new ServiceHostBuilder() | |
.WithService<DistributedMessagingConsumerService>() | |
.ConfigureServices((services, host) => | |
{ | |
services.AddEntityFrameworkSqlServer(); | |
host.ConfigureEntityFramework((csp, g, options) | |
=> options.UseSqlServer(csp.GetForLogicalGroup(g).ToConnectionString())); | |
services.AddDistributedService<DistributedMessagingConsumerService>(); | |
}) | |
.ConfigureLogging(logging => | |
{ | |
logging.AddConsole(); | |
}) | |
.Build(); | |
// MA - Descriibe the service | |
var descriptor = new Win32ServiceDescriptor( | |
ServiceName, | |
ServiceDisplayName, | |
ServiceDescription, | |
onStart: (interactive, arguments, onStop) => | |
{ | |
if (interactive) | |
{ | |
serviceHost.Run(); | |
} | |
else | |
{ | |
Task.Factory.StartNew(async () => await serviceHost.StartAsync()); | |
} | |
}, | |
onStop: () => | |
{ | |
serviceHost.Stop(); | |
}); | |
// MA - Run the service | |
Win32.RunService(args, descriptor); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Fx.DistributedServices | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Logging; | |
/// <summary> | |
/// Represents a service host | |
/// </summary> | |
/// <typeparam name="TService">The service type</typeparam> | |
public class ServiceHost : DisposableBase | |
{ | |
private readonly IList<Type> _serviceTypes; | |
private readonly IServiceCollection _applicationServices; | |
private readonly IServiceProvider _hostingServiceProvider; | |
private readonly IConfiguration _configuration; | |
private readonly ILogger<ServiceHost> _logger; | |
private readonly Action<IServiceCollection, Host> _servicesBuilder; | |
private IServiceProvider _applicationServiceProvider; | |
private bool _stopped; | |
/// <summary> | |
/// Initialises a new instance of <see cref="ServiceHost"/> | |
/// </summary> | |
/// <param name="serviceTypes">The service types</param> | |
/// <param name="applicationServices">The set of application services</param> | |
/// <param name="hostingServiceProvider">The hosting service provider</param> | |
/// <param name="configuration">The configuration</param> | |
/// <param name="servicesBuilder">The services builder</param> | |
public ServiceHost( | |
IList<Type> serviceTypes, | |
IServiceCollection applicationServices, | |
IServiceProvider hostingServiceProvider, | |
IConfiguration configuration, | |
ILogger<ServiceHost> logger, | |
Action<IServiceCollection, Host> servicesBuilder = null) | |
{ | |
_serviceTypes = Ensure.ArgumentNotNull(serviceTypes, nameof(serviceTypes)); | |
_applicationServices = Ensure.ArgumentNotNull(applicationServices, nameof(applicationServices)); | |
_hostingServiceProvider = Ensure.ArgumentNotNull(hostingServiceProvider, nameof(hostingServiceProvider)); | |
_configuration = Ensure.ArgumentNotNull(configuration, nameof(configuration)); | |
_logger = Ensure.ArgumentNotNull(logger, nameof(logger)); | |
_servicesBuilder = servicesBuilder; | |
} | |
/// <summary> | |
/// Gets the set of services | |
/// </summary> | |
public ServiceBase[] Services { get; private set; } | |
/// <summary> | |
/// Gets a reset event used for controlling when the service should end | |
/// </summary> | |
public ManualResetEventSlim ResetEvent => new ManualResetEventSlim(false); | |
/// <summary> | |
/// Gets the application service provider | |
/// </summary> | |
public IServiceProvider ApplicationServices | |
{ | |
get | |
{ | |
EnsureApplicationServices(); | |
return _applicationServiceProvider; | |
} | |
} | |
/// <summary> | |
/// Initialises the host | |
/// </summary> | |
public void Initialise() | |
{ | |
if (Services == null) | |
{ | |
Services = BuildServices(); | |
} | |
} | |
/// <summary> | |
/// Starts the service host | |
/// </summary> | |
public void Start() | |
=> StartAsync().GetAwaiter().GetResult(); | |
/// <summary> | |
/// Starts the service host | |
/// </summary> | |
/// <param name="cancellationToken">[Optional] The cancellation token</param> | |
/// <returns>The task instance</returns> | |
public virtual async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
if (Services != null) | |
{ | |
var tasks = Services.Select(s => Task.Factory.StartNew(() => | |
{ | |
_logger.LogInformation($"Starting service: {s.GetType().FullName}"); | |
s.Start(); | |
_logger.LogInformation($"Started service: {s.GetType().FullName}"); | |
})).ToArray(); | |
await Task.WhenAll(tasks).ConfigureAwait(false); | |
} | |
} | |
/// <summary> | |
/// Stops the service host | |
/// </summary> | |
/// <param name="cancellationToken">[Optional] The cancellation token</param> | |
/// <returns>The task instance</returns> | |
public void Stop(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
if (_stopped) | |
{ | |
return; | |
} | |
_stopped = true; | |
var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; | |
if (!cancellationToken.CanBeCanceled) | |
{ | |
cancellationToken = timeoutToken; | |
} | |
else | |
{ | |
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken).Token; | |
} | |
if (Services != null) | |
{ | |
foreach (var service in Services) | |
{ | |
_logger.LogInformation($"Stopping service: {service.GetType().FullName}"); | |
service.Stop(); | |
_logger.LogInformation($"Stopped service: {service.GetType().FullName}"); | |
} | |
} | |
} | |
/// <inheritdoc /> | |
protected override void ImplicitDispose() | |
{ | |
if (!_stopped) | |
{ | |
try | |
{ | |
Stop(); | |
} | |
catch { } | |
} | |
(_applicationServiceProvider as IDisposable)?.Dispose(); | |
(_hostingServiceProvider as IDisposable)?.Dispose(); | |
} | |
/// <summary> | |
/// Ensures the application services have been created | |
/// </summary> | |
private void EnsureApplicationServices() | |
{ | |
if (_applicationServiceProvider == null) | |
{ | |
var hostingEnvironment = _hostingServiceProvider.GetRequiredService<IHostingEnvironment>(); | |
var host = new Host(hostingEnvironment, cb => { }); | |
host.ConfigureServices(_applicationServices); | |
_servicesBuilder?.DynamicInvoke(_applicationServices, host); | |
_applicationServiceProvider = _applicationServices.BuildServiceProvider(); | |
host.SetRootServiceProvider(_applicationServiceProvider); | |
} | |
} | |
/// <summary> | |
/// Builds the application service | |
/// </summary> | |
private ServiceBase[] BuildServices() | |
{ | |
var services = new List<ServiceBase>(); | |
foreach (var serviceType in _serviceTypes) | |
{ | |
var service = ApplicationServices.GetRequiredService(serviceType) as ServiceBase; | |
if (service != null) | |
{ | |
_logger.LogInformation($"Created service: {serviceType.FullName}"); | |
services.Add(service); | |
} | |
else | |
{ | |
_logger.LogWarning($"Unable to create service: {serviceType.FullName}"); | |
} | |
} | |
return services.ToArray(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Fx.DistributedServices | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Reflection; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.FileProviders; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.Extensions.ObjectPool; | |
/// <summary> | |
/// Creates instances of <see cref="ServiceHost"/> | |
/// </summary> | |
/// <typeparam name="TService">The service type</typeparam> | |
public class ServiceHostBuilder | |
{ | |
private readonly IHostingEnvironment _hostingEnvironment; | |
private readonly List<Action<IServiceCollection, Host>> _configureServicesDelegates; | |
private readonly List<Action<ILoggingBuilder>> _configureLoggingDelegates; | |
private readonly IConfiguration _configuration; | |
private readonly List<Type> _services = new List<Type>(); | |
private bool _hostBuilt; | |
public ServiceHostBuilder() | |
{ | |
_hostingEnvironment = new ServiceHostingEnvironment(); | |
_configureServicesDelegates = new List<Action<IServiceCollection, Host>>(); | |
_configureLoggingDelegates = new List<Action<ILoggingBuilder>>(); | |
_configuration = new ConfigurationBuilder() | |
.AddEnvironmentVariables(prefix: "FX_") | |
.Build(); | |
} | |
public ServiceHost Build() | |
{ | |
if (_hostBuilt) | |
{ | |
throw new InvalidOperationException("Only a single instance of a service host can be created"); | |
} | |
_hostBuilt = true; | |
var hostingServices = BuildCommonServices(); | |
var applicationServices = CloneServices(hostingServices); | |
var hostingServiceProvider = hostingServices.BuildServiceProvider(); | |
var host = new ServiceHost( | |
_services, | |
applicationServices, | |
hostingServiceProvider, | |
_configuration, | |
hostingServiceProvider.GetRequiredService<ILogger<ServiceHost>>(), | |
(sc, h) => | |
{ | |
foreach (var @delegate in _configureServicesDelegates) | |
{ | |
@delegate(sc, h); | |
} | |
}); | |
host.Initialise(); | |
return host; | |
} | |
/// <summary> | |
/// Configures services for the application | |
/// </summary> | |
/// <param name="configureServices">The configure services delegate</param> | |
/// <returns>The service builder</returns> | |
public ServiceHostBuilder ConfigureServices(Action<IServiceCollection, Host> configureServices) | |
{ | |
Ensure.ArgumentNotNull(configureServices, nameof(configureServices)); | |
_configureServicesDelegates.Add(configureServices); | |
return this; | |
} | |
/// <summary> | |
/// Configures logging for the application | |
/// </summary> | |
/// <param name="configureLogging">The configure logging delegate</param> | |
/// <returns>The service builder</returns> | |
public ServiceHostBuilder ConfigureLogging(Action<ILoggingBuilder> configureLogging) | |
{ | |
Ensure.ArgumentNotNull(configureLogging, nameof(configureLogging)); | |
_configureLoggingDelegates.Add(configureLogging); | |
return this; | |
} | |
/// <summary> | |
/// Gets the setting with the given key | |
/// </summary> | |
/// <param name="key">The setting key</param> | |
/// <returns>The setting value</returns> | |
public string GetSetting(string key) | |
=> _configuration[key]; | |
/// <summary> | |
/// Add or replace a setting in the configuration | |
/// </summary> | |
/// <param name="key">The setting key</param> | |
/// <param name="value">The setting value</param> | |
/// <returns>The service host builder</returns> | |
public ServiceHostBuilder UseSetting(string key, string value) | |
{ | |
_configuration[key] = value; | |
return this; | |
} | |
/// <summary> | |
/// Adds a distributed service to the service host | |
/// </summary> | |
/// <typeparam name="TService">The service type</typeparam> | |
/// <returns>The service host builder</returns> | |
public ServiceHostBuilder WithService<TService>() | |
where TService : ServiceBase | |
{ | |
_services.Add(typeof(TService)); | |
return this; | |
} | |
private IServiceCollection BuildCommonServices() | |
{ | |
_hostingEnvironment.EnvironmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"; | |
_hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name; | |
_hostingEnvironment.ContentRootPath = AppContext.BaseDirectory; | |
_hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath); | |
_hostingEnvironment.WebRootPath = Path.Combine(_hostingEnvironment.ContentRootPath, "wwwroot"); | |
if (!Directory.Exists(_hostingEnvironment.WebRootPath)) | |
{ | |
Directory.CreateDirectory(_hostingEnvironment.WebRootPath); | |
} | |
_hostingEnvironment.WebRootFileProvider = new PhysicalFileProvider(_hostingEnvironment.WebRootPath); | |
var services = new ServiceCollection(); | |
services.AddSingleton(_hostingEnvironment); | |
var builder = new ConfigurationBuilder() | |
.SetBasePath(_hostingEnvironment.ContentRootPath) | |
.AddInMemoryCollection(_configuration.AsEnumerable()); | |
var configuration = builder.Build(); | |
services.AddSingleton<IConfiguration>(configuration); | |
services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>(); | |
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); | |
services.AddLogging(loggingBuilder => | |
{ | |
foreach (var loggingBuilderDelegate in _configureLoggingDelegates) | |
{ | |
loggingBuilderDelegate(loggingBuilder); | |
} | |
}); | |
foreach (var type in _services) | |
{ | |
services.AddSingleton(type); | |
} | |
return services; | |
} | |
private IServiceCollection CloneServices(IServiceCollection services) | |
{ | |
IServiceCollection clone = new ServiceCollection(); | |
foreach (var service in services) | |
{ | |
clone.Add(service); | |
} | |
return clone; | |
} | |
private class ServiceHostingEnvironment : IHostingEnvironment | |
{ | |
public string EnvironmentName { get; set; } | |
public string ApplicationName { get; set; } | |
public string WebRootPath { get; set; } | |
public IFileProvider WebRootFileProvider { get; set; } | |
public string ContentRootPath { get; set; } | |
public IFileProvider ContentRootFileProvider { get; set; } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment