Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OWIN-compatible implementation for "PerWebRequest" lifestyle #283

Closed
mario-d-s opened this issue Jun 13, 2017 · 73 comments
Closed

OWIN-compatible implementation for "PerWebRequest" lifestyle #283

mario-d-s opened this issue Jun 13, 2017 · 73 comments

Comments

@mario-d-s
Copy link

mario-d-s commented Jun 13, 2017

The PerWebRequest HttpModule has served everyone well for applications hosted in IIS, to enable "Per webrequest" lifestyle.

However, in recent years, OWIN has become more popular and with it, the ability to self-host websites. Windsor's PerWebRequestLifeStyle is incompatible with OWIN's self-hosting mechanism, as HTTP modules only work in the IIS pipeline.

Some OWIN-compatible implementations of this lifestyle have been hacked together, see StackOverflow and GitHub.

However I think it would be best if such implementation was cleaned up, put under test, and integrated with the main project here. It would be a nice bonus if the same configuration syntax could be used as well (i.e. calling LifestylePerWebRequest()). Maybe there could be some magic to make it work under all hosting scenarios.

@jonorossi
Copy link
Member

@mario-d-s completely agree the IIS module isn't useful for new web applications. There has been some work around ASP.NET Core and its built-in container in #120. I'd really like to see those with an interest in this to get together (probably via GitHub) to discuss what needs to happen and ultimately put forward those changes.

We should have a release of Windsor out with .NET Core support very soon, so I'll be great to have this type of support come in a following release.

@ghost
Copy link

ghost commented Jun 13, 2017

@mario-d-s: +1

PerWebRequest tries to implement dependencies like IHttpModule and then call back into the micro kernel using scopes, I think we should be inverting this. BeginScope/EndScope was the start of that work and was meant to supersede the other implementations but it was based on remoting which was not available in netcoreapp via netstandard(CallContexts) during the migration of Windsor. I do believe in netstandard is bringing this back in v2.0.

Once it compiles everywhere, it would be a doddle to implement extensions in that wrap this idea.

@mario-d-s mario-d-s changed the title Provide OWIN-compatible implementation for "PerWebRequest" lifestyle OWIN-compatible implementation for "PerWebRequest" lifestyle Jun 14, 2017
@mario-d-s
Copy link
Author

@jonorossi it's great to see that Windsor and Castle.Core are almost completely ready for .NET Core!
However, from what I've been reading on the web, there are a couple of differences between OWIN in .NET Core and OWIN in full framework. I have 0 experience with Core myself so I can't really comment.

I just hope any compatibility will also be backported to work with OWIN on the full framework.

@fir3pho3nixx According to at least one Microsoft engineer, CallContext should be avoided in scenario's unrelated to remoting. The source of that claim would be behind this broken link but unfortunately there seems to be no way of retrieving that page anymore (archive.org doesn't have it).

@jonorossi
Copy link
Member

@fir3pho3nixx with the port we've left the .NET Framework code for scopes using CallContext (because we are targeting .NET 4.5), however the .NET Standard code will use AsyncLocal.

@jonorossi
Copy link
Member

I just hope any compatibility will also be backported to work with OWIN on the full framework.

@mario-d-s this is where I highly encourage you to get involved in the project, we really do need help and features won't get supported unless someone actually wants them and puts in some work. I hope as a collective updates to the per-webrequest lifestyle can support everyone involved.

@ghost
Copy link

ghost commented Jun 14, 2017

@mario-d-s: I remember reading about this many moons ago, cannot for the life of me remember why though :)

@jonorossi: I did come across the use of AsyncLocal here. I must have missed that bit in the netcore branch when my internet was borked.

Checking out some middleware implementations would be as simple as creating an extension that goes something like this?

app.Use(new Func<AppFunc, AppFunc>(next => (async env =>
{
    myWindsorContainer.BeginScope();
    await next.Invoke(env);
    myWindsorContainer.EndScope()
})));

You could then wrap it in a tidy extension method.

@mario-d-s
Copy link
Author

@jonorossi I would love to get involved, and it's something I will definitely begin to do in the near future when I get around to finally learn Git 😃

@fir3pho3nixx Correct me if I'm wrong, but such middleware would use the default scope (i.e. LifeTimeScoped()). What if you need scopes in other places. Does this support nesting?

@ghost
Copy link

ghost commented Jun 14, 2017

Correct me if I'm wrong, but such middleware would use the default scope (i.e. LifeTimeScoped())

@mario-d-s: I could not find the particular extension LifeTimeScoped but I am pretty sure you mean LifestyleScoped. This is basically a shorthand API extension that will apply LifestyleType.Scoped to the config for that registration(using descriptors) which eventually ends up creating a ScopedLifestyleManager(commit: 4e7716e) in the DefaultKernel. This is significant because when it tries to obtain the current scope for an instance it will return a CallContextLifetimeScope(the class based on remoting I mentioned earlier written by Kozmic himself). The default scope you mention(DefaultLifetimeScope) was only really a thing in the PerWebRequestLifestyleModule and the ThreadScopeAccessor.

What if you need scopes in other places.

@mario-d-s: There is an overload for LifestyleScoped whereby you can specify the type of an accessor. This is a concept which allows you get these things from anywhere but you have to implement an IScopeAccessor. Pretty neat huh?

Does this support nesting?

@mario-d-s: From what I can tell it does.

