Last active
March 26, 2024 08:30
-
-
Save ShawnTheBeachy/0602195874764b7144389a336e3f7169 to your computer and use it in GitHub Desktop.
WorkerServiceFactory implementation
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
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(); | |
} |
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
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; } | |
} |
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
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