Ninject InRequestScope fallback to InThreadScope
Asked Answered
B

3

11

In my MVC3 project I've setup my kernel to Ninject the Entityframework context on a InRequestScope basis, this works perfect. But I have a background runner that does some workflow management.

It fires up a new thread each 5 minutes and I Ninject my dependencies into this thread, If I change the scope to InThreadScope the Dispose method is fired, but If I change it back to InRequestScope the Dispose method won't fire.

Is there a way of fallbacking to InThreadScope if InRequestScope isn't available?

Update: Just got an upvote for this question and why not update it with some additional info. I think that Ninjects way of handling life time is a bit outdated. Other IoC's have child containers were Transient registered objects live during the whole child container and are disposed when the child containers are. This is a much easier way of combining for example Web API with a custom worker like above scenario.

Burin answered 31/10, 2011 at 9:35 Comment(0)
A
14

There's an InScope method, with which you can specify a custom scoping rule.

The implementation of InRequestScope is currently undergoing change from 2.2-2.4.

So go to the source of your version, look at the impl of InRequestScope and InThreadScope and create an amalgam (which you can then slot in as an extension method alongside the other InXXXScope methods.

It'll look (before you extract it into an extension method) something like:

Bind<X>.To<T>().InScope( ctx => ScopeContext.Request(ctx) ?? ScopeContext.Thread(ctx))

The other way is to create two Bindings, with an appropriate constraint such as WhenInjectedInto<T> to customize the scoping based on the contextual binding.

Arlindaarline answered 31/10, 2011 at 10:17 Comment(16)
Thanks for Quick answer, So the only way of testing for InRequestScope is HttpContext.Current != null? I would like to avoid webreferences in my NinjectExtensionmethod if possible... Does Ninject has some helper methdo to determin that InRequstScope is available? Then i could do (Pseudo code) .InScope( () => InRequest() ? StandardScopeCallbacks.Request : StandardScopeCallbacks.Thread). My Ninject bindings reside in the the business assemblie and I would like to keep the Web assembly out of it.Burin
The helper you found will return null if its not in a request, so you dont need to do the conditional bit you're worrying about (hence my ?? bit to let it fall through).Arlindaarline
Next, Ninject shouldnt be worrying about Web either (hence the pieces that pertain to that are being split out too), and InRequestScope should also hook into the WCF OperationContext.Current. These things are being worked on in trunk but the ninject maintainers.Arlindaarline
Regarding a nice way to make it work in your context... Strongly suggest getting manning.com/seemann and read it cover to cover - it doesnt cover anything about Ninject but covers everything about DI architecture. As a quick answer, based on my limited knowledge of your specific constraints and not having done lots of similar work, I'd suggest that the trickery you're doing in trying to alter the default scoping needs to be very near to your composition root - there's no way that the business component should be worrying about myriad ways of hosting it.Arlindaarline
Offcourse StandardScopeCallbacks.Request is just a wrapper for HttpContext.Current :D This is very strange, If I use InScope(() => StandardScopeCallbacks.Request) it behaves different than using .InRequestScope() If i use InRequestScope it calls the dispoise method right after request has ended. With the inscope it doesnt cal the dispose method at allBurin
In other words, the business component needs to ask for a Factory for EF contexts rather than assuming that it's possible to know enough about the (two) root's desires from where it sits. One simple way might be to add Func<EFContext> ctor args, and have the composition root Bind an appropriately managed one there. Being only 7 chapters in and not having read @MarkSeemann's discussion of scoping yet, I'm going to have to hope on your behalf that he's available and willing to step in and provide a link to something salient (I'm sure this question has popped up before)Arlindaarline
I was a bit unclear, the DI code resides in a Project called MyProjectName.Business.Bootstrap, so business and DI is nicely decoupled...Burin
InScope with thread wont die until after the Thread has been GC'd. While you may be able to have an EFContext managed appropriately by ninject, be aware that the fact that Dispose happens at the correct time is due to a Http-specific customization. See the Nate Kohari Cache and Collect article for details. Bottom line for me is that the root of your processing needs to take care of how to create and manage EF contexts correctly. Unfortunately I dont have time to discuss this further. I suggest putting a container-neutral question with a DI tag explaining your question in more details - sorry!Arlindaarline
@Mike Flynn: Dont have any dev environment right here. Find in Files in the source for InRequestScope() and InThreadScope() - it'll be drop-dead simple to combine them, trust me. The only difference V3 brings really brings is that the impl of InRequestScope now lives in Ninject.Web.Common (i.e. InScope hasn't changed and the principle of how you combine them). Also there was no 2.4; semver dictated calling it 3.0. If you're still not able to find it, re-ping and I can look laterArlindaarline
It's as easy as useful! Kudos on you, this saved my ass:DReliance
Hi @DaniilGrudzinskiy, can you post the combination please? That would be awesome!Boudoir
@Boudoir Does stackoverflow.com/a/7954385 help? Are you on v3? What exact things are you trying to compose? If its RequestScope and ThreadScope you're trying to combine, the respective selectors now live in github.com/ninject/Ninject.Web.Common/blob/master/src/… and github.com/ninject/ninject/blob/master/src/Ninject/… and Someone needs to do a PR to pull out a RequestScopeCallbacks.ActiveRequestScopeArlindaarline
Thanks for the answer @RubenBartelink. I added my own answer down below. #7952414Boudoir
@Seregwethrin As mentioned in my May 27 comment, they have moved around over time. There is no longer a single static class that contains them. They are in the cited files - so it depends which one you're loking for.Arlindaarline
So... Would you care to update your answer? I'm having confusions with Ninject, I even asked a related question here: #17919814Pharr
@Pharr In shall do so in due course, but... a) I have to do the PR of which I spoke to add more (and wile doing that, I do associated stuff to make naming more consistent). b) I'm pretty sure there are differences between then Stable and Pre-release versions The bottom line is, this is a low level detail in a file and the answer was correct and useful at the time - I've provided links to it and I'm in the middle of stuff right now so won't be able to get to it today. You BTW have failed to give an relevant detail as to what it is that you can't find.Arlindaarline
B
6