I think there was significant work at play here to try and clean this up and to make lifestyle management a little easier in general and if Windsor doesn't have it you could "roll your own" in a very easy way, so I am glad you raised this issue. I like the public API for BeginScope/EndScope because of it's simplicity, I am not particularly bound to the implementation of it, I just mentioned the current implementation to add more info to the discussion.

@ghost
Copy link

ghost commented Jun 27, 2017

@mario-d-s - I was thinking we start a series of PR's to start moving the DesktopCLR PerWebRequestLifestyleModuleRegistration stuff out of Windsor and into facilities(or something else). It is still there and supports the FEATURE_SYSTEM_WEB . The reason I say this is there has been a significant effort to migrate this windsor to dotnet core and it presents different challenges for Windsor's public API when it comes to lifestyle management. My vote is that we talk about how we would abstract this out whilst implementing an OWIN compatible version at the same time? What do you think?

@ghost
Copy link

ghost commented Jul 4, 2017

@jonorossi - I would like to start work on this next. Can you tell me what you would like to see happen here?

My recommendation is we start creating satellite assemblies(Windsor Lifestyle NuGet's) for vendor specific stuff and start breaking it out of the MicroKernel. Our versioning can then be kept in step with vendors and not be tied to core implementations of Windsor. Perhaps we want to start leveraging the facility architecture which would be keeping with the spirit of the original design or we could just create them as extension methods(honestly I prefer this approach).

  • Identify how we want to get PerWebRequest out of the MicroKernel.
  • Extend it to OWIN in a separate release.

Look forward to your feedback.

@jonorossi
Copy link
Member

A new assembly sounds like a great way to go, that keeps the dependencies of Castle.Windsor.dll small and keeps things self contained. I see no reason the System.Web lifestyle couldn't also move out of the main assembly, which also helps by removing some conditional compilation. The lifestyle enum should be reserved for the really basic built-in lifestyles, and really its use de-emphasised.

Extension methods are definitely a good approach, take a look at the Castle.Windsor.Lifestyles contrib project that did the same thing: https://github.com/castleproject-deprecated/Castle.Windsor.Lifestyles/blob/master/Castle.Windsor.Lifestyles/LifestyleRegistrationExtensions.cs

@ghost
Copy link

ghost commented Jul 5, 2017

Yes, this is what I was thinking. Can I go ahead and create a new branch called lifestyles?

Also, do we want a separate assembly for each target implementation? eg.

  • Castle.Windsor.Lifestyles.WebRequest
  • Castle.Windsor.Lifestyles.OWIN
  • Castle.Windsor.Lifestyles.NetCore

If you are happy with this as a starting block I can add these new projects and their co-respective NuGet packing requirements into the VS2017 build infrastructure as empty projects for now. I will then commence work on moving PerWebRequest down.

@jonorossi
Copy link
Member

Can I go ahead and create a new branch called lifestyles?

Go for it.

Also, do we want a separate assembly for each target implementation?

That probably makes sense as the package dependencies would be vastly different, I'd probably use these names instead:

  • Castle.Windsor.Lifestyles.WebRequest SystemWeb
  • Castle.Windsor.Lifestyles.OWIN Owin
  • Castle.Windsor.Lifestyles.NetCore AspNetCore (I think I'm wrong on this one, maybe .MSDependencyInjection instead)

Unless they'll be more than just a lifestyle, i.e. the WCF integration has a per channel lifestyle but is obviously named a facility as the main part is the Windsor facility.

I will then commence work on moving PerWebRequest down.

Feel free to do what you think works best, but I'd have thought it would be best to leave that one until you've got the others the way you want them.

ghost pushed a commit that referenced this issue Jul 5, 2017
@ghost
Copy link

ghost commented Jul 18, 2017

I have done some more investigation into this. My interest here was how we future proof Castle Windsor and create an implementation that services both dotnet core and desktop clr using OWIN because it is already available on both.

All paths appear to be leading to Rome, or at least in this case Microsoft.Extensions.DependencyInjection. I did know that this could be used on netcore but did not for one second think about using it in a desktop clr scenario. A bit of a duh moment I must admit.

My example is only using the Microsoft.Extensions.DependencyInjection nuget sans any Windsor container implementation. The reason I have done this is because I want to understand how the dependencies and the consumer API works before we dive into Microsoft.Extensions.DependencyInjection.Abstractions, which is where the meat and potatoes of the cross runtime implementation might need to be implemented if we all agree on my suggested approach.

I submit my cobbled together OWIN startup file for net452. Where you can see the same old divergent IDependencyResolver implementation for MVC here and WebApi here. I then went ahead and registered my own service called IAnyService. Following that I registered an MVC and a WebApi controller.

This got me round to thinking about how we still use Windsor's registration API but still get the buttery goodness of integration using MVC and WebAPI in a DesktopClr/NetCore scenario. The answer appears to be by re-implementing Microsoft.Extensions.DependencyInjection.Abstractions. I then had a look at the competition and it appears as though they have beat us to the punch. This could be viewed as a good thing because they have solved the problem for us. We just need a Windsor flavour.

@mario-d-s: Can you weigh in on your intended usage of Castle.Windsor for this issue? How you would like this API to work? I am thinking this kind of extension is perfectly inline with the spirit of registering OWIN middleware(Use* convention). My suggestion here is we have a UseCastleWindsor extension. I would also like to know whether you have any apprehension netstandard being pulled in as a dependency? Assuming you are using this in a DesktopClr scenario, this can slow down CI builds. Are there also any other Service Location concerns you think we are missing whilst registering middleware(seen some stackoverflow's on this)?

@jonorossi: I think the test project for this should boot up my example as a OWIN self host, use the windsor registration API for IAnyService which is ultimately resolved in MVC and WebApi using a HttpClient. Would be super cool to get a dotnet core version of this going too. It is supported.

Let me know what you guys think.

@mario-d-s
Copy link
Author

@fir3pho3nixx I think you're pretty much on track for this. I would definitely refer to the AutoFac stuff as an inspiration to get started.

I don't have any additional concerns for the moment. I can't really comment on your question about the netstandard dependency, I assume this is .NET Core stuff which, I've already mentioned, I have no experience with.

By the way, I've already learned a bit of Git in the last couple of weeks. Perhaps I'll be able to contribute myself in a meaningful way here soon, I'll keep you posted.

@jonorossi
Copy link
Member

I agree with @mario-d-s, it sounds like you are on the right track.

I think the test project for this should boot up my example as a OWIN self host, use the windsor registration API for IAnyService which is ultimately resolved in MVC and WebApi using a HttpClient. Would be super cool to get a dotnet core version of this going too. It is supported.

I've got no objection to adding some integration tests, it might be worthwhile having a unit test project just for the MSDependencyInjection project though so anyone working on core Windsor doesn't have to worry about all of that and its dependencies.

@ghost
Copy link

ghost commented Jul 24, 2017

@mario-d-s - I am sorry if this is not relevant to your issue, which I have already solved BTW. I do have an interest in getting dotnet core going for the Microsoft.Extensions.DependencyInjection stuff. If you feel this is not relevant here I am happy to take a slap on the hand and raise a new issue. I just feel that the implementation is incredibly naive and vulnerable to to memory leaks when it comes to scoping. I submit further detail to @jonorossi below.

@jonorossi - Can you weigh in on this(without pulling your punches)? Microsoft.Extensions.DependencyInjection makes some bold assumptions about naked parent level transients that are resolved in a scoped lifestyle.

This test right here assumes that scoped lifestyles will auto-magically dispose transients without being housed in a parent dependency that is marked as scoped. I got this test from here and copied it into the solution for debugging purposes. This to me is quite simply bad design. It is non deterministic and opens up the gates of hell for prematurely disposed transients or alternatively could create further memory leaks if the developer changing this code makes an assumption that his/her transient object is running within a scope. There simply is no type safety around this design.

I have also been reviewing custom lifestyles over here and was wondering if this was truly the answer. It feels like a sledge hammer.

My expectation for this test would be something like this where by you could find usages of OptionsContainer and immediately know it is scoped. Better or no?

@ghost
Copy link

ghost commented Jul 24, 2017

I am tempted to wrap transients using Castle Core mixins with fallback logic. Not sure if this would work, but I think I am going to start looking at this tomorrow night.

@jonorossi
Copy link
Member

This test right here assumes that scoped lifestyles will auto-magically dispose transients without being housed in a parent dependency that is marked as scoped. I got this test from here and copied it into the solution for debugging purposes. This to me is quite simply bad design. It is non deterministic and opens up the gates of hell for prematurely disposed transients or alternatively could create further memory leaks if the developer changing this code makes an assumption that his/her transient object is running within a scope. There simply is no type safety around this design.

How does Windsor behave today resolving and disposing transients as the root inside a scope, does the burden cause it to get released on scope disposal?

@jonorossi
Copy link
Member

I am tempted to wrap transients using Castle Core mixins with fallback logic. Not sure if this would work, but I think I am going to start looking at this tomorrow night.

The performance penalty would be quite high especially for all transients. How would this work anyway, how would you know the user is finished with the object, I can't see how you'd do reference counting.

The transient really should be released as per Windsor's long standing guidelines.

@ghost
Copy link

ghost commented Jul 25, 2017

How does Windsor behave today resolving and disposing transients as the root inside a scope, does the burden cause it to get released on scope disposal?

No it does not, and I don't believe it should. If it is resolved as a child dependency where the parent has a scope it does. I think this is correct behaviour.

The performance penalty would be quite high especially for all transients. How would this work anyway, how would you know the user is finished with the object, I can't see how you'd do reference counting.

Yeah, thinking about it I don't think it would be easy and anything as best would be a lifecycle hack. I will keep digging but I might raise an issue to see what the authors say. Lifestyle side effects like this can lead to all sorts of bad things.

@ghost
Copy link

ghost commented Jul 25, 2017

Yeah not sure how to proceed, raised this issue. I think this needs fixing. Parent/Child scoping got violated in Windsor when I implemented this. Let's see what happens next.

@ghost
Copy link

ghost commented Jul 25, 2017

The duplicate registrations of RC1ForwardingActivator are also doing my head in.

image

@ghost
Copy link

ghost commented Jul 25, 2017

Getting tests going was a little tricky also.

@jonorossi
Copy link
Member

Looking at that test code again it feels a little like Windsor's child containers since the resolution of all components is happening against the scope, but then the singleton doesn't get disposed on scope disposal so it doesn't really match. When the code references scope.ServiceProvider is that the exact same instance as the provider variable or something special for that specific scope? Just wondering if nested scopes are a thing. If they are the same, the code would read clearer with just provider.GetService.

If the transient gets disposed at the end of the scope then why shouldn't the singleton also get disposed? Is there a design document/specification for how the MSDependencyInjection abstractions should be implemented and how the lifecycle of components behave with scopes?

ServiceCollectionServiceExtensions.AddSingleton<IFakeSingletonService, FakeService>(collection);
ServiceCollectionServiceExtensions.AddScoped<IFakeScopedService, FakeService>(collection);
ServiceCollectionServiceExtensions.AddTransient<IFakeService, FakeService>(collection);
...

using (var scope = ServiceProviderServiceExtensions.CreateScope(provider))
{
	disposableService = (FakeService) scope.ServiceProvider.GetService<IFakeScopedService>();
	transient1 = (FakeService) scope.ServiceProvider.GetService<IFakeService>();
	transient2 = (FakeService) scope.ServiceProvider.GetService<IFakeService>();
	singleton = (FakeService) scope.ServiceProvider.GetService<IFakeSingletonService>();

	Assert.False(disposableService.Disposed);
	Assert.False(transient1.Disposed);
	Assert.False(transient2.Disposed);
	Assert.False(singleton.Disposed);
}

Assert.True(disposableService.Disposed);
Assert.True(transient1.Disposed);
Assert.True(transient2.Disposed);
Assert.False(singleton.Disposed);

@ghost
Copy link

ghost commented Jul 26, 2017

Looking at that test code again it feels a little like Windsor's child containers since the resolution of all components is happening against the scope, but then the singleton doesn't get disposed on scope disposal so it doesn't really match.

We appear to have matching behaviour for singletons. I am thinking the reasoning behind this not getting disposed is because although it is a memory leak it is a small one because of the long lived lifestyle.

When the code references scope.ServiceProvider is that the exact same instance as the provider variable or something special for that specific scope? Just wondering if nested scopes are a thing. If they are the same, the code would read clearer with just provider.GetService

In the case of their recommended test (which uses xunit) I have debugged the IServiceProviderFactory which would suggest that the instances could be different for scope.ServiceProvider but it is the same instance. So for now I am in complete agreement with you.

If the transient gets disposed at the end of the scope then why shouldn't the singleton also get disposed?

They ask you not to used scoped dependencies in a singleton in the docs. So I guess this is just something the user has to remember. Then again thinking about it, if you use a transient in a singleton according this test it will get disposed but the singleton will live on ....

Is there a design document/specification for how the MSDependencyInjection abstractions should be implemented and how the lifecycle of components behave with scopes?

This test suite is what they came back with from GitHub. The docs online point you to an autofac implementation. It is a long old read.

@ghost
Copy link

ghost commented Jul 26, 2017

They just got back to me, BeginScope() = new WindsorContainer(). So will go ahead and try this.

@ghost
Copy link

ghost commented Jul 26, 2017

Right, after blasting @davidfowl with WTF's he pointed me to aspnet/Mvc#5403.

Apparently there is an implementation that uses an outside-in approach for containers here by bastardising scopes using OWIN middleware. I am definitely not saying this is the answer, it needs to be validated but luckily we are not the only ones. I am going to drop this business of trying to write my own abstraction implementation and join this discussion. I just need time to absorb what is going on here. I resent the fact that registration is fault tolerant, I also dont like the fact that service resolution is done from scopes with overriding lifestyles when it comes to disposables. This is a radical departure from windsor's design. Scopes are something else entirely(they dont nest). Will keep this issue updated.

@ghost
Copy link

ghost commented Oct 9, 2017

Aside from that, I guess PerWebRequestLifeStyle had another functionality: scope other services (not controllers) to web requests implicitly. I don't believe we use that anywhere in our projects but I don't see how the facility you wrote supports that?

Yes, we moved PerWebRequest to it's own facility SystemWeb. You implicitly get PerWebRequest using the LifeStyleScoped method on the facility. I thought this was what we were saying in this discussion. Feel free to tell me if you thought otherwise.

@ghost
Copy link

ghost commented Oct 9, 2017

I'm not criticizing here by the way, I just feel like I'm missing something.

Nope not at all, I really value your feedback :)

@mario-d-s
Copy link
Author

mario-d-s commented Oct 9, 2017

Transients would be scoped to the lifestyle of the request based on the resolve/release policy. Transients consumed multiple times as dependents of controllers though would behave as expected and then be tracked as burdens with decomission concerns. Do you believe this is incorrect?

Technically that is correct, but since controllers should be the composition root (comparable to Program.Main() in a console app) I believe it's terrible design to have them as dependencies in other components. Still, you are right on that part and it would be the main difference compared to the transient lifestyle.

Oops, total misinterpretation there. If I understand correctly, using the facility, all transients would instead become per-web-request? But what if you really needed a transient, how can you still register it?

Yes, we moved PerWebRequest to it's own facility SystemWeb. You implicitly get PerWebRequest using the LifeStyleScoped method on the facility. I thought this was what we were saying in this discussion. Feel free to tell me if you thought otherwise.

That is indeed what the discussion was about. However, I thought LifeStyleScoped works when you are using Container.BeginScope() calls. Unless you have a custom scope accessor, but I can't find that in your docs or commits. With the original PerWebRequest lifestyle, one did not need to do BeginScope() or similar calls, it was fully implicit.

@ghost
Copy link

ghost commented Oct 9, 2017

Oops, total misinterpretation there. If I understand correctly, using the facility, all transients would instead become per-web-request? But what if you really needed a transient, how can you still register it?

You still have transients, it just they behave differently at the controller level. You have to remember that they will get resolved and released using the activator which only lasts as long as the request. If your dependency graph went 3 levels deep and the same transient service was consumed inside of that twice I would expect them to be 2 completely different instances.

** Edited **

In fact I think it would be a good idea to add another test that proves this. 👍

@ghost
Copy link

ghost commented Oct 9, 2017

However, I thought LifeStyleScoped works when you are using Container.BeginScope() calls. Unless you have a custom scope accessor, but I can't find that in your docs or commits

You can see the BeginScope/EndScope logic here.

For self host it is here

@ghost
Copy link

ghost commented Oct 9, 2017

With the original PerWebRequest lifestyle, one did not need to do BeginScope() or similar calls, it was fully implicit.

You are effectively getting this if you register your controllers as either transient or scoped. The difference is that with scope's you need to opt in using WithScopedLifestyles. You can find this in the docs over here

@ghost
Copy link

ghost commented Oct 9, 2017

PS: Thanks for this, I will also update the docs to describe the lifestyles better. If there is anything you would like to ask or perhaps is not clear let me know and I will make some changes where ever I can to make the UX for this a little easier.

@mario-d-s
Copy link
Author

mario-d-s commented Oct 9, 2017

If your dependency graph went 3 levels deep and the same transient service was consumed inside of that twice I would expect them to be 2 completely different instances.

From the viewpoint of resolving components, that totally makes sense. Of course, dependencies of a controller will be per-web-request implicitly (the same way transient dependencies of a singleton are effectively singleton in that context). But in the following situation:

  • Controller A depends on services M and N
  • M and N depend on X and Y
  • M, N are transient
  • X is per web request, Y is transient

How do you register them so that M and N both get the same instance of X per request, but each get a different instance of Y per request?

I don't see how your proposed registration API makes that clear, since you are registering everything as transient?

You are effectively getting this if you register your controllers as either transient or scoped. The difference is that with scope's you need to opt in using WithScopedLifestyles.

I guess I'm confused over how scopes work in Windsor, I've never really used them either. But I know the docs and the docs say basically:

  • register as LifestyleScoped() and use Container.BeginScope() + scope.Dispose()
  • register as LifestyleScoped<CustomScopeAccessor>().

AFAIK you haven't implemented the CustomScopeAccessor bit for the per-web-request lifestyle here?

@ghost
Copy link

ghost commented Oct 9, 2017

How do you register them so that M and N both get the same instance of X per request, but each get a different instance of Y per request?

Make X scoped.

@ghost
Copy link

ghost commented Oct 9, 2017

I don't see how your proposed registration API makes that clear, since you are registering everything as transient?

Should I update the docs to Scoped? Maybe add an example and test for what you are mentioning above?

@ghost
Copy link

ghost commented Oct 9, 2017

AFAIK you haven't implemented the CustomScopeAccessor bit for the per-web-request lifestyle here?

Don't need the scope accessor. It falls back on to CallContext's. I did need it for SystemWeb facility though.

@ghost
Copy link

ghost commented Oct 9, 2017

To make scopes a little clearer, you use a scope accessor when you want to tie the scope to some external thing. In the case of SystemWeb we use a HttpModule for this which is tied to EndRequests for scope disposal. So it make sense to use an accessor.

In SelfHost we are falling back on to callcontext, which shares the scope for the current executing thread in a fancy way without carrying the same problems as using a ThreadStatic.

In SelfHost the scope is managed in an instance of an IDepdencyScope, so no need for a scope accessor.

Hope this makes sense.

@ghost
Copy link

ghost commented Oct 9, 2017

@mario-d-s - I am just making sure we are covering the bases here, please refer to this reply to @masterpoi in this discussion thread where I clarified how scopes would work.

To summarise this was my internal view:

  • DesktopClr (net452)
    • WebHost(IIS)
      • AspNet.Mvc: (PerWebRequest becomes LifeStyleScoped -> CallContextLifetimeScope -> Remoting)
      • AspNet.WebApi: (PerWebRequest becomes LifeStyleScoped -> CallContextLifetimeScope -> Remoting)
    • SelfHost
      • AspNet.WebApi: (LifeStyleScoped -> CallContextLifetimeScope -> Remoting)
  • NetCore (netcoreapp1.1)
    • Kestrel/WebListener
      • AspNetCore: (LifeStyleScoped -> CallContextLifetimeScope -> AsyncLocal)

@masterpoi - A review from you is also most welcome.

@mario-d-s
Copy link
Author

@fir3pho3nixx I've been digging into the code in Windsor to find how this whole idea of scopes works, and it's finally beginning to click!

Can you confirm I'm getting it please:

  • In Windsor, Scoped components cannot be resolved without beginning a scope using e.g. Container.BeginScope(), otherwise you get an exception
  • Your facility, if configured using WithScopedLifestyles(), begins a scope when a controller is resolved and ends it when a controller is released. This should happen only once per request, right?
  • All services, including controllers, registered as transient are still transient, but you can make them scoped as a replacement of per-web-request. That only works if you have called WithScopedLifestyles(), otherwise you'll get exceptions that no scope is available.
  • Controllers, although transient, are only released and resolved per-request by your AspNetWebApiControllerActivator, effectively making their lifestyle per-web-request. Which does make total sense of course.

If my reasonig is right, my only issue is that as a registration API, LifestylePerWebRequest() was really obvious anc clear what the lifestyle was going to be. A user of Windsor did not need to care how it worked under the hood. By now making the lifestyle scoped, you have to remember that a scope is being created and that it is really a per-web-request scope.

If possible, and if you agree, I would suggest you keep the LifestyleScoped() but instead use a custom scope accessor. I guess all you'd need to do is move a little bit of code from your facility to a scope accessor.

The registration API can then be LifestyleScoped<WebRequestScopeAccessor>() or similar, and you could even have another method LifestylePerWebRequest() that just calls the other one. Maybe it's a way to avoid breaking changes in the public API around this?

@mario-d-s
Copy link
Author

mario-d-s commented Oct 10, 2017

There is one extra improvement I'd like to see, or at least a decision I'd like to understand better: you've chosen to still use the CallContext to store scopes into. I'm not super clear on how this works exactly, but is there a reason you can't bind it to some context that is closer to the request? I see you're already using it to store the scope, so what's the difference with the scope binding itself to the context?

@ghost
Copy link

ghost commented Oct 10, 2017

In Windsor, Scoped components cannot be resolved without beginning a scope using e.g. Container.BeginScope(), otherwise you get an exception

Correct

Your facility, if configured using WithScopedLifestyles(), begins a scope when a controller is resolved and ends it when a controller is released. This should happen only once per request, right?

Correct again

All services, including controllers, registered as transient are still transient, but you can make them scoped as a replacement of per-web-request. That only works if you have called WithScopedLifestyles(), otherwise you'll get exceptions that no scope is available.

In a self host this is done for you but in the web host scenario you definitely would have to opt in. In fact I actually try and wrap the exception in the facility here to give you a more relevant self help message.

Controllers, although transient, are only released and resolved per-request by your AspNetWebApiControllerActivator, effectively making their lifestyle per-web-request. Which does make total sense of course.

Correct

If my reasonig is right, my only issue is that as a registration API, LifestylePerWebRequest() was really obvious anc clear what the lifestyle was going to be. A user of Windsor did not need to care how it worked under the hood. By now making the lifestyle scoped, you have to remember that a scope is being created and that it is really a per-web-request scope.

A scope for me is the more general use case with some really extensible API's for how they are accessed. I also thought they had been around for a while. If you check the first commit for the ScopedLifestyleManager you will notice it goes all the way back 2011 which was over 5 years ago. I am also not sure I follow you when you say people will just have to remember. I my hope is that the documentation is clearly stating this, which I pointed out with a link earlier :)

If possible, and if you agree, I would suggest you keep the LifestyleScoped() but instead use a custom scope accessor. I guess all you'd need to do is move a little bit of code from your facility to a scope accessor.

Why the scope accessor here? You can already do this with vanilla Windsor if you want to manage your own activator/dependency resolver. This facility is trying to help manage that for you, so you dont have to spent time googling the right way to achieve that. Also what other kind of scoped access would people want to support in an OWIN scenario? Are you perhaps thinking along the lines of middleware?

The registration API can then be LifestyleScoped() or similar, and you could even have another method LifestylePerWebRequest() that just calls the other one. Maybe it's a way to avoid breaking changes in the public API around this?

2 things here:

  • I like you're suggestion of bringing back PerWebRequest just not sure whether a namesake for familiarity sake to obscure what is really going on is a good idea. I will defer to @jonorossi on this approach. If you really want PerWebRequest it is very much alive and well(as it ever was) in the SystemWeb facility.

  • It would still be a breaking change because the namespaces would be different. That is why we are potentially targeting V5 for this stuff.

@ghost
Copy link

ghost commented Oct 10, 2017

There is one extra improvement I'd like to see, or at least a decision I'd like to understand better: you've chosen to still use the CallContext to store scopes into. I'm not super clear on how this works exactly, but is there a reason you can't bind it to some context that is closer to the request?

This was designed in 2011 by @kkozmic but here is a link to CallContext.

@ghost
Copy link

ghost commented Oct 10, 2017

@mario-d-s - Your feedback has been great, thank you. Please see commit's where I have taken your input on board.

My only criticism for this issue is that you did not follow the discussion fully and I had to explain how scopes worked again. In future it would be great if you limit the conscious streaming in terms of questions/challenges, and commit to comments like this one where you pledged to commit code once you learned git.

Please remember this is an open source project. If you raise issues, you should be prepared to get involved and ultimately try commit code for a solution.

Again I thank you for you're feedback but @jonorossi and I will take it from here.

@mario-d-s
Copy link
Author

@fir3pho3nixx I understand, I realize this was not the best place to seek further understanding about the topics I've asked about.

I still genuinely intend to get involved more, but this particular issue was too complicated for me either way. I've followed the discussions from the sideline though. I'm glad I was at least of some help.

The update you made to the docs on your facility makes it much clearer by the way.

Thanks for all the help!

@masterpoi
Copy link

@fir3pho3nixx I still think your proposed solutions is great.

I took a quick look at the docs you wrote. Look good.

Just a quick thought i had, typically i'd not make my controllers scoped, but the database session / uow only. Making the controllers scoped might be a bit confusing as an example.

In the MVC case, the AddControllerAssembly method, i assume would scan the assembly for controllers? (And that WithLifestyleScopedPerWebRequest has nothing to do with the scanning?) Maybe name it ScanControllerAssembly instead and put WithLifestyleScopedPerWebRequest in front ?

@ghost
Copy link

ghost commented Oct 12, 2017

Just a quick thought i had, typically i'd not make my controllers scoped, but the database session / uow only. Making the controllers scoped might be a bit confusing as an example.

Not all. Your UoW pattern hopefully breaks things down to a factory type which yields instance based resources which are disposable at some point. Could you post a Gist link to your high level UoW pattern? Maybe something like this(interfaces only :))?

In the MVC case, the AddControllerAssembly method, i assume would scan the assembly for controllers?

No. I intentionally avoided ComponentRegistration scenario's. I would really love it if people started using IWindsorInstallers with conventional registration properly. The facilities should become the application of scopes.

And that WithLifestyleScopedPerWebRequest has nothing to do with the scanning?

Yes. For now.

Maybe name it ScanControllerAssembly instead and put WithLifestyleScopedPerWebRequest in front ?

LifestyleScopedControllerSourceAssembly. New name welcome.

I guess this is where we can work some magic, to expose new ComponentRegistration extensions and see if we can do something to sweeten the registration part. It would make these facilities very handy.

Thoughts/Gist?

@jonorossi
Copy link
Member

@fir3pho3nixx I've reviewed #351.

@masterpoi
Copy link

Not all. Your UoW pattern hopefully breaks things down to a factory type which yields instance based resources which are disposable at some point. Could you post a Gist link to your high level UoW pattern? Maybe something like this(interfaces only :))?

