Skip to content

Instantly share code, notes, and snippets.

@Antaris
Created June 5, 2018 14:37
Show Gist options
  • Save Antaris/760a9be19a77f078fb36ecd053351fd5 to your computer and use it in GitHub Desktop.
Save Antaris/760a9be19a77f078fb36ecd053351fd5 to your computer and use it in GitHub Desktop.
// 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);
}
}
}
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();
}
}
}
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