LightInject
LightInject copied to clipboard
Implementing PerJobLifetime() for use with Hangfire
I'm trying to understand scope management in LightInject by looking at the scope manger from the LightInject.Web project. The goal is to ensure that each job inside of Hangfire gets their own instance of services.
As far as I have found, there is no global state available in hangfire - meaning that there is no Hangfire.CurrentJob or anything similar to the HttpContext.Current for web applications. What do exist, though, is a IServerFilter with OnPerforming and OnPerformed methods, both being passed a PerformContext-object with a dictionary where key-value pairs can be added.
Should I just use the PerScopeLifetime-class and begin new scopes in OnPerforming and end them in OnPerformed? Or would it make more sense to implement a PerJobLifetime similar to the PerRequestLifeTime with its own scope manager and everything?
I am not familiar with Hangfire, but I would most certainly start with managing the scopes inside OnPerforming and OnPerformed and see where that takes you
It took me just a short step.
I'm having huge trouble with threads currently. Seems like async/await breaks the scope. Both for PerRequestLifetime()-services on the webside, and PerScopeLifetime() on the background worker.
I'm using MediatR alot, and have opened an issue there to see if Bogard is able to explain what's going on.
Anyway, 'mid-request', Lightinject throws exception telling me there is no request scope available, and on the background worker, I get new instances for services where I expected to receive same instance as long as I haven't ended the scope.
😢
From my IoC config:
var container = new ServiceContainer {
ScopeManagerProvider = new PerLogicalCallContextScopeManagerProvider()
};
container.Register<IPrincipalProvider, HangfireJobPrincipalProvider>(new PerScopeLifetime());
The IPrincipalProvider has methods for setting and getting current user.
In my IServerFilter, I have code like this: (passing the container to the constructor of the filter...)
public void OnPerforming(PerformingContext filterContext)
{
var scope = _container.BeginScope();
filterContext.Items[key] = scope;
var user = filterContext.GetJobParameter<Identity>("User");
if (user != null) {
var p = _container.GetInstance<IPrincipalProvider>()
p.SetUser(user);
}
}
public void OnPerformed(PerformedContext filterContext)
{
var scope = filterContext.Items[key] as Scope;
scope.Dispose();
}
And it doesn't work....
When instances of my IParticipantProvider-interfaces are injected into code, it receives new copies instead of the previously populated instance.
One thought that just came to me: Could my problem be related to services with different lifetime depending on each other? Except - all other services are basically transient, so I can't see the problem there either....
A small repro of this would be very useful. How is this hosted? Is it a web app and if so, is it an IIS web app? I'd love to get to the bottom of this, but I really need something to look at where I can set a breakpoint and see it fail. There are so many ifs and buts when it comes to ambient context depending on the hosting environment. Also take a look at #386 for more information about HttpContext.Current and async/await.
I would love to create a small repo, but I'm not sure I'll manage...
Anyway. It's a Azure Cloud Service hosted project with one WebRole and one WorkerRole. Both are targeting .NET Framework 4.5.2 and hosted in IIS Express when running locally.
I had a look at #386 earlier, and have to admit that it goes a little over my head... Same for the 4.7.1 feature that was announced. As I understand it, it gives an option to manually restore context between execution steps?
None of this should relate to the problems I have with the background worker, though, should it?
When it says No .ConfigAwaiter(false) - I can of cause prevent such a call in my code, but if I await a library method, and that method awaits another call, with .ConfigAwaiter(false) - does that count? Will that break the chain?
Will that break the chain?
It could, but that scenario should be rare. If you inject some Func<T> somewhere that is used to resolve a scoped instance, it could break if you're somehow lost the synchronization context. I would not worry to much about that, but it is something to be aware of.
If I understand correctly, your main issue here is to ensure a single IPrincipalProvider across the boundaries of OnPerforming and OnPerformed ?
https://www.hangfire.io/extensions.html#ioc-containers
You are aware of this I assume?
https://github.com/sbosell/Hangfire.LightInject
Yes, my goal is to ensure a single instance of IPrincipalProvider across the bounderies of OnPerforming and OnPerformed.
We are using @sbosells extension to inject dependencies into our job implementations. When looking at the source, I see there is some scope handling there as well. I'm not sure how that applies, though.
These projects simplify the integration between Hangfire and your favorite IoC Container. They provide custom implementation of JobActivator class as well as registration extensions that allow you to use unit of work pattern or deterministic disposal in your background jobs. https://www.hangfire.io/extensions.html
Maybe I already have 'per job'-lifetime handling out of the box?
Yeah, I would at least try to use the extension first. It has a few thousands downloads on NuGet too 👍
Did you get any further with this?
Not yet. Been occupied with other things :-)
From: Bernhard Richter [email protected] Sent: Wednesday, May 16, 2018 5:32:11 PM To: seesharper/LightInject Cc: Vegar Vikan; Author Subject: Re: [seesharper/LightInject] Implementing PerJobLifetime() for use with Hangfire (#416)
Did you get any further with this?
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/seesharper/LightInject/issues/416#issuecomment-389562655, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AAuqZYVIspVFTmJtpHbmXuHPqoW4VvAFks5tzEZ6gaJpZM4T7oSo.