Yes, typically a factory method that provides a uow something. I.e. ravendb

var ds = new DocumentStore() { /* settings */ };
container.Register(Component.For<IDocumentSession>().LifestyleScoped().UsingFactoryMethod(() => ds.OpenSession()));

In my mind everything that doesn't keep state should either be transient or maybe singleton for perf. Since controllers classes typically don't keep state, i tend to make them transient. (You typically won't need multiple controllers per request, so making them transient means in practice they have the same lifetime as making them scoped). The uow / db session may be shared between multiple services that the controller uses, so that really needs to be scoped to the request.

As i said, it might be confusing to some, but it might only be confusing to me ;)

In the MVC case, the AddControllerAssembly method, i assume would scan the assembly for controllers?

No. I intentionally avoided ComponentRegistration scenario's. I would really love it if people started using IWindsorInstallers with conventional registration properly. The facilities should become the application of scopes.

Agreed, but then I guess i'm missing something. What does AddControllerAssembly do?

@ghost
Copy link

ghost commented Oct 14, 2017

AddControllerAssembly merely supplies assembly to source types from when creating controllers for MVC. It has been converted to a fluent interface, you can now have many.

Not sure about the UoW scenario, will have to get back to you.

@ghost
Copy link

ghost commented Jan 18, 2018

