Ninject Scope Issues with First TopShelf, Ninject, and EF Code

I am currently using TopShelf with Ninject to create a Windows service. I have the following code to configure a 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 a Windows service object that does 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 of all, let me say that I am not happy with this code. When the application starts the kernel instance, the values ​​from the parameters are created, and I use TopShelf to create the Windows service using the BotService object.

Each time a timer event fires the Run () method. Here another kernel instance is created, it reads the settings again and depending on the value that the kernel retrieves in all implementations of the interface and executes the corresponding method. Each of these implementations has a constructor into which IUnitOfWork and IMyRepository are entered to access the data.

When the methods are finished, I delete the context and delete the kernel.

Why did I set it up like this? Initially, I created only one core in Main and used the constructor in BotService to implement the implementations, and not to create another instance of the kernel. The problem was that DatabaseFactory needed InSingletonScope or InThreadScope to work.

If I used InSingelScope, the context would become obsolete, and eventually problems would start to appear where the context was invalid. If I used InThreadScope, I run the same problem because it does not destroy objects after the thread terminates. Ultimately, Run () used the previously used thread, and an exception was thrown, since I already got rid of the context. If I delete a line of code that I am well versed in, we are faced with the same problem as InSingletonScope, where the deprecated context ends when reusing the stream.

This will result in the current code in which I am guaranteed that Run () is executed every time, the context is around until it is executed where it is located, and since the kernel is also located, I guarantee that the next time the same thread will we use a new context, as the kernel is restored (at least I think this is what happens).

My Ninject skills are not so advanced, and there is very limited information on how to approach this problem. I think the correct approach would be to create only one core only in Main, and then inject what I need into the BotService object through the constructor. But at the same time, a context must be created for each Run () to avoid the outdated context that would occur if I used one of the areas mentioned above with this approach.

How can I modify the above example so that it is correct? I am currently using Ninject 2.2.1.4.

+4
source share
1 answer

First, let me try to solve the problem a bit. It sounds as if you have a dependency (DatabaseFactory) that requires a custom scope (or a lifetime, as others may refer to them as). It seems to me that you want the same instance of DatabaseFactory to return for the duration of a single run run.

If this is correct, I think you can accomplish this in one of two ways:

  • If you don't mind that all instances are updated for every Run 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 want specific instances to be updated for each execution, you must do this with a custom scope object (take a look at the InScope () method and this post from Nate ). Unfortunately, you are likely to run into a lot of problems with multiple threads, as Timer can call Run before another thread exits.
+3
source

Source: https://habr.com/ru/post/1395646/


All Articles