I'm trying to create a service which will run as a Windows service as described here. My problem is that the example Web Host Service constructor only takes an IWebHost
parameter. My service needs a constructor more like this:
public static class HostExtensions
{
public static void RunAsMyService(this IWebHost host)
{
var webHostService =
new MyService(host, loggerFactory, myClientFactory, schedulerProvider);
ServiceBase.Run(webHostService);
}
}
My Startup.cs
file looks similar to this:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.AddInMemoryCollection();
this.Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
this.container.RegisterSingleton<IConfiguration>(this.Configuration);
services.AddSingleton<IControllerActivator>(
new SimpleInjectorControllerActivator(container));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseSimpleInjectorAspNetRequestScoping(this.container);
this.container.Options.DefaultScopedLifestyle = new AspNetRequestLifestyle();
this.InitializeContainer(app, loggerFactory);
this.container.Verify();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
}
private void InitializeContainer(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
container.Register(() => loggerFactory, Lifestyle.Singleton);
container.Register<IMyClientFactory>(() => new MyClientFactory());
container.Register<ISchedulerProvider>(() => new SchedulerProvider());
}
Obviously I'm using Simple Injector as a DI container. It's registered with the IServiceCollection
as detailed in their documentation.
My question is how do I access the framework's container (the IServicesCollection) in the HostExtensions class so that I can inject the necessary dependencies into MyService
? For MVC controllers that's all just handled under the covers, but I don't know of any documentation detailing how to access it where needed elsewhere.
You just have to make a few minor tweeks to your code to get this working.
Container
a public static
field in the Startup
class:public class Startup
{
public static readonly Container container = new Container();
Startup
and into the Main
:Doing this allows extra registration to be added to the container, after the Startup
class is done with it, but before the application is actuall started:
public static void Main(string[] args)
{
...
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(directoryPath)
.UseStartup<Startup>()
.Build();
// Don't forget to remove the Verify() call from within the Startup.
Startup.Container.Verify();
...
}
MyService
as singletonRegistering it explictly as singleton in the container allows Simple Injector to run diagnostics on it and prevents accidental captive dependencies that the MyService
might accidentaly have. You should register it as singleton, because it will be kept alive for the duration of the application:
public static void Main(string[] args)
{
...
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(directoryPath)
.UseStartup<Startup>()
.Build();
Startup.Container.RegisterSingleton<MyService>();
Startup.Container.Verify();
...
}
MyService
requires.MyService
depends on host, loggerFactory, myClientFactory and schedulerProvider, which are not all currently registered.
The host
can be registered inside the Main
method:
public static void Main(string[] args)
{
...
Startup.Container.RegisterSingleton<MyService>();
Startup.Container.RegisterSingleton<IWebHost>(host);
Startup.Container.Verify();
...
}
While the loggerFactory
can be registered inside the Startup
class:
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
Container.RegisterSingleton(loggerFactory);
...
}
I assume that the myClientFactory
and schedulerProvider
dependencies are already registered in the container.
RunAsMyService
extension method with simple resolve from the containerSince the MyService
is successfully registered in the container with all its dependencies, we should now be able to resolve it from the container and pass it on to the ServiceBase.Run
method:
public static void Main(string[] args)
{
...
Startup.Container.Verify();
ServiceBase.Run(Startup.Container.GetInstance<MyService>());
}
That should be all.
One last note, depending on the type of application you are building, you might not need such elaborate Startup
class at all. You might move the configuration of the container closer to the Main
method. Whether you should do this, depends on how much you actually need.
Here's an example of working without an Startup
class:
public static void Main(string[] args)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new AspNetRequestLifestyle();
IWebHost host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.ConfigureServices(services =>
{
// Configure framework components
services.AddOptions();
})
.Configure(app =>
{
app.UseSimpleInjectorAspNetRequestScoping(container);
// Apply cross-wirings:
container.RegisterSingleton(
app.ApplicationServices.GetRequiredService<ILoggerFactory>());
})
.UseStartup<Startup>()
.Build();
container.RegisterSingleton<MyService>();
container.RegisterSingleton(host);
container.Verify();
ServiceBase.Run(Startup.Container.GetInstance<MyService>());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With