@masterpoi - I have done more digging here.

Your example:

var ds = new DocumentStore() { /* settings */ };
container.Register(Component.For<IDocumentSession>().LifestyleScoped().UsingFactoryMethod(() => ds.OpenSession()));

I believe we can support this but please let's explore this a bit more.

-> means has a dependency on.

If you are using something like ControllerType -> ServiceType -> RepositoryType -> IDocumentSession then there are a few things to consider about lifestyles here when it comes to how things are resolved.

You mentioned either transient or singleton lifestyle would be good:

In my mind everything that doesn't keep state should either be transient or maybe singleton for perf

I agree with you completely. If the paradigm is a layered to hell architecture like in some of the awful implementations I have seen round London recently then things change ever so subtly depending on where they are resolved.

Transient. This one is tricky. You would always expect a new instance. However, if you mark a controller as transient and the facility only instantiates 1 when it engages the ASP.NET request pipeline via ControllerFactory or whatever the abstraction is. In this case you essentially have a "per web request" component right? Transient by the very definition is PerWebRequest scoped because of where it is consumed.

It get's more complicated when you move over to RepositoryType's. N number of services resolve into the controller they consume repositories, but unfortunately services can consume other services and repositories again and this is where the difference comes in.

Imagine this example:

Posts have comments, facebook right? You cannot add a comment without knowing what the original the post id was.