Thanks to Ruben I found a solution, however there sneaked in a little bug there in his pseudo code and since its a monday and Im tired I didnt see it right away :D

StandardScopeCallbacks.Request

Is a delegate and

StandardScopeCallbacks.Request ?? StandardScopeCallbacks.Thread 

will always return the left side since the delegate will never be null.

There are two ways of doing this correctly,

1 ExtensionMethod

public static IBindingWhenInNamedWithOrOnSyntax<T> InRequestFallbackScope<T>(this IBindingWhenInNamedWithOrOnSyntax<T> binding)
{
    Func<IContext, object> fallbackCallback = ctx => StandardScopeCallbacks.Request(ctx) ?? StandardScopeCallbacks.Thread(ctx);
    binding.Binding.ScopeCallback = fallbackCallback;
    return binding;
}

2 Using the InScope method

.InScope(ctx => StandardScopeCallbacks.Request(ctx) ?? StandardScopeCallbacks.Thread(ctx))
Burin answered 31/10, 2011 at 13:27 Comment(1)
Good catch, gobbled into the misleading bit of my answer. And ya gotta love fallbackCallback, so +1. If you wanted to gold plate, you could pull the callback logic out into a helper class a la ninject - this would bring out the fact that the extension method is really the same as InScope(x). (And I think you can then impl the extension as binding.InScope( FallbackScopeCallbacks.RequestOrThread) if you catch my drift)Arlindaarline
B
2

This is what I composed to have the request scope in the first place and the thread as a fallback.

public static class NinjectBindingExtensions
{
    private static bool IsRequestPresent()
    {
        try
        {
            return HttpContext.Current != null;
        }
        catch
        {
            return false;
        }
    }

    private static object GetScope(IContext ctx)
    {
        // note: this is a copy of the private method of Ninject.Web.Common.RequestScopeExtensionMethod#GetScope
        return ctx.Kernel.Components.GetAll<INinjectHttpApplicationPlugin>().Select(c => c.RequestScope).FirstOrDefault(s => s != null);
    }

    public static IBindingWhenInNamedWithOrOnSyntax<T> InRequestFallbackScope<T>(this IBindingWhenInNamedWithOrOnSyntax<T> binding)
    {
        Func<IContext, object> fallbackCallback = ctx => IsRequestPresent() ? GetScope(ctx) : StandardScopeCallbacks.Thread(ctx);
        binding.BindingConfiguration.ScopeCallback = fallbackCallback;
        return binding;
    }
}

If you are in the Thread scenario make sure you call _kernel.Components.Get<ICache>().Clear(Thread.CurrentThread); to trigger the (maybe existing) Dispose() on the objects you loaded in this scope (-> Thread.CurrentThread).

Boudoir answered 29/5, 2013 at 5:26 Comment(4)
I think Anders's second answer is equivalent, handles more cases and is less code (and am using something similar to do a fallback from HttpContext.Current to OperationScope.Current). I don't see what the IsRequestPresent check accomplishes - remember that anything can plugin in to the RequestScope stuff. And if a simple impl is not possible, create a pull request to expose the needed callback that means you dont need to track the internal impl as it changes.Arlindaarline
I created this answer because the StandardScopeCallbacks.Request does not exists anymore. To be honest this code have to work and is not intended to be beauty. I know this is not not the way it should be but for a quick solution it is enough. I'll think about the pull request.Boudoir
and why it is pointless? when a webapp starts up and you try to get the current httpcontext, you will get a juicy exception.Boudoir
Why? When IsRequestPresent() returns false, GetScope(ctx) will return null. FirstOrDefault doesn't throw? i.e., I'd replace the lambda of fallbackCallback with ctx => GetScope(ctx) ??StandardScopeCallbacks.Thread(ctx); (And I think it will all collapse down to return binding.InScope( ctx => GetScope(ctx) ??StandardScopeCallbacks.Thread(ctx))Arlindaarline

© 2022 - 2024 — McMap. All rights reserved.