Hangfire 使用用户上下文运行后台作业

gri*_*nay 6 hangfire

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

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

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

我的问题是如何实现下一步。或者,也许您可​​以为它提出其他解决方案。

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

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

BackgroundJob.Enqueue<MySvc>(UserContext, mysvc=>mysvc.Run());
Run Code Online (Sandbox Code Playgroud)

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

任何帮助是极大的赞赏。

gri*_*nay 11

最后,我完成了与 @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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将过滤器添加到Hangfire中

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

然后我更换了当前的工作激活器

    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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

  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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后替换容器中已有的JobActivator

services.Replace(new ServiceDescriptor(typeof(JobActivator), typeof(CustomJobActivator), ServiceLifetime.Scoped));
Run Code Online (Sandbox Code Playgroud)

public interface ICurrentUser
{
    string UserId { get; set; }
}


public class UserProvider : ICurrentUser
{
    private int? _userId;

    protected readonly IHttpContextAccessor HttpContextAccessor;


    public UserProvider(IHttpContextAccessor httpContextAccessor)
        {
            HttpContextAccessor = httpContextAccessor;
        }

    public virtual int? UserId =>
        HttpContextAccessor.HttpContext?.User?.Id() != null &&
        HttpContextAccessor.HttpContext?.User?.Id() != default(int)
            ? HttpContextAccessor.HttpContext?.User?.Id(): _userId;
} 
Run Code Online (Sandbox Code Playgroud)

UserProvider 是有范围的服务。

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