CommentController -> CommentService -> CommentRepository(instanceOf(1))
                                                                  -> PostService -> CommentRepository(instanceOf(2))

The net effect of transient being marked on the CommentController vs the CommentRepository can yield different behaviours when it comes to state purely because of the way it is consumed. If CommentRepository was an in memory instance based repository it would almost always be wrong in this scenario.

What we are trying to say here, is that scoped is a thing(it combats these idiosyncrasies). We are also saying you need to consider the how it is consumed. Scoped in this case would fix the state problem of transients that hold N+1 upstream dependencies minus framework consumers because we cant control that(singletons should violate this law).

Let's consider disposables for transients given the above. This where it can get a little crazy especially if you are using TypedFactories that them self re-engage the resolution mechanism of Windsor by using the (k) -> k.Resolve<> feature pattern. Burdens (tracking of disposables from what I can tell) simply wont allow this in their current state because they don't care about release policies or late bound factory components.

What this means in plain English is that typed factories will hurt you bad, when it comes to what you "think is possible" and then you drop a "typed factory" in and it does not work(ref: #373).

Moving on, so "unit of work". Please explain what your context is? I would not use this word lightly. In fact I find it quite offensive given the implementations I have seen in the wild because most people do not get what this really means. It is one of the most mis-interpreted patterns I have found across the realms of insurance, banking and e-commerce. I think it means transactional state with ACID and managing resources in a federated service oriented architecture. I would love to hear your experiences on this.

That said, I have investigated using AutoFac with Instance per matching lifetime scope. I honestly don't associate this with "unit of work" because it quite simply does not match the problem domain we are trying to solve in windsor. The taxonomy is completely wrong for a container. We can offer named scopes, similar such. When it is resolved that name needs to be supplied to the resolve mechanism. This would create named scoped slices that could conform to lifestyle variants that would eventually support a unit of work(which we should have to care about in Windsor). The problem I have with all of this, it is supported today in Windsor. You just need to structure you code correctly.

Please share what you are trying to do and with further discussion hopefully we can figure this out :)

