Skip to content

Instantly share code, notes, and snippets.

@ShawnTheBeachy
Last active March 26, 2024 08:30
Show Gist options
  • Save ShawnTheBeachy/0602195874764b7144389a336e3f7169 to your computer and use it in GitHub Desktop.
Save ShawnTheBeachy/0602195874764b7144389a336e3f7169 to your computer and use it in GitHub Desktop.
WorkerServiceFactory implementation
using Microsoft.Extensions.Hosting;
public sealed class TestWorkerService : IDisposable
{
internal readonly IHost Host;
internal TestWorkerService(
HostApplicationBuilder builder,
Action<IHostApplicationBuilder> configure
)
{
configure(builder);
Host = builder.Build();
Host.StartAsync().GetAwaiter().GetResult();
}
internal TestWorkerService(IHostBuilder builder, Action<IHostBuilder> configure)
{
configure(builder);
Host = builder.Build();
Host.StartAsync().GetAwaiter().GetResult();
}
public void Dispose() => Host.Dispose();
}
using System.Reflection;
using Microsoft.Extensions.Hosting;
internal sealed class WorkerHostFactoryLocator
{
private readonly Type _entryPoint;
private readonly string _environment;
private readonly string? _methodName;
private const BindingFlags FactoryMethodFlags =
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
public WorkerHostFactoryLocator(Type entryPoint, string? methodName, string environment)
{
_entryPoint = entryPoint;
_environment = environment;
_methodName = methodName;
if (TryGetFactoryMethodByName(out var factoryMethod))
CreateBuilder(factoryMethod);
else if (TryGetHostApplicationBuilderFactory(out factoryMethod))
CreateHostApplicationBuilder(factoryMethod);
else if (TryGetIHostBuilderFactory(out factoryMethod))
CreateHostBuilder(factoryMethod);
else
ThrowNoFactoryFound();
}
private void CreateBuilder(MethodInfo factoryMethod)
{
if (factoryMethod.ReturnType == typeof(IHostBuilder))
CreateHostBuilder(factoryMethod);
else if (factoryMethod.ReturnType == typeof(HostApplicationBuilder))
CreateHostApplicationBuilder(factoryMethod);
else
throw new Exception(
$"Method '{factoryMethod.Name}' does not return a valid host builder."
);
}
private void CreateHostApplicationBuilder(MethodInfo factoryMethod) =>
HostApplicationBuilder = (HostApplicationBuilder)
factoryMethod.Invoke(
null,
new object?[]
{
new HostApplicationBuilderSettings
{
ApplicationName = _entryPoint.Assembly.GetName().Name,
EnvironmentName = _environment
}
}
)!;
private void CreateHostBuilder(MethodInfo factoryMethod) =>
HostBuilder = (IHostBuilder)
factoryMethod.Invoke(null, new object?[] { Array.Empty<string>() })!;
private void ThrowNoFactoryFound() =>
throw new Exception(
$"Unable to find a suitable factory method on type `{_entryPoint.FullName}`."
);
private bool TryGetFactoryMethodByName(out MethodInfo factoryMethod)
{
if (string.IsNullOrWhiteSpace(_methodName))
{
factoryMethod = default!;
return false;
}
var methodByName = _entryPoint.GetMethod(_methodName, FactoryMethodFlags);
factoryMethod =
methodByName
?? throw new Exception(
$"Expected to find method '{_methodName}' on type `{_entryPoint.FullName}`."
);
return true;
}
private bool TryGetHostApplicationBuilderFactory(out MethodInfo factoryMethod)
{
factoryMethod = default!;
var hostBuilderFactory = _entryPoint
.GetMethods(FactoryMethodFlags)
.SingleOrDefault(
method =>
method.ReturnType == typeof(HostApplicationBuilder)
|| method.ReturnType == typeof(IHostApplicationBuilder)
);
if (hostBuilderFactory is null)
return false;
factoryMethod = hostBuilderFactory;
return true;
}
private bool TryGetIHostBuilderFactory(out MethodInfo factoryMethod)
{
factoryMethod = default!;
var hostBuilderFactory = _entryPoint
.GetMethods(FactoryMethodFlags)
.SingleOrDefault(method => method.ReturnType == typeof(IHostBuilder));
if (hostBuilderFactory is null)
return false;
factoryMethod = hostBuilderFactory;
return true;
}
public HostApplicationBuilder? HostApplicationBuilder { get; private set; }
public IHostBuilder? HostBuilder { get; private set; }
}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public abstract class WorkerServiceFactory<T> : IDisposable
{
private readonly HostApplicationBuilder? _hostAppBuilder;
private readonly IHostBuilder? _hostBuilder;
protected WorkerServiceFactory()
{
var entryPoint = typeof(T).Assembly.EntryPoint!.DeclaringType!;
var locator = new WorkerHostFactoryLocator(
entryPoint,
CreateBuilderMethodName,
Environment
);
_hostAppBuilder = locator.HostApplicationBuilder;
_hostBuilder = locator.HostBuilder;
}
protected virtual void ConfigureApplicationHost(IHostApplicationBuilder builder) { }
protected virtual void ConfigureHost(IHostBuilder builder) { }
public void Dispose()
{
_workerService?.Dispose();
ServiceScope.Dispose();
GC.SuppressFinalize(this);
}
public TService GetService<TService>()
where TService : class => ServiceScope.ServiceProvider.GetRequiredService<TService>();
public void Start() => _ = WorkerService;
private bool TryHostAppBuilder(out TestWorkerService service)
{
service = default!;
if (_hostAppBuilder is null)
return false;
service = new TestWorkerService(_hostAppBuilder, ConfigureApplicationHost);
return true;
}
private bool TryHostBuilder(out TestWorkerService service)
{
service = default!;
if (_hostBuilder is null)
return false;
service = new TestWorkerService(_hostBuilder, ConfigureHost);
return true;
}
protected virtual string? CreateBuilderMethodName { get; }
protected virtual string Environment { get; } = Environments.Development;
private IServiceScope? _serviceScope;
private IServiceScope ServiceScope => _serviceScope ??= Services.CreateScope();
public IServiceProvider Services => WorkerService.Host.Services;
private TestWorkerService? _workerService;
public TestWorkerService WorkerService =>
_workerService ??=
TryHostAppBuilder(out _workerService) || TryHostBuilder(out _workerService)
? _workerService
: throw new Exception("No valid host has been provided.");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment