【问题标题】:Hangfire run background job with user contextHangfire 使用用户上下文运行后台作业
【发布时间】:2019-09-25 15:13:29
【问题描述】:

我有一个多租户应用。我想在用户上下文下创建后台作业,但我找不到实现它的好方法。 我将解释一下我的架构。我正在使用包含 UserID 的接口 ICurrentUser。在 Startup 类中,我在 IoC 中注册了实现 ICurrentUser 的类 WebUser,该类获取 HttpContext 并从声明中提取用户详细信息。

我正在执行后台作业,并且 ICurrentUser.UserID 按预期为空,因为 hangfire 没有任何 httpcontext。

我通过使用接受 ICurrentUser 作为第一个参数的方法创建我的后台任务来解决这个问题,然后在方法体内, 我为 UnitOfWork(和 AppServices)设置了“CurrentUser”并开始执行任务,这种方法的问题是我必须对每个后台任务重复此代码并将 CurrentUser 传递给它。

我的问题是如何实现下一个目标。或者,也许您可​​以建议其他解决方案。

  1. 如何将我的 CurrentUser 传递给 JobActivator,以便在解决所有服务之前设置用户上下文。

例如,它可能看起来像这样:

BackgroundJob.Enqueue<MySvc>(UserContext, mysvc=>mysvc.Run());

我阅读了源代码,但确实没有找到任何扩展点来实现这一点。

非常感谢任何帮助。

【问题讨论】:

  • 谢谢。这解决了我的部分问题。我已经更进一步了。希望在最近的时间解决它并发布解决方案。与 DI 相关的事情很少,我仍在努力解决。其中之一是如何使过滤器作为作用域(或临时服务)工作并传递依赖项。目前我将 ServiceProvider 传递给构造函数。我要实现的最重要的事情是从作业参数创建新的 (ICurrentUser) 实例,以便在我的后台作业中使用此实例订购依赖服务。我为此实现了 JobActivator,但它仍然无法正常工作。

标签: hangfire


【解决方案1】:

最后,我完成了与@jbl 建议的几乎相同的解决方案。 我创建了一个过滤器,将我当前的用户存储到作业参数中。

public class BackgroundJobFilter : JobFilterAttribute, IClientFilter, IApplyStateFilter
{
    private readonly IServiceProvider _serviceProvider;

    public BackgroundJobFilter(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void OnCreating(CreatingContext filterContext)
    {
        var currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
        filterContext.SetJobParameter(nameof(ICurrentUser), currentUser);
    }
}

然后将过滤器添加到 Hangfire 中

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    GlobalConfiguration.Configuration.UseFilter(new BackgroundJobFilter(app.ApplicationServices));
}

然后我已经替换了当前的工作激活器

    internal class ServiceJobActivatorScope : JobActivatorScope
    {
        private readonly IServiceScope _serviceScope;

        public ServiceJobActivatorScope([NotNull] IServiceScope serviceScope)
        {
            if (serviceScope == null)
                throw new ArgumentNullException(nameof(serviceScope));

            _serviceScope = serviceScope;
        }

        public override object Resolve(Type type)
        {
            return ActivatorUtilities.GetServiceOrCreateInstance(_serviceScope.ServiceProvider, type);
        }

        public override void DisposeScope()
        {
            _serviceScope.Dispose();
        }
    }

最后,设置当前用户详细信息(在运行任务时为空)

  public class CustomJobActivator : JobActivator
    {
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly IMapper _objectMapper;


        public CustomJobActivator([NotNull] IServiceScopeFactory serviceScopeFactory, IMapper objectMapper)
        {
            if (serviceScopeFactory == null)
                throw new ArgumentNullException(nameof(serviceScopeFactory));

            _serviceScopeFactory = serviceScopeFactory;
            _objectMapper = objectMapper;
        }

        public override JobActivatorScope BeginScope(JobActivatorContext context)
        {
            var user = context.GetJobParameter<WebUser>(nameof(ICurrentUser));

            var serviceScope = _serviceScopeFactory.CreateScope();

            var currentUser = serviceScope.ServiceProvider.GetRequiredService<ICurrentUser>();
            //Copy value from user to currentUser
            _objectMapper.Map(user, currentUser);

            return new ServiceJobActivatorScope(serviceScope);
        }
    }

然后替换容器中已有的JobActivator

services.Replace(new ServiceDescriptor(typeof(JobActivator), typeof(CustomJobActivator), ServiceLifetime.Scoped));

之后,当服务开始从此范围解析时,当我使用 ICurrentUser 正常工作时,它们将获得用户上下文和 DbContext 和其他地方的所有过滤器。

【讨论】:

  • 嗨,我意识到这个年纪大了,但我有一个问题。我正在尝试做同样的事情并将tenantId 传递给jobfilter。但是,我在发现该服务时遇到了一些麻烦。在您上面的示例 BackgroundJobFilter.OnCreating 中,我假设您的 ICurrentUser 是一个作用域或临时服务。但是,这些服务似乎在 _serviceProvider 中不可用(我的测试显示只显示单例服务)。你是如何解决这个问题的?
  • I CurrentUser 是作用域的。你能提供更多的细节。也许那是因为我正在使用 aurofac。不确定。如果您给我更多详细信息,我会尽力提供帮助。您在哪里尝试解决服务问题?
  • 您好,感谢您的回复。我们现在找到了解决该问题的方法,并且由于我们没有使用 Autofac,因此可能很难进行苹果与苹果的比较。再次感谢!
  • 可能你没有正确注册BackgroundJobFilter,设置断点并确保你的SetJobParameter实际被调用。
  • 在我的情况下,ICurrentUser 是作用域的,我没有在 IClientFilter OnCreating 方法上获取数据我最终手动设置了作业 ID 的参数,例如使用 (var connection = JobStorage.Current.GetConnection()) { connection.SetJobParameter(jobId, nameof(ICurrentUser), Newtonsoft.Json.JsonConvert.SerializeObject(CurrentUser)); }
猜你喜欢
  • 1970-01-01
  • 2018-08-23
  • 2017-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多