@ghost
Copy link

ghost commented Jan 18, 2018

@masterpoi - thanks for you input so far, it is a great help.

@masterpoi
Copy link

I believe we can support this but please let's explore this a bit more.

What do you mean with support? I'm sorry if i let you believe otherwise, but i don't have any fundamental issues with how things are now. And again, the solution you are working on looks great.

The rest of my remarks can be summarized as "Let the architect choose the right lifestyles depending on his architecture." ;)

Transient. This one is tricky. You would always expect a new instance. However, if you mark a controller as transient and the facility only instantiates 1 when it engages the ASP.NET request pipeline via ControllerFactory or whatever the abstraction is. In this case you essentially have a "per web request" component right? Transient by the very definition is PerWebRequest scoped because of where it is consumed.

Yes exactly.

The net effect of transient being marked on the CommentController vs the CommentRepository can yield different behaviours when it comes to state purely because of the way it is consumed. If CommentRepository was an in memory instance based repository it would almost always be wrong in this scenario.

This is true if CommentRepository was also registered as Transient. But if it needs to keep state I would register it scoped. In general Repositories would need to be Scoped because they depend on the UOW.

I don't see how registering the controller transient has any impact here. If the controller would somehow need to be resolved again in the chain i think there is something really wrong in the design. Given that the controller is the interface between the framework (MVC) and the "user code", it should be managed by the framework. And hence, as said before, in this case Transient implies PerWebRequest.
And the controller being transient does not imply it's dependencies should be.

