问题
I am currently using TopShelf with Ninject to create a Windows Service. I have the following code to setup the Windows Service using TopShelf:
static void Main(string[] args)
{
using (IKernel kernel = new StandardKernel(new NinjectDependencyResolver()))
{
Settings settings = kernel.Get<Settings>();
var host = HostFactory.New(x =>
{
x.Service<BotService>(s =>
{
s.ConstructUsing(name => new BotService(settings.Service.TimeInterval));
s.WhenStarted(ms => ms.Start());
s.WhenStopped(ms => ms.Stop());
});
x.RunAsNetworkService();
x.SetServiceName(settings.Service.ServiceName);
x.SetDisplayName(settings.Service.DisplayName);
x.SetDescription(settings.Service.Description);
});
host.Run();
}
}
This is the object behind the Windows Service doing all the work:
public class BotService
{
private readonly Timer timer;
public BotService(double interval)
{
this.timer = new Timer(interval) { AutoReset = true };
this.timer.Elapsed += (sender, eventArgs) => Run();
}
public void Start()
{
this.timer.Start();
}
public void Stop()
{
this.timer.Stop();
}
private void Run()
{
IKernel kernel = new StandardKernel(new NinjectDependencyResolver());
Settings settings = kernel.Get<Settings>();
if (settings.Service.ServiceType == 1)
{
// The interface implementation has constructor injection of IUnitOfWork and IMyRepository
kernel.GetAll<IExternalReportService>().Each(x => x.Update());
}
if (settings.Service.ServiceType == 2)
{
// The interface implementation has constructor injection of IUnitOfWork and IMyRepository
kernel.GetAll<IExternalDataService>().Each(x => x.GetData());
}
kernel.Get<IUnitOfWork>().Dispose();
kernel.Dispose();
}
}
These are the Ninject bindings:
public class NinjectDependencyResolver : NinjectModule
{
public override void Load()
{
Settings settings = CreateSettings();
ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings["DB"];
Bind<IDatabaseFactory>().To<DatabaseFactory>()
.InThreadScope()
.WithConstructorArgument("connectionString", connectionStringSettings.Name);
Bind<IUnitOfWork>().To<UnitOfWork>();
Bind<IMyRepository>().To<MyRepository>();
Bind<IExternalReportService>().To<ReportService1>();
Bind<IExternalReportService>().To<ReportService2>();
Bind<IExternalDataService>().To<DataService1>();
Bind<IExternalDataService>().To<DataService2>();
Bind<Settings>().ToConstant(settings);
}
private Settings CreateSettings()
{
// Reads values from app.config and returns object with settings
}
}
First off let me say that I am not happy with this code. When the application starts an instance of the kernel is created, the values from settings are fetched and I use TopShelf to create a Windows Service using the BotService object.
Everytime the timer event fires the Run() method is executed. Here another instance of the kernel is created, again it reads the settings and depending on the value the kernel fetches all implementations of the interface and executes the corresponding method. Each of these implementations has a constructor where IUnitOfWork and IMyRepository are injected for data access.
When the methods are finished I dispose of the context and dispose of the kernel.
Why did I set it up like this? Originally I only created one kernel in the Main and used a constructor in the BotService to inject the implementations as opposed to creating another instance of the kernel. The problem was that the DatabaseFactory needed a InSingletonScope or InThreadScope to work.
If I used InSingeltonScope the context would become stale and eventually issues would start to creep up where the context is invalid. If I used InThreadScope I run into the same issue because it doesn't dispose the objects once the thread is done. Eventually Run() used a previously used thread and an exception occurrs since I already disposed of the Context. If I removed the line of code where I dispose of the context well then we run into the same issue as InSingletonScope where we end up with a stale context when the thread is re-used.
This lead to the current code where I am guaranteed that each Time Run() is executed the context is around until it is done where it is disposed and since the kernel is disposed as well I ensure that next time the same thread is used we get a new context since the kernel is re-created (at least I think this is what's happening).
My Ninject skills are not that advanced and there is very limited information out there on how to approach this problem. I think the right approach would be to create one kernel in the Main only and then be able to inject what I need into the BotService object via a constructor. But at the same time the Context needs to be created for each Run() in order to avoid a stale context which would happen if I used one of the scopes mentioned above with this approach.
How can I modify the example above so it would be correct? I am currently using Ninject 2.2.1.4.
回答1:
First, let me try and distill your problem down a bit. It sounds to me like you have a dependency (DatabaseFactory) that needs a custom scope (or life-time as others may refer to them as). It sounds to me like you want the same instance of DatabaseFactory returned for the duration of a single execution of Run.
If this is correct, I think you should be able to accomplish this in one of two ways:
If you don't mind all instances being refreshed for each execution of Run:
private StandardKernel _kernel /* passed into constructor */; public void Run() { using (var block = _kernel.BeginBlock()) { var settings = block.Get<Settings>(); if (settings.Service.ServiceType == 1) { // The interface implementation has constructor injection of IUnitOfWork and IMyRepository block.GetAll<IExternalReportService>().Each(x => x.Update()); } if (settings.Service.ServiceType == 2) { // The interface implementation has constructor injection of IUnitOfWork and IMyRepository block.GetAll<IExternalDataService>().Each(x => x.GetData()); } } }
- If you only want the specific instances to be refreshed for each execution, you should be able to accomplish this using a custom scope object (have a look at InScope() method and this post from Nate). Unfortunately, you would probably run into a host of multi-threading issues since Timer may call Run before another thread has finished running.
来源:https://stackoverflow.com/questions/9216006/ninject-scope-problems-with-topshelf-ninject-and-ef-code-first