用非通用接口装饰 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);
    }
}