我目前正在使用TopShelf和Ninject来创建
Windows服务.我有以下代码使用TopShelf设置Windows服务:
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(); } }
这是Windows服务完成所有工作背后的对象:
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(); } }
这些是Ninject绑定:
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 } }
首先让我说我对这段代码不满意.当应用程序启动时,会创建一个内核实例,从中获取设置中的值,并使用TopShelf使用BotService对象创建Windows服务.
每次定时器事件触发时,都会执行Run()方法.这里创建了另一个内核实例,它再次读取设置,并根据值获取接口的所有实现并执行相应的方法.这些实现中的每一个都有一个构造函数,其中注入IUnitOfWork和IMyRepository以进行数据访问.
当方法完成后,我处理上下文并处理内核.
为什么我这样设置?最初我只在Main中创建了一个内核,并使用BotService中的构造函数来注入实现,而不是创建另一个内核实例.问题是DatabaseFactory需要InSingletonScope或InThreadScope才能工作.
如果我使用InSingeltonScope,上下文将变得陈旧,最终问题将开始蔓延到上下文无效的地方.如果我使用InThreadScope,我会遇到同样的问题,因为一旦线程完成,它就不会处理对象.最终Run()使用了以前使用过的线程和异常,因为我已经处理了Context.如果我删除了我处理上下文的代码行,那么我们遇到与InSingletonScope相同的问题,当重新使用该线程时,我们最终会遇到陈旧的上下文.
这导致了当前的代码,我保证每个Time Run()都被执行,上下文一直存在,直到它完成它被处理的地方,因为内核也被处理掉了我确保下次使用相同的线程时我们得到自重新创建内核以来的新上下文(至少我认为这是正在发生的事情).
我的Ninject技能并不是那么先进,关于如何解决这个问题的信息非常有限.我认为正确的方法是仅在Main中创建一个内核,然后能够通过构造函数将我需要的内容注入到BotService对象中.但同时需要为每个Run()创建Context,以避免在我使用上述方法之一时使用此方法时会发生的过时上下文.
如何修改上面的示例以便它是正确的?我目前正在使用Ninject 2.2.1.4.
首先,让我尝试将您的问题提炼一点.听起来像你有一个需要自定义范围的依赖项(DatabaseFactory)(或其他人可能将其称为生命周期).听起来我想要在单次执行Run期间返回相同的DatabaseFactory实例.
如果这是正确的,我认为您应该能够通过以下两种方式之一完成此任务:
>如果您不介意每次执行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()); } } }
>如果您只希望为每次执行刷新特定实例,则应该能够使用自定义范围对象完成此操作(查看InScope()方法和this post from Nate).不幸的是,您可能会遇到许多多线程问题,因为Timer可能会在另一个线程运行完毕之前调用Run.