我目前正在使用带有 Ninject 的 TopShelf 来创建 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。