What we are trying to say here, is that scoped is a thing(it combats these idiosyncrasies). We are also saying you need to consider the how it is consumed. Scoped in this case would fix the state problem of transients that hold N+1 upstream dependencies minus framework consumers because we cant control that(singletons should violate this law).

Agreed that scoped can fix this. But I really think you should consider every service in your dependency chain and give it the appropriate lifestyle when registering. Making the lifestyle of the dependencies hang of the controller lifestyle makes it harder to reason about them in those cases where you need the dependencies out of the scope of the controller. Making higher up dependencies transient gives flexibility for lower dependencies to be something more strict like scoped or singleton. The reverse is not true. If the controller is scoped, every dependency is de facto also scoped.

So yes, making the controllers scoped fixes the issue in your example. But i feel like it is better to link the lifestyle to the specific component demanding it.

This where it can get a little crazy especially if you are using TypedFactories that them self re-engage the resolution mechanism of Windsor by using the (k) -> k.Resolve<> feature pattern.

Do you mean making Controllers transient would make it hard/impossible for Windsor to known when to dispose it's IDisposable dependencies? Yes, i can see this might become in issue in some designs, and in that case, the architect/designer has the choice to make the controllers Scoped for this reason. But i really consider that to be his job.

I think it means transactional state with ACID and managing resources in a federated service oriented architecture. I would love to hear your experiences on this.

