I have an app, with multi-tenancy. I want to create background job under user context, but I can't find good way to implement that. I'll explain a bit my architecture. I'm using Interface ICurrentUser that contain UserID. In Startup class I register as scoped in IoC the class WebUser which implements ICurrentUser, this class getting HttpContext and extract user details from claims.
I'm executing background job and the ICurrentUser.UserID is null as expected because hangfire doesn't have any httpcontext.
I'm solving this problem by creating my background tasks with method which accept ICurrentUser as first argument, then inside method body, I set my "CurrentUser" for UnitOfWork (and AppServices) and start executing task, the problem with this approach that I have to repeat this code with every background task and pass CurrentUser into it.
My question how can achieve next thing. Or maybe you can suggest other solutions for it.
For Example it may look like that:
BackgroundJob.Enqueue<MySvc>(UserContext, mysvc=>mysvc.Run());
I read sources and really didn't find any extension points to implement this.
Any help is greatly appreciated.
Finally, I finished up with almost the same solution that @jbl suggested. I've created a filter which stores my current user into the job parameters.
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);
}
}
Then add filter into Hangfire
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
GlobalConfiguration.Configuration.UseFilter(new BackgroundJobFilter(app.ApplicationServices));
}
Then I've replaced current job activator
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();
}
}
And finally, set current user details (which is null on the moment of running task)
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);
}
}
Then replace the existing JobActivator in container
services.Replace(new ServiceDescriptor(typeof(JobActivator), typeof(CustomJobActivator), ServiceLifetime.Scoped));
After that when services start resolving from this scope they will get user context and all filter in DbContext and other places when I use ICurrentUser works properly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With