用非通用接口装饰 class,其中装饰器依赖于接口,而不是 class
Decorating a class with a non-generic interface where the decorator is dependent on the interface, not the class
我有一个装饰器,我想将它用于我所有的工作。作业都实现了非通用 IJob
接口。为了运行作业正常,我注册了具体的class。现在我想用一个简单的装饰器装饰所有实现 IJob
的 classes。问题当然是装饰器依赖于 IJob
,但是 IJob
并没有这样注册。
这是作业示例
internal class HelloWorldJob : IJob
{
private readonly ILogger<HelloWorldJob> _logger;
public HelloWorldJob(ILogger<HelloWorldJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Hello world! " +
$"{context.JobDetail.JobDataMap.GetByCaseInsensitiveKey("tenant_code")}");
return Task.CompletedTask;
}
public void Dispose()
{
_logger.LogInformation("Disposing Hello World Job!");
}
}
和装饰器
internal class JobTimingDecorator : IJob
{
private readonly ILogger _logger;
private readonly IJob _decorated;
public JobTimingDecorator(ILogger logger, IJob decorated)
{
_logger = logger;
_decorated = decorated;
}
public async Task Execute(IJobExecutionContext context)
{
var stopwatch = Stopwatch.StartNew();
var jobTypeName = _decorated.GetType().Name;
var tenantCode =
context.JobDetail.JobDataMap.GetByCaseInsensitiveKey("tenant_code") as string
?? throw new ArgumentOutOfRangeException(
message: "Unable to retrieve the tenant_code from the context",
paramName: nameof(context));
_logger.Information($"Begin job {jobTypeName} for tenant {tenantCode}");
await _decorated.Execute(context);
stopwatch.Stop();
_logger.Information(
$"End job {jobTypeName} for tenant {tenantCode}. " +
"Job took {stopwatch.Elapsed.TotalSeconds} to process");
}
}
我知道我可以使用 IJob<>
的通用接口轻松解决此问题,但对于这种特殊情况,它有点矫枉过正。有没有办法告诉SI,"decorate anything that implements IJob
with this decorator"?
以下是从容器中检索作业的方式。
public async Task Execute(IJobExecutionContext context)
{
using (AsyncScopedLifestyle.BeginScope(container: _container))
{
var jobType = context.JobDetail.JobType;
var job = (IJob)_container.GetInstance(jobType);
await job.Execute(context);
}
}
其中 context.JobDetail.JobType
是要执行的实际作业的类型。
您需要做的是,不是按具体类型注册所有作业,而是为每个使用 IJob
作为服务类型的作业创建 InstanceProducer
个实例。您可以向根据作业类型解析作业的代码提供作业生成器列表。
InstanceProducer
是简单注入器中根据使用的抽象应用装饰器的地方。
当您调用 container.Register<TImplementation>()
时,Simple Injector 会注册一个 InstanceProducer
。使用 container.Collection.Register<IJob>(assembly)
注册作业集合在您的情况下不起作用,因为在列表中再次找到正确的作业变得非常困难,特别是因为您将作业包装在装饰器中。您将不得不遍历整个列表并查看装饰者的类型。这不会很高效。
但是您可以自己跟踪它们,而不是让 Simple Injector 管理 InstanceProducer
个实例:
container.RegisterDecorator<IJob, JobTimingDecorator>();
var jobProducers = (
from jobType in container.GetTypesToRegister<IJob>(myAssembly)
select Lifestyle.Transient.CreateProducer<IJob>(jobType, container))
.ToDictionary(p => p.Registration.ImplementationType, p => p);
public async Task Execute(IJobExecutionContext context)
{
using (AsyncScopedLifestyle.BeginScope(container: _container))
{
var jobType = context.JobDetail.JobType;
var producer = jobProducers[jobType];
var job = producer.GetInstance();
await job.Execute(context);
}
}
我有一个装饰器,我想将它用于我所有的工作。作业都实现了非通用 IJob
接口。为了运行作业正常,我注册了具体的class。现在我想用一个简单的装饰器装饰所有实现 IJob
的 classes。问题当然是装饰器依赖于 IJob
,但是 IJob
并没有这样注册。
这是作业示例
internal class HelloWorldJob : IJob
{
private readonly ILogger<HelloWorldJob> _logger;
public HelloWorldJob(ILogger<HelloWorldJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Hello world! " +
$"{context.JobDetail.JobDataMap.GetByCaseInsensitiveKey("tenant_code")}");
return Task.CompletedTask;
}
public void Dispose()
{
_logger.LogInformation("Disposing Hello World Job!");
}
}
和装饰器
internal class JobTimingDecorator : IJob
{
private readonly ILogger _logger;
private readonly IJob _decorated;
public JobTimingDecorator(ILogger logger, IJob decorated)
{
_logger = logger;
_decorated = decorated;
}
public async Task Execute(IJobExecutionContext context)
{
var stopwatch = Stopwatch.StartNew();
var jobTypeName = _decorated.GetType().Name;
var tenantCode =
context.JobDetail.JobDataMap.GetByCaseInsensitiveKey("tenant_code") as string
?? throw new ArgumentOutOfRangeException(
message: "Unable to retrieve the tenant_code from the context",
paramName: nameof(context));
_logger.Information($"Begin job {jobTypeName} for tenant {tenantCode}");
await _decorated.Execute(context);
stopwatch.Stop();
_logger.Information(
$"End job {jobTypeName} for tenant {tenantCode}. " +
"Job took {stopwatch.Elapsed.TotalSeconds} to process");
}
}
我知道我可以使用 IJob<>
的通用接口轻松解决此问题,但对于这种特殊情况,它有点矫枉过正。有没有办法告诉SI,"decorate anything that implements IJob
with this decorator"?
以下是从容器中检索作业的方式。
public async Task Execute(IJobExecutionContext context)
{
using (AsyncScopedLifestyle.BeginScope(container: _container))
{
var jobType = context.JobDetail.JobType;
var job = (IJob)_container.GetInstance(jobType);
await job.Execute(context);
}
}
其中 context.JobDetail.JobType
是要执行的实际作业的类型。
您需要做的是,不是按具体类型注册所有作业,而是为每个使用 IJob
作为服务类型的作业创建 InstanceProducer
个实例。您可以向根据作业类型解析作业的代码提供作业生成器列表。
InstanceProducer
是简单注入器中根据使用的抽象应用装饰器的地方。
当您调用 container.Register<TImplementation>()
时,Simple Injector 会注册一个 InstanceProducer
。使用 container.Collection.Register<IJob>(assembly)
注册作业集合在您的情况下不起作用,因为在列表中再次找到正确的作业变得非常困难,特别是因为您将作业包装在装饰器中。您将不得不遍历整个列表并查看装饰者的类型。这不会很高效。
但是您可以自己跟踪它们,而不是让 Simple Injector 管理 InstanceProducer
个实例:
container.RegisterDecorator<IJob, JobTimingDecorator>();
var jobProducers = (
from jobType in container.GetTypesToRegister<IJob>(myAssembly)
select Lifestyle.Transient.CreateProducer<IJob>(jobType, container))
.ToDictionary(p => p.Registration.ImplementationType, p => p);
public async Task Execute(IJobExecutionContext context)
{
using (AsyncScopedLifestyle.BeginScope(container: _container))
{
var jobType = context.JobDetail.JobType;
var producer = jobProducers[jobType];
var job = producer.GetInstance();
await job.Execute(context);
}
}