In this case i use uow to refer to the transaction boundary around database operations in a single process (i.e. inside a single (micro)service). I.e. the DbContext in EF, the DocumentSession in RavenDb / Marten, NServiceBus messagehandler.

That said, I have investigated using AutoFac with Instance per matching lifetime scope. I honestly don't associate this with "unit of work" because it quite simply does not match the problem domain we are trying to solve in windsor. The taxonomy is completely wrong for a container. We can offer named scopes, similar such. When it is resolved that name needs to be supplied to the resolve mechanism. This would create named scoped slices that could conform to lifestyle variants that would eventually support a unit of work(which we should have to care about in Windsor). The problem I have with all of this, it is supported today in Windsor. You just need to structure you code correctly.

Agreed.

@ghost
Copy link

ghost commented Jan 22, 2018

What do you mean with support? I'm sorry if i let you believe otherwise, but i don't have any fundamental issues with how things are now. And again, the solution you are working on looks great.

Add some public API's to solve problems we think might be an issue. Don't ever say sorry, I do enough of that on project already :)

The rest of my remarks can be summarized as "Let the architect choose the right lifestyles depending on his architecture." ;)

Read this. Feel free to rant but don't use names.

So yes, making the controllers scoped fixes the issue in your example. But i feel like it is better to link the lifestyle to the specific component demanding it.

Interesting, can you elaborate on this. We have several problems with typed factories in this space at the moment. There is an issue where some big brains are going to duke this out. I am going to back off and see what happens. Not linking for cleanliness and separation of problems. Please give me your account.

Do you mean making Controllers transient would make it hard/impossible for Windsor to known when to dispose it's IDisposable dependencies?

No, deeper, chained resolves via vanilla windsor kernel that dependends on typed factories(using LateBoundComponents) with disposables at the end are a pain in the arse. It only crops up with N+2 resolution chains where a typed factory is in the middle. Hopefully we will have an answer soon.

It is not often I get a reflective opinion that leans towards my own. Thanks for your input. We are nearing the home run on this issue. Closing this out as I have prepped the final PR. Release is imminent.

@ghost ghost closed this as completed Jan 22, 2018
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants