使用 ASP.NET Core 2.0 将 Simple Injector 组件注入 IHostedService
Injecting Simple Injector components into IHostedService with ASP.NET Core 2.0
在ASP.NET Core 2.0 中,有一种方法可以通过实现IHostedService
接口来添加后台任务(参见https://docs.microsoft.com/en-us/aspnet/core/fundamentals/hosted-services?view=aspnetcore-2.0)。按照本教程,我能够让它工作的方法是在 ASP.NET Core 容器中注册它。我的目标是从队列中读取消息并在后台处理作业;消息被发布到队列(通过控制器操作),然后在后台按时间间隔进行处理。
// Not registered in SimpleInjector
services.AddSingleton<IHostedService, MyTimedService>();
当我将此注册放入 ASP.NET 核心容器时,它会在应用程序启动时自动启动该进程。但是,当我在 SimpleInjector 中注册它时,该服务不会自动启动。我相信是这种情况,因为我们只向 MvcControllers 和 MvcViewComponents 注册了 SimpleInjector 容器:
// Wire up simple injector to the MVC components
container.RegisterMvcControllers(app);
container.RegisterMvcViewComponents(app);
我 运行 遇到的问题是,当我想开始将来自 SimpleInjector 的组件注册(例如存储库、带有装饰器的通用处理程序...)注入到 IHostedService 的实现中时,如下所示:
public class TimedService : IHostedService, IDisposable
{
private IJobRepository _repo;
private Timer _timer;
public TimedService(IJobRepository repo)
{
this._repo = repo;
}
...
...
...
}
由于 IHostedService
注册了 ASP.NET Core 而不是 Simple Injector,我在 运行 启动定时后台服务时收到以下错误:
Unhandled Exception: System.InvalidOperationException: Unable to resolve service for type 'Optimization.Core.Interfaces.IJobRepository' while attempting to activate 'Optimization.API.BackgroundServices.TimedService'.
所以我的问题是,在 Simple Injector 中实现后台任务的最佳方式是什么?这是否需要与标准 MVC 集成不同的单独集成包?我如何才能将我的 Simple Injector 注册注入 IHostedService
?如果能在Simple Injector中注册后自动启动服务,我想就可以解决这个问题了。
感谢您在此提供任何指点以及有关此主题的任何建议!我可能做错了什么。在过去的一年里,我非常喜欢使用 Simple Injector。
有多种方法可以解决这个问题。最简单的方法可能是以内置配置系统从 Simple Injector 解析托管服务的方式交叉连接托管服务:
// Register in Simple Injector as Singleton
container.RegisterSingleton<THostedService>();
// Cross-wire TimedService in the built-in configuration system
services.AddSingleton<IHostedService>(
c => container.GetInstance<TimedService>());
请注意,托管服务仅解析一次,并永久缓存,有效地使它们成为单例。这就是为什么你应该在 Simple Injector 中将其注册为 Singleton。
然而,这样做的后果是您将无法将任何 Scoped
或 Transient
依赖项注入托管服务。最重要的是,它迫使您让您的应用程序组件 (TimedService
) 依赖于 ASP.NET 核心抽象 (IHostedService
)。这并不理想。
因此,我的首选方法是创建一个适配器实现,您在 ASP.NET 核心配置系统中注册,该系统将调用转发到简单注入器,同时使用特定于应用程序的抽象来实现您的服务。因此,您无需创建许多 IHostedService
实现,而是定义一个特定于您的应用程序的理想抽象。我们称这个抽象为 IMyJob
.
IHostedService
适配器实现可能如下所示:
public class SimpleInjectorJobProcessorHostedService : IHostedService, IDisposable
{
private readonly Container container;
private Timer timer;
public SimpleInjectorJobProcessorHostedService(Container c) => this.container = c;
public Task StartAsync(CancellationToken cancellationToken)
{
this.timer = new Timer(this.DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
// Run operation in a scope
using (AsyncScopedLifestyle.BeginScope(this.container))
{
// Resolve the collection of IMyJob implementations
foreach (var service in this.container.GetAllInstances<IMyJob>())
{
service.DoWork();
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
this.timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose() => this.timer?.Dispose();
}
您可以在ASP.NET核心中注册如下:
services.AddSingleton<IHostedService>(
new SimpleInjectorJobProcessorHostedService(container));
通过这种方式,您 运行 可以忽略 ASP.NET 核心的实际工作,并且可以定义如下:
public class CoolJob : IMyJob
{
private readonly IJobRepository repo;
public CoolJob(IJobRepository repo) => this.repo = repo;
public void DoWork() => ...
}
并且所有作业都可以在 Simple Injector 中注册如下:
// NOTE: Simple Injector v4.3 API
container.Collection.Register<IMyJob>(typeof(CoolJob).Assembly);
我会连接到 HostBuilder 的 ConfigureContainer 方法并在那里设置 simpleinjectore,如下所示:
IHostBuilder()
.ConfigureContainer<ServiceCollection>((builder, services) =>
{
var container = new Container();
container.RegisterSingleton<IJobRepository, JobRepository>();
services.AddTransient<IHostedService, TimedService>();
})
.ConfigureServices((hostContext, services) =>
{
// Originally we would have done this
//services.AddHostedService<Service>();
})
.Build();
using (host)
{
await host.StartAsync();
await host.WaitForShutdownAsync();
}
虽然您确实可以使用 IHostedService 实现,但我认为它可能会隐藏正在发生的事情。我相信基础设施引导应该在一个地方完成,或者至少在一个地方进行协调。我认为容器是基础设施,并会通过 HostBuilder 方法将其与应用程序的其余部分一起设置。
一个额外的优势可能是您不必完全替换 ServiceCollection,因为它可以很好地处理其他与框架相关的事情。我仍然会用 ServiceCollection 做的一些事情的例子:
IHostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddOptions();
})
这与 simpleinjector 文档中关于使用 ASP.NET 核心设置容器的说明一致:
The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components,The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components
这同样适用于 .net 核心和通用 HostBuilder。
在ASP.NET Core 2.0 中,有一种方法可以通过实现IHostedService
接口来添加后台任务(参见https://docs.microsoft.com/en-us/aspnet/core/fundamentals/hosted-services?view=aspnetcore-2.0)。按照本教程,我能够让它工作的方法是在 ASP.NET Core 容器中注册它。我的目标是从队列中读取消息并在后台处理作业;消息被发布到队列(通过控制器操作),然后在后台按时间间隔进行处理。
// Not registered in SimpleInjector
services.AddSingleton<IHostedService, MyTimedService>();
当我将此注册放入 ASP.NET 核心容器时,它会在应用程序启动时自动启动该进程。但是,当我在 SimpleInjector 中注册它时,该服务不会自动启动。我相信是这种情况,因为我们只向 MvcControllers 和 MvcViewComponents 注册了 SimpleInjector 容器:
// Wire up simple injector to the MVC components
container.RegisterMvcControllers(app);
container.RegisterMvcViewComponents(app);
我 运行 遇到的问题是,当我想开始将来自 SimpleInjector 的组件注册(例如存储库、带有装饰器的通用处理程序...)注入到 IHostedService 的实现中时,如下所示:
public class TimedService : IHostedService, IDisposable
{
private IJobRepository _repo;
private Timer _timer;
public TimedService(IJobRepository repo)
{
this._repo = repo;
}
...
...
...
}
由于 IHostedService
注册了 ASP.NET Core 而不是 Simple Injector,我在 运行 启动定时后台服务时收到以下错误:
Unhandled Exception: System.InvalidOperationException: Unable to resolve service for type 'Optimization.Core.Interfaces.IJobRepository' while attempting to activate 'Optimization.API.BackgroundServices.TimedService'.
所以我的问题是,在 Simple Injector 中实现后台任务的最佳方式是什么?这是否需要与标准 MVC 集成不同的单独集成包?我如何才能将我的 Simple Injector 注册注入 IHostedService
?如果能在Simple Injector中注册后自动启动服务,我想就可以解决这个问题了。
感谢您在此提供任何指点以及有关此主题的任何建议!我可能做错了什么。在过去的一年里,我非常喜欢使用 Simple Injector。
有多种方法可以解决这个问题。最简单的方法可能是以内置配置系统从 Simple Injector 解析托管服务的方式交叉连接托管服务:
// Register in Simple Injector as Singleton
container.RegisterSingleton<THostedService>();
// Cross-wire TimedService in the built-in configuration system
services.AddSingleton<IHostedService>(
c => container.GetInstance<TimedService>());
请注意,托管服务仅解析一次,并永久缓存,有效地使它们成为单例。这就是为什么你应该在 Simple Injector 中将其注册为 Singleton。
然而,这样做的后果是您将无法将任何 Scoped
或 Transient
依赖项注入托管服务。最重要的是,它迫使您让您的应用程序组件 (TimedService
) 依赖于 ASP.NET 核心抽象 (IHostedService
)。这并不理想。
因此,我的首选方法是创建一个适配器实现,您在 ASP.NET 核心配置系统中注册,该系统将调用转发到简单注入器,同时使用特定于应用程序的抽象来实现您的服务。因此,您无需创建许多 IHostedService
实现,而是定义一个特定于您的应用程序的理想抽象。我们称这个抽象为 IMyJob
.
IHostedService
适配器实现可能如下所示:
public class SimpleInjectorJobProcessorHostedService : IHostedService, IDisposable
{
private readonly Container container;
private Timer timer;
public SimpleInjectorJobProcessorHostedService(Container c) => this.container = c;
public Task StartAsync(CancellationToken cancellationToken)
{
this.timer = new Timer(this.DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
// Run operation in a scope
using (AsyncScopedLifestyle.BeginScope(this.container))
{
// Resolve the collection of IMyJob implementations
foreach (var service in this.container.GetAllInstances<IMyJob>())
{
service.DoWork();
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
this.timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose() => this.timer?.Dispose();
}
您可以在ASP.NET核心中注册如下:
services.AddSingleton<IHostedService>(
new SimpleInjectorJobProcessorHostedService(container));
通过这种方式,您 运行 可以忽略 ASP.NET 核心的实际工作,并且可以定义如下:
public class CoolJob : IMyJob
{
private readonly IJobRepository repo;
public CoolJob(IJobRepository repo) => this.repo = repo;
public void DoWork() => ...
}
并且所有作业都可以在 Simple Injector 中注册如下:
// NOTE: Simple Injector v4.3 API
container.Collection.Register<IMyJob>(typeof(CoolJob).Assembly);
我会连接到 HostBuilder 的 ConfigureContainer 方法并在那里设置 simpleinjectore,如下所示:
IHostBuilder()
.ConfigureContainer<ServiceCollection>((builder, services) =>
{
var container = new Container();
container.RegisterSingleton<IJobRepository, JobRepository>();
services.AddTransient<IHostedService, TimedService>();
})
.ConfigureServices((hostContext, services) =>
{
// Originally we would have done this
//services.AddHostedService<Service>();
})
.Build();
using (host)
{
await host.StartAsync();
await host.WaitForShutdownAsync();
}
虽然您确实可以使用 IHostedService 实现,但我认为它可能会隐藏正在发生的事情。我相信基础设施引导应该在一个地方完成,或者至少在一个地方进行协调。我认为容器是基础设施,并会通过 HostBuilder 方法将其与应用程序的其余部分一起设置。
一个额外的优势可能是您不必完全替换 ServiceCollection,因为它可以很好地处理其他与框架相关的事情。我仍然会用 ServiceCollection 做的一些事情的例子:
IHostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddOptions();
})
这与 simpleinjector 文档中关于使用 ASP.NET 核心设置容器的说明一致:
The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components,The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components
这同样适用于 .net 核心和通用 HostBuilder。