Integration for Microsoft.Extensions.DependencyInjection (ASP.NET Core) #120

Closed
cwe1ss opened this Issue Feb 6, 2016 · 93 comments

Comments

Projects
None yet
@cwe1ss

cwe1ss commented Feb 6, 2016

Hi,
I created an integration for Microsoft.Extensions.DependencyInjection.

You can find the code here: https://gist.github.com/cwe1ss/050a531e2711f5b62ab0

The code is based on the following links:

I adjusted the code for RC1 and fixed some issues:

  • Components were added with .OnlyNewServices() which resulted in services that are supposed to exist multiple times not being added
  • GetService<IEnumerable>() was not resolved properly. I had to add special handling for IEnumerable<>
  • ASP.NET Core doesn't expect GetService() to return an exception if the component wasn't registered. (they use it for optional dependencies). For this reason, I added a check to Kernel.HasComponent()

I'm not sure why the call to BeginScope() in Startup.cs is necessary - if it's missing, the first request fails. Seems like ASP.NET Core doesn't start the scope for the first request or some Scoped service is requested before it does.

best regards!

@cwe1ss

This comment has been minimized.

Show comment
Hide comment
@cwe1ss

cwe1ss Mar 11, 2016

I moved the code into a repository. you can find it here.

cwe1ss commented Mar 11, 2016

I moved the code into a repository. you can find it here.

@jonorossi

This comment has been minimized.

Show comment
Hide comment
@jonorossi

jonorossi Mar 11, 2016

Member

@cwe1ss sorry for not replying to your original post, I've had some personal things going on over the last couple of months which have really pulled me away from Castle.

Firstly thanks for building the integration. Unfortunately I've done zero with ASP.NET Core and the new DI framework built into it so can't really comment as to the problems you've encountered.

I assume you'd like Windsor to ship with this integration? You pasted links to code and problems, followed by "best regards", unfortunately it isn't going to get into Windsor without someone contributing the work. Your gist doesn't have a license, while your repo is licensed under the MIT license, contributions to Windsor must be licensed under the Apache 2 license.

At this point in the lifecycle of ASP.NET/DNX/CLI/etc I think it is probably best if you maintain the integration until things settle down and stabilise. My focus at the moment is on Castle Core and getting it ported to .NET Core. Windsor will obviously come next, but at the moment the build system of Windsor is completely incompatible with project.json so our learnings from Castle Core will flow into fixing that up too along with porting to .NET Core so we can accept this integration without it being just for the .NET Framework. Thoughts, opinions, feedback?

Member

jonorossi commented Mar 11, 2016

@cwe1ss sorry for not replying to your original post, I've had some personal things going on over the last couple of months which have really pulled me away from Castle.

Firstly thanks for building the integration. Unfortunately I've done zero with ASP.NET Core and the new DI framework built into it so can't really comment as to the problems you've encountered.

I assume you'd like Windsor to ship with this integration? You pasted links to code and problems, followed by "best regards", unfortunately it isn't going to get into Windsor without someone contributing the work. Your gist doesn't have a license, while your repo is licensed under the MIT license, contributions to Windsor must be licensed under the Apache 2 license.

At this point in the lifecycle of ASP.NET/DNX/CLI/etc I think it is probably best if you maintain the integration until things settle down and stabilise. My focus at the moment is on Castle Core and getting it ported to .NET Core. Windsor will obviously come next, but at the moment the build system of Windsor is completely incompatible with project.json so our learnings from Castle Core will flow into fixing that up too along with porting to .NET Core so we can accept this integration without it being just for the .NET Framework. Thoughts, opinions, feedback?

@cwe1ss

This comment has been minimized.

Show comment
Hide comment
@cwe1ss

cwe1ss May 18, 2016

Hi @jonorossi,
we've had some serious issues with the integration during the last weeks/months - mostly related to how scopes and sub-containers are handled in Microsoft's DI framework. I'm not sure if Windsor is really compatible with their container. Seems like other DI vendors (especially SimpleInjector) don't even want to create an adapter.

I don't know much about the Windsor internals, so I didn't want to invest too much time. Instead, we decided to remove Windsor and go with the integrated DI from Microsoft for now (albeit it has a lot less features).

This means I don't really have a motivation to maintain the integration. So it's up to you to decide if and when to do something in this area!

Feel free to fork my repository, in case you need the files for future reference. I will delete the repository in a few weeks, since I believe it's broken and therefore not really helpful for anyone except you.

cwe1ss commented May 18, 2016

Hi @jonorossi,
we've had some serious issues with the integration during the last weeks/months - mostly related to how scopes and sub-containers are handled in Microsoft's DI framework. I'm not sure if Windsor is really compatible with their container. Seems like other DI vendors (especially SimpleInjector) don't even want to create an adapter.

I don't know much about the Windsor internals, so I didn't want to invest too much time. Instead, we decided to remove Windsor and go with the integrated DI from Microsoft for now (albeit it has a lot less features).

This means I don't really have a motivation to maintain the integration. So it's up to you to decide if and when to do something in this area!

Feel free to fork my repository, in case you need the files for future reference. I will delete the repository in a few weeks, since I believe it's broken and therefore not really helpful for anyone except you.

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 18, 2016

Hi @cwe1ss

I'm using Castle Windsor in aspnet boilerplate (http://www.aspnetboilerplate.com/) and now want to move to AspNet Core. I faced to the same problem with you. It seems the biggest problem is the Scope implementation.
Did you try to use WindsorContainer.AddChildContainer method to return a ServiceScope?

Hi @cwe1ss

I'm using Castle Windsor in aspnet boilerplate (http://www.aspnetboilerplate.com/) and now want to move to AspNet Core. I faced to the same problem with you. It seems the biggest problem is the Scope implementation.
Did you try to use WindsorContainer.AddChildContainer method to return a ServiceScope?

@cwe1ss

This comment has been minimized.

Show comment
Hide comment
@cwe1ss

cwe1ss May 18, 2016

No. As far as I understand them, windsor child containers are used at service registration time and not at resolve time. ( http://mikehadlow.blogspot.co.at/2010/05/10-advanced-windsor-tricks-13-use.html ). This could be wrong though - I don't know much about Windsor.

cwe1ss commented May 18, 2016

No. As far as I understand them, windsor child containers are used at service registration time and not at resolve time. ( http://mikehadlow.blogspot.co.at/2010/05/10-advanced-windsor-tricks-13-use.html ). This could be wrong though - I don't know much about Windsor.

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 18, 2016

Hmmm... yes, I'm creating some unit tests and it's like that.
I created a child container, added to main container (thus I can resolve classes registered to the main container), resolved some transient objects from child container then disposed the child container but it did not release resolved objects.

hikalkan commented May 18, 2016

Hmmm... yes, I'm creating some unit tests and it's like that.
I created a child container, added to main container (thus I can resolve classes registered to the main container), resolved some transient objects from child container then disposed the child container but it did not release resolved objects.

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 18, 2016

I'm investigating docs and source code of Windsor and I suppose we should implement a custom LifeStyleManager (https://github.com/castleproject/Windsor/blob/master/docs/lifestyles.md#custom-lifestyles). This manager should return the same instance for same scope and destroy all instances when IServiceScope.Dispose() is called by AspNet.

I'm investigating docs and source code of Windsor and I suppose we should implement a custom LifeStyleManager (https://github.com/castleproject/Windsor/blob/master/docs/lifestyles.md#custom-lifestyles). This manager should return the same instance for same scope and destroy all instances when IServiceScope.Dispose() is called by AspNet.

@cwe1ss

This comment has been minimized.

Show comment
Hide comment
@cwe1ss

cwe1ss May 18, 2016

I'm not sure if this will be enough. ASP.NET creates a scope (= a child ServiceProvider) per request. You can have transient and scoped services in such a scope so they have a different lifestyle (scoped = once per request). All of them have to be disposed on scope.Dispose().

but feel free to fork my repository and give it a go :)

cwe1ss commented May 18, 2016

I'm not sure if this will be enough. ASP.NET creates a scope (= a child ServiceProvider) per request. You can have transient and scoped services in such a scope so they have a different lifestyle (scoped = once per request). All of them have to be disposed on scope.Dispose().

but feel free to fork my repository and give it a go :)

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 19, 2016

OK, I created an adapter library based on your solution and it's passing all unit tests: https://github.com/volosoft/castle-windsor-ms-adapter
Can you check it? I will publish it to nuget but there is a problem with nuget.exe with AspNet RC2 packages currently.

OK, I created an adapter library based on your solution and it's passing all unit tests: https://github.com/volosoft/castle-windsor-ms-adapter
Can you check it? I will publish it to nuget but there is a problem with nuget.exe with AspNet RC2 packages currently.

@cwe1ss

This comment has been minimized.

Show comment
Hide comment
@cwe1ss

cwe1ss May 19, 2016

I guess the usage of ThreadStatic might be problematic - AFAIK it's not compatible with the new async/await world. You might have to store this in CallContext, AsyncLocal ?

Also, what would happen if there were multiple child scopes within one request?

cwe1ss commented May 19, 2016

I guess the usage of ThreadStatic might be problematic - AFAIK it's not compatible with the new async/await world. You might have to store this in CallContext, AsyncLocal ?

Also, what would happen if there were multiple child scopes within one request?

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 19, 2016

Hi,

I know ThreadStatic has problem with async/await. But I used it just while resolving dependencies and Windsor's resolving operation does not include async/await. Whole resolving progress is performed in a single thread (Windsor's resolve mechanism has also similar ThreadStatic mechanism) So, it should not be a problem.

It supports multiple scope (unit tests also have such cases). Also, there is no child scope actually in MS's DI. There can be multiple scopes nested, but they don't have child/parent relation. While this does not effect it, I just wanted to notice.

Hi,

I know ThreadStatic has problem with async/await. But I used it just while resolving dependencies and Windsor's resolving operation does not include async/await. Whole resolving progress is performed in a single thread (Windsor's resolve mechanism has also similar ThreadStatic mechanism) So, it should not be a problem.

It supports multiple scope (unit tests also have such cases). Also, there is no child scope actually in MS's DI. There can be multiple scopes nested, but they don't have child/parent relation. While this does not effect it, I just wanted to notice.

@jpsingleton jpsingleton referenced this issue in jpsingleton/ANCLAFS May 21, 2016

Closed

DI/IoC Containers #1

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 23, 2016

I suppose finally it's working. You can try it from nuget: https://github.com/volosoft/castle-windsor-ms-adapter#how-to-use

But surely it not works for .NET Core, it working for full .NET Framework since .NET Core is not supported by Windsor yet.

hikalkan commented May 23, 2016

I suppose finally it's working. You can try it from nuget: https://github.com/volosoft/castle-windsor-ms-adapter#how-to-use

But surely it not works for .NET Core, it working for full .NET Framework since .NET Core is not supported by Windsor yet.

@jonorossi

This comment has been minimized.

Show comment
Hide comment
@jonorossi

jonorossi May 27, 2016

Member

I won't pretend I know anything about how the DI container works in ASP.NET Core and I haven't looked at @hikalkan's implementation, however have you guys looked at how the current per web request lifestyle works (i.e. ScopedLifestyleManager used by all scoped lifestyles and WebRequestScopeAccessor)?

Member

jonorossi commented May 27, 2016

I won't pretend I know anything about how the DI container works in ASP.NET Core and I haven't looked at @hikalkan's implementation, however have you guys looked at how the current per web request lifestyle works (i.e. ScopedLifestyleManager used by all scoped lifestyles and WebRequestScopeAccessor)?

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 28, 2016

We can not use webrequestscopeaccesor, because Microsoft.Extensions.DependencyInjection's scope approach is different and related to an IServiceScope instance.

We can not use webrequestscopeaccesor, because Microsoft.Extensions.DependencyInjection's scope approach is different and related to an IServiceScope instance.

@jonorossi

This comment has been minimized.

Show comment
Hide comment
@jonorossi

jonorossi May 28, 2016

Member

We can not use webrequestscopeaccesor, because Microsoft.Extensions.DependencyInjection's scope approach is different and related to an IServiceScope instance.

@hikalkan I didn't mean to imply that you could use WebRequestScopeAccessor, I'm aware that HttpContext.Current is no longer present in ASP.NET Core, I meant have you looked at how they are implemented.

Scoped lifestyles in Windsor 3.0+ already implement the interfaces you've implemented yourself. You'll see the LifetimeScopeAccessor will keep track of the scope via CallContext (and only fallback to ThreadStatic on Silverlight). For ASP.NET you need to use CallContext rather than ThreadStatic (as HttpContext does) because IIS performs thread agility where it reuses OS threads while a request is blocked on I/O.

Using the Windsor scoped implementation should simply the implementation to very little, unless I'm missing something fundamental with how the ASP.NET Core DI works, which I may be as I can't find any documentation.

Nancy's Windsor integration might help to show how they are using LifetimeScopeAccessor (and WebRequestScopeAccessor) to handle non-ASP.NET pipeline (and ASP.NET pipeline requests): https://github.com/NancyFx/Nancy.Bootstrappers.Windsor. You obviously don't care about the WebRequestScopeAccessor bit but the other half.

Member

jonorossi commented May 28, 2016

We can not use webrequestscopeaccesor, because Microsoft.Extensions.DependencyInjection's scope approach is different and related to an IServiceScope instance.

@hikalkan I didn't mean to imply that you could use WebRequestScopeAccessor, I'm aware that HttpContext.Current is no longer present in ASP.NET Core, I meant have you looked at how they are implemented.

Scoped lifestyles in Windsor 3.0+ already implement the interfaces you've implemented yourself. You'll see the LifetimeScopeAccessor will keep track of the scope via CallContext (and only fallback to ThreadStatic on Silverlight). For ASP.NET you need to use CallContext rather than ThreadStatic (as HttpContext does) because IIS performs thread agility where it reuses OS threads while a request is blocked on I/O.

Using the Windsor scoped implementation should simply the implementation to very little, unless I'm missing something fundamental with how the ASP.NET Core DI works, which I may be as I can't find any documentation.

Nancy's Windsor integration might help to show how they are using LifetimeScopeAccessor (and WebRequestScopeAccessor) to handle non-ASP.NET pipeline (and ASP.NET pipeline requests): https://github.com/NancyFx/Nancy.Bootstrappers.Windsor. You obviously don't care about the WebRequestScopeAccessor bit but the other half.

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 28, 2016

Hi,

Surely, I opened Windsor's source code in my Visual Studio and investigated reated classes including WebRequestScopeAccessor for hours :) I also investigated source code of Microsoft.Extensions.DependencyInjection.

CallContext can not work since AspNet Core's lifetime does not depend on callcontext. You can create arbitrary scope objects (IServiceScope), store and use them whenever you need. ThreadStatic is worse since it can not work with async pattern. Even there is a HttpContext, we don't rely on it. Also, Microsoft.Extensions.DependencyInjection package is independent from web pipeline.
Surely, I would use a standard mechanism of Windsor if it's possible, but I suppose it's not. My implementation is the most similar implementation to AspNet's, I suppose :)

Hi,

Surely, I opened Windsor's source code in my Visual Studio and investigated reated classes including WebRequestScopeAccessor for hours :) I also investigated source code of Microsoft.Extensions.DependencyInjection.

CallContext can not work since AspNet Core's lifetime does not depend on callcontext. You can create arbitrary scope objects (IServiceScope), store and use them whenever you need. ThreadStatic is worse since it can not work with async pattern. Even there is a HttpContext, we don't rely on it. Also, Microsoft.Extensions.DependencyInjection package is independent from web pipeline.
Surely, I would use a standard mechanism of Windsor if it's possible, but I suppose it's not. My implementation is the most similar implementation to AspNet's, I suppose :)

@jonorossi

This comment has been minimized.

Show comment
Hide comment
@jonorossi

jonorossi May 28, 2016

Member

@hikalkan ah, I didn't realise the IServiceScope was just an arbitrary scope object, thought they were created and destroyed in the call chain. Could you implement Windsor's IScopeAccessor using a ConcurrentDictionary<IServiceScope, ILifetimeScope> rather than a ThreadStatic?

Member

jonorossi commented May 28, 2016

@hikalkan ah, I didn't realise the IServiceScope was just an arbitrary scope object, thought they were created and destroyed in the call chain. Could you implement Windsor's IScopeAccessor using a ConcurrentDictionary<IServiceScope, ILifetimeScope> rather than a ThreadStatic?

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 28, 2016

I would not want to use ThreadStatic at all actually. I used it just while resolving an object to understand which scope is being used in this resolving operation. If I use that dictionary, how I will transfer the scope information to the resolving code to ensure they use the same lifetimescope. Resolving code can be something like that:

var myService = _serviceScope.ServiceProvider.GetService(typeof(MyService));

myService instance and all dependencies and all their dependencies should be related to the _serviceScope instance and all they should be released when _serviceScope.Dispose() is called by AspNet. Without a threadstatic, how can I relate this serviceScope with the all objects resolved in this particular GetService method call?

hikalkan commented May 28, 2016

I would not want to use ThreadStatic at all actually. I used it just while resolving an object to understand which scope is being used in this resolving operation. If I use that dictionary, how I will transfer the scope information to the resolving code to ensure they use the same lifetimescope. Resolving code can be something like that:

var myService = _serviceScope.ServiceProvider.GetService(typeof(MyService));

myService instance and all dependencies and all their dependencies should be related to the _serviceScope instance and all they should be released when _serviceScope.Dispose() is called by AspNet. Without a threadstatic, how can I relate this serviceScope with the all objects resolved in this particular GetService method call?

@hammett

This comment has been minimized.

Show comment
Hide comment
@hammett

hammett May 28, 2016

Contributor

If you need something that works with TPL, check AsyncLocal<T>, or more error prone LogicalCallContext.

Contributor

hammett commented May 28, 2016

If you need something that works with TPL, check AsyncLocal<T>, or more error prone LogicalCallContext.

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan Jun 6, 2016

threadstatic/async stuff is not a problem, believe me. Because, Windsor's Resolve code is single threaded (it even use ThreadStatic internally, it won't work if this was a problem).

And also, https://github.com/volosoft/castle-windsor-ms-adapter works properly as I tested. But would be good to get some code review.

hikalkan commented Jun 6, 2016

threadstatic/async stuff is not a problem, believe me. Because, Windsor's Resolve code is single threaded (it even use ThreadStatic internally, it won't work if this was a problem).

And also, https://github.com/volosoft/castle-windsor-ms-adapter works properly as I tested. But would be good to get some code review.

@Kralizek

This comment has been minimized.

Show comment
Hide comment
@Kralizek

Kralizek Oct 5, 2017

Any news about this one?

Kralizek commented Oct 5, 2017

Any news about this one?

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx Oct 5, 2017

Contributor

@Kralizek - Please see #283. There is a wealth of info there.

I have not touched on this for a while because there were other things that took priority. I honestly don't believe supporting the abstractions for Microsoft.Extensions.DependencyInjection is viable because of impedance mismatches in the design. It leads to behaviours that are difficult to maintain.

It has also earned Windsor the title of a non-conforming container. Not even sure what that means. I am planning to dust off the work I have been pursuing on this in the next few weeks.

Contributor

fir3pho3nixx commented Oct 5, 2017

@Kralizek - Please see #283. There is a wealth of info there.

I have not touched on this for a while because there were other things that took priority. I honestly don't believe supporting the abstractions for Microsoft.Extensions.DependencyInjection is viable because of impedance mismatches in the design. It leads to behaviours that are difficult to maintain.

It has also earned Windsor the title of a non-conforming container. Not even sure what that means. I am planning to dust off the work I have been pursuing on this in the next few weeks.

@Kralizek

This comment has been minimized.

Show comment
Hide comment
@Kralizek

Kralizek Oct 5, 2017

How is people supposed to use Castle Windsor in ASP.NET Core applications? Should we do all the built-in registrations again?
Also, many library creators are creating setup helpers based on IServiceCollection.

Non supporting the de-facto standard in .NET (Core) will eventually mean pushing Castle Windsor into oblivion. And that would be sad.

Kralizek commented Oct 5, 2017

How is people supposed to use Castle Windsor in ASP.NET Core applications? Should we do all the built-in registrations again?
Also, many library creators are creating setup helpers based on IServiceCollection.

Non supporting the de-facto standard in .NET (Core) will eventually mean pushing Castle Windsor into oblivion. And that would be sad.

@jonorossi

This comment has been minimized.

Show comment
Hide comment
@jonorossi

jonorossi Oct 6, 2017

Member

Non supporting the de-facto standard in .NET (Core) will eventually mean pushing Castle Windsor into oblivion. And that would be sad.

Windsor relies on contributions, @fir3pho3nixx isn't saying Windsor won't support it, more just that we haven't worked out how within the constraints of what we've got on both sides. I encourage you to continue the discussion, I personally don't have the time to work on this in the short or medium term, but would like to see those that have spent energy on this already get together and nut out or workaround remaining issues so Windsor can support this.

Member

jonorossi commented Oct 6, 2017

Non supporting the de-facto standard in .NET (Core) will eventually mean pushing Castle Windsor into oblivion. And that would be sad.

Windsor relies on contributions, @fir3pho3nixx isn't saying Windsor won't support it, more just that we haven't worked out how within the constraints of what we've got on both sides. I encourage you to continue the discussion, I personally don't have the time to work on this in the short or medium term, but would like to see those that have spent energy on this already get together and nut out or workaround remaining issues so Windsor can support this.

@Kralizek

This comment has been minimized.

Show comment
Hide comment
@Kralizek

Kralizek Oct 6, 2017

Don't get me wrong. I have extreme respect for the people who pulled off the .NET Core support.

But my comment was about attitude, intentions and future plans. When people write "I believe X is not viable" and X is the biggest thing happened in the .NET space in the last couple of years, you are putting yourself in the wrong mindset.

Let me write it explicitly again, my criticism is not about "not having done something because lack of resources", rather "CW is a great product (it really is or i wouldn't be here) and we define our own standards, people will follow us".

The focus should be giving users the possibility to jump on CW from a dotnet newed project in three steps. And those three steps, like it oe not, are necessarily passing by supporting Microsoft.Extensions.DependencyInjection. Then it's just about deciding the level of support.

Again, I'm a "dedicated customer" of CW. Dedicated enough to have googled for a solution for those three steps. I found "https://github.com/volosoft/castle-windsor-ms-adapter" but the vibe i get here is that it's compiling solution rather than a sustainable one (aka, can i go live with it?). If i weren't so committed into using CW, I would have already jumped ship.

Kralizek commented Oct 6, 2017

Don't get me wrong. I have extreme respect for the people who pulled off the .NET Core support.

But my comment was about attitude, intentions and future plans. When people write "I believe X is not viable" and X is the biggest thing happened in the .NET space in the last couple of years, you are putting yourself in the wrong mindset.

Let me write it explicitly again, my criticism is not about "not having done something because lack of resources", rather "CW is a great product (it really is or i wouldn't be here) and we define our own standards, people will follow us".

The focus should be giving users the possibility to jump on CW from a dotnet newed project in three steps. And those three steps, like it oe not, are necessarily passing by supporting Microsoft.Extensions.DependencyInjection. Then it's just about deciding the level of support.

Again, I'm a "dedicated customer" of CW. Dedicated enough to have googled for a solution for those three steps. I found "https://github.com/volosoft/castle-windsor-ms-adapter" but the vibe i get here is that it's compiling solution rather than a sustainable one (aka, can i go live with it?). If i weren't so committed into using CW, I would have already jumped ship.

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan Oct 6, 2017

@Kralizek https://github.com/volosoft/castle-windsor-ms-adapter is maintained by us and it's used in production by our customers. It's not something just compiles. We have spent significiant time to adapt Windsor to MS DI (it was hard). So, I think you should give a chance to it :)

hikalkan commented Oct 6, 2017

@Kralizek https://github.com/volosoft/castle-windsor-ms-adapter is maintained by us and it's used in production by our customers. It's not something just compiles. We have spent significiant time to adapt Windsor to MS DI (it was hard). So, I think you should give a chance to it :)

@Kralizek

This comment has been minimized.

Show comment
Hide comment

Kralizek commented Oct 6, 2017

Great!

@hconceicao

This comment has been minimized.

Show comment
Hide comment
@hconceicao

hconceicao Oct 6, 2017

Contributor

@Kralizek I'm not writing on behalf of the project and/or the people working on it atm. This is a personal opinion.

But as a long time OSS contributor, this is the kind of behavior that pisses off everyone working it.

We (oss devs) don't get paid for our work, so you're not a customer (download it or using in an very importante/production project also doesn't make you a customer). Also, we don't care if you googled a solution to fix an issue for the people who do pay your salary.

So, next time (unless you're willing to contribute with an actual solution that involves contributing source code), please: save the smarts thoughts/criticisms about long term strategy and priorities to yourself.

Contributor

hconceicao commented Oct 6, 2017

@Kralizek I'm not writing on behalf of the project and/or the people working on it atm. This is a personal opinion.

But as a long time OSS contributor, this is the kind of behavior that pisses off everyone working it.

We (oss devs) don't get paid for our work, so you're not a customer (download it or using in an very importante/production project also doesn't make you a customer). Also, we don't care if you googled a solution to fix an issue for the people who do pay your salary.

So, next time (unless you're willing to contribute with an actual solution that involves contributing source code), please: save the smarts thoughts/criticisms about long term strategy and priorities to yourself.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx Oct 6, 2017

Contributor

I do believe an implementation around Missing Core DI Extensions is the best way to go in terms of maintainability for the Castle Windsor Project. I will list advantages and disadvantages below.

@hikalkan feel free to weigh in, you and I have done heaps of work on the Microsoft.Extensions.DependencyInjection.Abstractions space and I feel you are the leading authority on this approach. Counter arguments from you are most welcome indeed.

Advantages:

  • Castle Windsor has a first class mature registration API. The registration API extension's for ServiceCollections are incredibly naive from my point of view. Also it is woefully opinionated for the AspNet team to expect container's to forgo their years of hardening and design around this and to merely adapt to their desired service state configuration for a container. Missing-DI-Extensions promotes the use of the underlying containers public registration API's. This gives access to things like IWindsorInstaller's and not to mention all the namespace registration slicing, conventional registration power along with things like specifying interceptors using Castle Core. By comparison their registration API is absolute garbage.

  • We do scoping properly, they simply don't. Our scopes are a single responsibility which applies to the lifestyle of a dependency under certain registration conditions with predictable burden based decommissioning concerns(not including parent/child containers in this statement). Their scopes blend in a service locator pattern which mutates lifestyle outcomes depending on whether it was consumed inside or outside of a scope but also imposes constraints on singletons. It also requires the new'ing up of child container(not in the windsor sense) which is just plain rubbish when it comes to memory management and compared to how Windsor does it today.

  • Singleton's live on, transients consumed by them get disposed. Eh? What? Yeah, with the Missing-DI-Extensions approach you wont have this problem.

  • You don't have to figure out the internals of AspNet to adapt their desired service state configuration to Windsor via the bizarre Microsoft.Extensions.DependencyInjection.Abstractions suggestion. @hikalkan you conquered this, but already I can tell you have to mutate lifestyles to fall back on to other lifestyles achieve this.

  • AspNet do what they need to do and you are in control of your container using the native functionality. It is unfortunately a world of two halves. You just get to pick the one you want for your stuff without the lacking API's or blatant inefficiencies.

  • Another advantage or something Windsor tries to support is disposing things in the right order. This might be a little counter intuitive when implementing the adapter.

Disadvantages

  • You have to register framework services/extensions yourself. This can be problematic if you misunderstand what they needed/meant or they did not bother to document before releasing stuff. I personally don't believe this to be a big problem at all.

  • You are effectively running 2 containers. The AspNet internal one which you are side stepping, and the consumer one which is your choice. This could be considered bad, although I would like to hear elaborations from people on this thread as to the severity.

Summary

After walking @hikalkan's path myself, I can honestly say if you want a registration API that relies on ServiceCollection extensions then you should definitely use that.

If you want to use Windsor, go all in ... that includes the Windsor Registration API along with all the buttery goodness it has to offer.

Contributor

fir3pho3nixx commented Oct 6, 2017

I do believe an implementation around Missing Core DI Extensions is the best way to go in terms of maintainability for the Castle Windsor Project. I will list advantages and disadvantages below.

@hikalkan feel free to weigh in, you and I have done heaps of work on the Microsoft.Extensions.DependencyInjection.Abstractions space and I feel you are the leading authority on this approach. Counter arguments from you are most welcome indeed.

Advantages:

  • Castle Windsor has a first class mature registration API. The registration API extension's for ServiceCollections are incredibly naive from my point of view. Also it is woefully opinionated for the AspNet team to expect container's to forgo their years of hardening and design around this and to merely adapt to their desired service state configuration for a container. Missing-DI-Extensions promotes the use of the underlying containers public registration API's. This gives access to things like IWindsorInstaller's and not to mention all the namespace registration slicing, conventional registration power along with things like specifying interceptors using Castle Core. By comparison their registration API is absolute garbage.

  • We do scoping properly, they simply don't. Our scopes are a single responsibility which applies to the lifestyle of a dependency under certain registration conditions with predictable burden based decommissioning concerns(not including parent/child containers in this statement). Their scopes blend in a service locator pattern which mutates lifestyle outcomes depending on whether it was consumed inside or outside of a scope but also imposes constraints on singletons. It also requires the new'ing up of child container(not in the windsor sense) which is just plain rubbish when it comes to memory management and compared to how Windsor does it today.

  • Singleton's live on, transients consumed by them get disposed. Eh? What? Yeah, with the Missing-DI-Extensions approach you wont have this problem.

  • You don't have to figure out the internals of AspNet to adapt their desired service state configuration to Windsor via the bizarre Microsoft.Extensions.DependencyInjection.Abstractions suggestion. @hikalkan you conquered this, but already I can tell you have to mutate lifestyles to fall back on to other lifestyles achieve this.

  • AspNet do what they need to do and you are in control of your container using the native functionality. It is unfortunately a world of two halves. You just get to pick the one you want for your stuff without the lacking API's or blatant inefficiencies.

  • Another advantage or something Windsor tries to support is disposing things in the right order. This might be a little counter intuitive when implementing the adapter.

Disadvantages

  • You have to register framework services/extensions yourself. This can be problematic if you misunderstand what they needed/meant or they did not bother to document before releasing stuff. I personally don't believe this to be a big problem at all.

  • You are effectively running 2 containers. The AspNet internal one which you are side stepping, and the consumer one which is your choice. This could be considered bad, although I would like to hear elaborations from people on this thread as to the severity.

Summary

After walking @hikalkan's path myself, I can honestly say if you want a registration API that relies on ServiceCollection extensions then you should definitely use that.

If you want to use Windsor, go all in ... that includes the Windsor Registration API along with all the buttery goodness it has to offer.

@Kralizek

This comment has been minimized.

Show comment
Hide comment
@Kralizek

Kralizek Oct 8, 2017

I finally have time to sit down and read where this conversation is going.

I am really sorry that my intentions were misinterpreted and I here offer my deepest apologies for the tone of the posts. In no occasion I wanted to be disrespectful to the work put into this amazing project by every single contributor. I understand that the tone used in my comments was heavy and ungrateful but, believe me, I never wanted this to be the point.

I might have been swinged away by comments written before things were actually the way they are right now, but this is not a good excuse for using such a tone in my posts.

Again, if any of you felt insulted, I am deeply sorry for that.

Kralizek commented Oct 8, 2017

I finally have time to sit down and read where this conversation is going.

I am really sorry that my intentions were misinterpreted and I here offer my deepest apologies for the tone of the posts. In no occasion I wanted to be disrespectful to the work put into this amazing project by every single contributor. I understand that the tone used in my comments was heavy and ungrateful but, believe me, I never wanted this to be the point.

I might have been swinged away by comments written before things were actually the way they are right now, but this is not a good excuse for using such a tone in my posts.

Again, if any of you felt insulted, I am deeply sorry for that.

@fir3pho3nixx fir3pho3nixx referenced this issue in NancyFx/Nancy.Bootstrappers.Windsor Oct 8, 2017

Open

Netstandard 2.0 support #54

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx Oct 8, 2017

Contributor

@Kralizek - We all get peeved sometimes seriously not a problem. If you have been following the notifications for this project you would have noticed that I have been doing a lot of research in this space over the past few months. I honestly cannot take the credit for everything. @dotnetjunkie did a lot of leg work pursuing this with the aspnet team. It was only when I started bombarding the same team with a multitude of issue's that I was pointed towards this issue.

My problem with supporting the adapter is the aspnet team gives you a suite of tests that you are supposed to make pass for your container, this unfortunately dictates really bad things in terms of design. It also reveals how inferior their design is when comparing it to something like Castle Windsor.

Anyone supporting this is either hacking around their malformed design(which the aspnet team are not interested in fixing) or actually implementing IoC incorrectly. I believe Windsor is better than that. I would literally have to break Windsor to support this nonsense.

** Edited **

I am willing to support AspNet Core, but only where Castle Windsor is a first class citizen.

Contributor

fir3pho3nixx commented Oct 8, 2017

@Kralizek - We all get peeved sometimes seriously not a problem. If you have been following the notifications for this project you would have noticed that I have been doing a lot of research in this space over the past few months. I honestly cannot take the credit for everything. @dotnetjunkie did a lot of leg work pursuing this with the aspnet team. It was only when I started bombarding the same team with a multitude of issue's that I was pointed towards this issue.

My problem with supporting the adapter is the aspnet team gives you a suite of tests that you are supposed to make pass for your container, this unfortunately dictates really bad things in terms of design. It also reveals how inferior their design is when comparing it to something like Castle Windsor.

Anyone supporting this is either hacking around their malformed design(which the aspnet team are not interested in fixing) or actually implementing IoC incorrectly. I believe Windsor is better than that. I would literally have to break Windsor to support this nonsense.

** Edited **

I am willing to support AspNet Core, but only where Castle Windsor is a first class citizen.

@dotnetjunkie

This comment has been minimized.

Show comment
Hide comment
@dotnetjunkie

dotnetjunkie Oct 26, 2017

@fir3pho3nixx

I would l to respond to your list of advantages and disadvantages:

The registration API extension's for ServiceCollections are incredibly naive from my point of view. [...] their registration API is absolute garbage.

Do note that it has never been Microsoft's intention to have a registration API that completely replaces that of a third-party container. In that case it would be rather useless to use a 3rd party container. The abstraction is merely meant to be able to push all the framework and 3rd party component registrations into a third-party container and to let application developers use their container’s native API on top of that, so they can have features like batch-registration and interception. So the idea always was that application developers registered their application components using the native API.

Problems however arise because there are now two APIs that might have completely different (and sometimes incompatible) behavior. When an application developer uses the MS API, can he expect the registrations to behave the same as when done through the native API? And what if object graphs are composed of both MS API registrations and native API registrations? Which rules should apply? Native rules or MS rules? Differences between the two could be very subtle.

Singleton's live on, transients consumed by them get disposed.

I tested this last week with Core v2. Transients, injected into singletons get disposed after their singleton gets disposed (i.e. they're scoped to the container). I'm unsure whether this behavior was different before.

You have to register framework services/extensions yourself. This can be problematic if you misunderstand what they needed/meant or they did not bother to document before releasing stuff. I personally don't believe this to be a big problem at all.

This is actually the hardest part in the framework/application container split. A big warning here: Do not advice your users to re-register framework components in Castle Windsor. This leads to extraordinary amounts of pain, because users will have to redo complete lists of registrations for object graphs for what they are only interested in getting the top-most object. This leads to large amounts of code duplication–for each framework component or third-party component they are using, and this block of registration needs to be updated with each new version of such component. This would lead to a lot of work and strange bugs after upgrading to a new version. On top of that, since there might be subtle differences in the registrations APIs, it might lead to weird bugs if users try to do this themselves. Having the writer of a DI Container create a integration package however, makes little sense, because it would force a tremendous burden on the maintainers of such DI Container.

Another bug that you might run into when doing this is that of Torn Lifestyles. This can happen when a framework or third-party component is registered as Scoped, and we re-register it again as Scoped in Castle Windsor. This might lead to having 2 instances of that component during the same request.

Instead, framework and third-party components should be cross-wired (this is a term I coined for the occasion). Cross-wiring means that we register a delegate in Castle Windsor that redirects the call to the MS container to resolve it for us. This way, the MS container is in control over the composition of that component and all its dependencies. The only thing we do is get it from the MS container and inject it into our object graph.

I always tell my users that their application should have very little dependencies on those application or third-party components, and that the amount of cross-wired dependencies should be very low. This means that it shouldn’t be a problem to cross-wire the few dependencies that exist.

Although I still feel this way, that statement however has proven to be a little problematic for application developers. A good example is when Identity Framework Core is used and especially when mixed with its templates that are installed in Visual Studio. To get the default template working with a non-conforming container is quite annoying.

Another problem with this approach is, that while cross-wiring, you need to be aware of the lifestyle of a component in the MS container, to prevent running into Captive Dependencies. A better approach would be to register these dependencies as Func<T> singletons that resolve from the MS container at runtime instead of at time of composition. Although I do endorse this model, it confuses developers, again because of the default Identity Visual Studio template, which needs to be refactored quite a bit.

Because of this, the Simple Injector community has been experimenting with some ‘auto cross-wire’ feature for Simple Injector (see code). This will try to cross-wire a registration automatically when the container encounters a missing registration in the Simple Injector container.

You might think this reintroduces the Conforming container, but this is not the case, because it completely circumvents the MS registration API, and only makes use of its Resolve functionality (so it’s a Service Locator at most–not a Conforming Container). It does however require some knowledge on what the meaning is of the IServiceCollection, because it needs to check whether or not a registration exists, and it also skips resolving collections, because behavior of collections is incompatible between MS and Simple Injector, even when just resolving collections.

This is still in the experimentation phase however. Although some users reported great success, it’s still a bit unclear the exact downsides of this approach.

One thing I would really like to see is such extension to be moved in a Microsoft assembly. In an ideal world, even non-conforming containers should need no integration packages what so ever. Castle Windsor should only have a piece of documentation explaining how to plug in the container into the Microsoft pipeline using the Microsoft supplied code. This is one of the promises that Microsoft made when building their Conforming Container. They wanted to end the need for endless permutation of integration libraries.

Unfortunately, that’s not the world we live in (and I don’t think we have to expect too much from Microsoft in this respect), so we are forced to still create our own integration packages for each new framework on top of .NET Core.

You are effectively running 2 containers. The AspNet internal one which you are side stepping, and the consumer one which is your choice.

Do note that out-of-the-box, ASP.NET Core already runs with 2 container instances. From the initial set of registrations a ‘hosting container’ is built. Later on, that same set of registrations is used to build a container that contains the registrations for MVC and your application components. This happens even if you use the built-in MS container, and since those containers aren’t cross-wiring, it can already lead to Torn Lifestyles.

This could be considered bad, although I would like to hear elaborations from people on this thread as to the severity.

I don’t agree with this. I always try to explain to developers that, from their perspective, there is only one container, which is their application container (in your case: Castle Windsor). The other thing is not a container, it is the framework’s configuration system. Older .NET frameworks had their own configuration system as well (ASP.NET Forms mainly XML based, and MVC and Web API contained their own Services collections that you could add and remove stuff to/from). In ASP.NET Core the configuration system is more complex, has more features, but it is still a configuration system. In the past we had our own application container for our own application components. As I see it, if we use an non-conforming container, this is the exact same model.

But even if you see the framework’s configuration system as a container, there’s nothing wrong in having two separate containers. You achieve a pretty nice separation between the components you are interested in (your application components) and the things you are typically not interested in (framework infrastructure). By separating these two worlds, your application container is not polluted with hundreds of registrations that are not of your concern. Keeping things separate is IMO the most sensible thing to do.

@fir3pho3nixx

I would l to respond to your list of advantages and disadvantages:

The registration API extension's for ServiceCollections are incredibly naive from my point of view. [...] their registration API is absolute garbage.

Do note that it has never been Microsoft's intention to have a registration API that completely replaces that of a third-party container. In that case it would be rather useless to use a 3rd party container. The abstraction is merely meant to be able to push all the framework and 3rd party component registrations into a third-party container and to let application developers use their container’s native API on top of that, so they can have features like batch-registration and interception. So the idea always was that application developers registered their application components using the native API.

Problems however arise because there are now two APIs that might have completely different (and sometimes incompatible) behavior. When an application developer uses the MS API, can he expect the registrations to behave the same as when done through the native API? And what if object graphs are composed of both MS API registrations and native API registrations? Which rules should apply? Native rules or MS rules? Differences between the two could be very subtle.

Singleton's live on, transients consumed by them get disposed.

I tested this last week with Core v2. Transients, injected into singletons get disposed after their singleton gets disposed (i.e. they're scoped to the container). I'm unsure whether this behavior was different before.

You have to register framework services/extensions yourself. This can be problematic if you misunderstand what they needed/meant or they did not bother to document before releasing stuff. I personally don't believe this to be a big problem at all.

This is actually the hardest part in the framework/application container split. A big warning here: Do not advice your users to re-register framework components in Castle Windsor. This leads to extraordinary amounts of pain, because users will have to redo complete lists of registrations for object graphs for what they are only interested in getting the top-most object. This leads to large amounts of code duplication–for each framework component or third-party component they are using, and this block of registration needs to be updated with each new version of such component. This would lead to a lot of work and strange bugs after upgrading to a new version. On top of that, since there might be subtle differences in the registrations APIs, it might lead to weird bugs if users try to do this themselves. Having the writer of a DI Container create a integration package however, makes little sense, because it would force a tremendous burden on the maintainers of such DI Container.

Another bug that you might run into when doing this is that of Torn Lifestyles. This can happen when a framework or third-party component is registered as Scoped, and we re-register it again as Scoped in Castle Windsor. This might lead to having 2 instances of that component during the same request.

Instead, framework and third-party components should be cross-wired (this is a term I coined for the occasion). Cross-wiring means that we register a delegate in Castle Windsor that redirects the call to the MS container to resolve it for us. This way, the MS container is in control over the composition of that component and all its dependencies. The only thing we do is get it from the MS container and inject it into our object graph.

I always tell my users that their application should have very little dependencies on those application or third-party components, and that the amount of cross-wired dependencies should be very low. This means that it shouldn’t be a problem to cross-wire the few dependencies that exist.

Although I still feel this way, that statement however has proven to be a little problematic for application developers. A good example is when Identity Framework Core is used and especially when mixed with its templates that are installed in Visual Studio. To get the default template working with a non-conforming container is quite annoying.

Another problem with this approach is, that while cross-wiring, you need to be aware of the lifestyle of a component in the MS container, to prevent running into Captive Dependencies. A better approach would be to register these dependencies as Func<T> singletons that resolve from the MS container at runtime instead of at time of composition. Although I do endorse this model, it confuses developers, again because of the default Identity Visual Studio template, which needs to be refactored quite a bit.

Because of this, the Simple Injector community has been experimenting with some ‘auto cross-wire’ feature for Simple Injector (see code). This will try to cross-wire a registration automatically when the container encounters a missing registration in the Simple Injector container.

You might think this reintroduces the Conforming container, but this is not the case, because it completely circumvents the MS registration API, and only makes use of its Resolve functionality (so it’s a Service Locator at most–not a Conforming Container). It does however require some knowledge on what the meaning is of the IServiceCollection, because it needs to check whether or not a registration exists, and it also skips resolving collections, because behavior of collections is incompatible between MS and Simple Injector, even when just resolving collections.

This is still in the experimentation phase however. Although some users reported great success, it’s still a bit unclear the exact downsides of this approach.

One thing I would really like to see is such extension to be moved in a Microsoft assembly. In an ideal world, even non-conforming containers should need no integration packages what so ever. Castle Windsor should only have a piece of documentation explaining how to plug in the container into the Microsoft pipeline using the Microsoft supplied code. This is one of the promises that Microsoft made when building their Conforming Container. They wanted to end the need for endless permutation of integration libraries.

Unfortunately, that’s not the world we live in (and I don’t think we have to expect too much from Microsoft in this respect), so we are forced to still create our own integration packages for each new framework on top of .NET Core.

You are effectively running 2 containers. The AspNet internal one which you are side stepping, and the consumer one which is your choice.

Do note that out-of-the-box, ASP.NET Core already runs with 2 container instances. From the initial set of registrations a ‘hosting container’ is built. Later on, that same set of registrations is used to build a container that contains the registrations for MVC and your application components. This happens even if you use the built-in MS container, and since those containers aren’t cross-wiring, it can already lead to Torn Lifestyles.

This could be considered bad, although I would like to hear elaborations from people on this thread as to the severity.

I don’t agree with this. I always try to explain to developers that, from their perspective, there is only one container, which is their application container (in your case: Castle Windsor). The other thing is not a container, it is the framework’s configuration system. Older .NET frameworks had their own configuration system as well (ASP.NET Forms mainly XML based, and MVC and Web API contained their own Services collections that you could add and remove stuff to/from). In ASP.NET Core the configuration system is more complex, has more features, but it is still a configuration system. In the past we had our own application container for our own application components. As I see it, if we use an non-conforming container, this is the exact same model.

But even if you see the framework’s configuration system as a container, there’s nothing wrong in having two separate containers. You achieve a pretty nice separation between the components you are interested in (your application components) and the things you are typically not interested in (framework infrastructure). By separating these two worlds, your application container is not polluted with hundreds of registrations that are not of your concern. Keeping things separate is IMO the most sensible thing to do.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 5, 2018

Contributor

@shanselman Houston, we have a problem ....

Contributor

fir3pho3nixx commented May 5, 2018

@shanselman Houston, we have a problem ....

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
Contributor

fir3pho3nixx commented May 5, 2018

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 5, 2018

Contributor

#LetsFixSingletonsInDependencyInjection

Contributor

fir3pho3nixx commented May 5, 2018

#LetsFixSingletonsInDependencyInjection

@dotnetjunkie

This comment has been minimized.

Show comment
Hide comment
@dotnetjunkie

dotnetjunkie May 5, 2018

Please note that the problem exists with Scoped registrations also.

Please note that the problem exists with Scoped registrations also.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 10, 2018

Contributor

For anyone interested in a debuggable solution I have one here: https://github.com/fir3pho3nixx/Windsor.AspNetCore/tree/aspnet-hosting

@dotnetjunkie My gut feel right is we need to start looking at hosting. Creating our own IServiceProvider'less hosting provider wrapper is potentially an option?

Contributor

fir3pho3nixx commented May 10, 2018

For anyone interested in a debuggable solution I have one here: https://github.com/fir3pho3nixx/Windsor.AspNetCore/tree/aspnet-hosting

@dotnetjunkie My gut feel right is we need to start looking at hosting. Creating our own IServiceProvider'less hosting provider wrapper is potentially an option?

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 13, 2018

Contributor

I firstly want to make it clear to everyone on this thread that singleton violation's with improperly tracked disposables are an absolute no no. We have enough of that going on with our very own typed factories.

Singletons to me are a process wide instance that shares state and is always threadsafe. Saying no to this creates a configuration nightmare.

I have found a thread-safe way to track "process wide" singletons with better tests for the abstractions.
I am hoping anyone from the hosting project would dive in. I raised this PR against my fork.
fir3pho3nixx/DependencyInjection#2.

Notice how I changed all tests to not track singletons for disposables: https://github.com/fir3pho3nixx/DependencyInjection/pull/2/files#diff-cf49ec75a3a58a8b15b902816b32ce28R690

I also added some negative testing for singletons when it comes to not tracking disposables here: https://github.com/fir3pho3nixx/DependencyInjection/pull/2/files#diff-4185789735b0c69cda3bd684d54a5828R148

Now that we have removed all the disposable tracking, I then came up with this rubbish idea of a singleton tracker which I added to the CallSiteRuntimeResolver. Instead of making singleton fallback on to scoped, I opted for using the singleton tracker here:https://github.com/fir3pho3nixx/DependencyInjection/pull/2/files#diff-df85ffd78647d080ca2e02c3ab6b4de7R6).

It allowed me to test for singletons between scopes and non-scopes like this: https://github.com/fir3pho3nixx/DependencyInjection/pull/2/files#diff-cf49ec75a3a58a8b15b902816b32ce28R444

I really did not like the idea of exposing options that tracked singletons, but I am sure there is a boffin in the house that could help us turn this into a valuable PR.

Common gents/ladies at Microsoft, surely we can work this one out?

Contributor

fir3pho3nixx commented May 13, 2018

I firstly want to make it clear to everyone on this thread that singleton violation's with improperly tracked disposables are an absolute no no. We have enough of that going on with our very own typed factories.

Singletons to me are a process wide instance that shares state and is always threadsafe. Saying no to this creates a configuration nightmare.

I have found a thread-safe way to track "process wide" singletons with better tests for the abstractions.
I am hoping anyone from the hosting project would dive in. I raised this PR against my fork.
fir3pho3nixx/DependencyInjection#2.

Notice how I changed all tests to not track singletons for disposables: https://github.com/fir3pho3nixx/DependencyInjection/pull/2/files#diff-cf49ec75a3a58a8b15b902816b32ce28R690

I also added some negative testing for singletons when it comes to not tracking disposables here: https://github.com/fir3pho3nixx/DependencyInjection/pull/2/files#diff-4185789735b0c69cda3bd684d54a5828R148

Now that we have removed all the disposable tracking, I then came up with this rubbish idea of a singleton tracker which I added to the CallSiteRuntimeResolver. Instead of making singleton fallback on to scoped, I opted for using the singleton tracker here:https://github.com/fir3pho3nixx/DependencyInjection/pull/2/files#diff-df85ffd78647d080ca2e02c3ab6b4de7R6).

It allowed me to test for singletons between scopes and non-scopes like this: https://github.com/fir3pho3nixx/DependencyInjection/pull/2/files#diff-cf49ec75a3a58a8b15b902816b32ce28R444

I really did not like the idea of exposing options that tracked singletons, but I am sure there is a boffin in the house that could help us turn this into a valuable PR.

Common gents/ladies at Microsoft, surely we can work this one out?

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 13, 2018

@fir3pho3nixx can you stop tagging the entire team here. It's not productive. What's the goal here? The thread is long and the latest issue might be something specific and unrelated to the original problem.

@fir3pho3nixx can you stop tagging the entire team here. It's not productive. What's the goal here? The thread is long and the latest issue might be something specific and unrelated to the original problem.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 14, 2018

Contributor

@davidfowl Very much connected to the original problem. Let me explain(although I had hoped you read downward from the first time I tagged everyone).

Creating 2 service providers, will produce 2 separate singletons respectively. This leaves me with torn lifestyles if I manage my own service provider from within the FrameworkDependencyResolver.

I think I have a way of fixing this but it would involve submitting a PR: fir3pho3nixx/DependencyInjection#2

It would then help unblock the original PR for this issue: #394

Contributor

fir3pho3nixx commented May 14, 2018

@davidfowl Very much connected to the original problem. Let me explain(although I had hoped you read downward from the first time I tagged everyone).

Creating 2 service providers, will produce 2 separate singletons respectively. This leaves me with torn lifestyles if I manage my own service provider from within the FrameworkDependencyResolver.

I think I have a way of fixing this but it would involve submitting a PR: fir3pho3nixx/DependencyInjection#2

It would then help unblock the original PR for this issue: #394

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 14, 2018

Creating 2 service providers, will produce 2 separate singletons respectively. This leaves me with torn lifestyles if I manage my own service provider from within the FrameworkDependencyResolver.

Yes, hosting does this and it's awful, what services are breaking for you specifically?

I think I have a way of fixing this but it would involve submitting a PR: fir3pho3nixx/DependencyInjection#2

That fix is in the wrong place. Hosting is the one in the "wrong" not the DI implementation itself so that's not the right place to tackle the issue.

It would then help unblock the original PR for this issue: #394

Is this basically trying to make castle work with ASP.NET Core without being a "conforming" container?

Creating 2 service providers, will produce 2 separate singletons respectively. This leaves me with torn lifestyles if I manage my own service provider from within the FrameworkDependencyResolver.

Yes, hosting does this and it's awful, what services are breaking for you specifically?

I think I have a way of fixing this but it would involve submitting a PR: fir3pho3nixx/DependencyInjection#2

That fix is in the wrong place. Hosting is the one in the "wrong" not the DI implementation itself so that's not the right place to tackle the issue.

It would then help unblock the original PR for this issue: #394

Is this basically trying to make castle work with ASP.NET Core without being a "conforming" container?

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 14, 2018

Contributor

Hosting is the one in the "wrong"

It also looks like I have made the same mistake. What is right way to do this? What are you guys planning to do with hosting? This would probably set me on the path to a proper fix.

Contributor

fir3pho3nixx commented May 14, 2018

Hosting is the one in the "wrong"

It also looks like I have made the same mistake. What is right way to do this? What are you guys planning to do with hosting? This would probably set me on the path to a proper fix.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 14, 2018

Contributor

Is this basically trying to make castle work with ASP.NET Core without being a "conforming" container?

In a word yes. We simply don’t treat scopes and singletons as the same thing. I tried “conforming” this container almost a year ago but the impedance mismatch in life cycles alone make it impossible. I am still hopeful we can make this work.

Contributor

fir3pho3nixx commented May 14, 2018

Is this basically trying to make castle work with ASP.NET Core without being a "conforming" container?

In a word yes. We simply don’t treat scopes and singletons as the same thing. I tried “conforming” this container almost a year ago but the impedance mismatch in life cycles alone make it impossible. I am still hopeful we can make this work.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 14, 2018

Contributor

That fix is in the wrong place. Hosting is the one in the "wrong" not the DI implementation itself so that's not the right place to tackle the issue.

Curious, it solves the abuse that has already happened in hosting(and might be proliferated through all sorts of third party libraries). After falling victim to this myself I would like see some safety built into dependency injection. Would you reconsider?

Contributor

fir3pho3nixx commented May 14, 2018

That fix is in the wrong place. Hosting is the one in the "wrong" not the DI implementation itself so that's not the right place to tackle the issue.

Curious, it solves the abuse that has already happened in hosting(and might be proliferated through all sorts of third party libraries). After falling victim to this myself I would like see some safety built into dependency injection. Would you reconsider?

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 14, 2018

The thing is, today the effect is that you get different instances because there are different universes. I still haven't looked closely enough to see what is different about castle core's implementation that makes it a deal breaker. That isn't to say they isn't a problem but it's been there since the beginning so we just have to understand why it matters more now.

Curious, it solves the abuse that has already happened in hosting(and might be proliferated through all sorts of third party libraries). After falling victim to this myself I would like see some safety built into dependency injection. Would you reconsider?

Maybe but the bar is extremely high for making changes to our default container. It's pretty much frozen unless something is outright broken (and wouldn't be a breaking change to fix).

The thing is, today the effect is that you get different instances because there are different universes. I still haven't looked closely enough to see what is different about castle core's implementation that makes it a deal breaker. That isn't to say they isn't a problem but it's been there since the beginning so we just have to understand why it matters more now.

Curious, it solves the abuse that has already happened in hosting(and might be proliferated through all sorts of third party libraries). After falling victim to this myself I would like see some safety built into dependency injection. Would you reconsider?

Maybe but the bar is extremely high for making changes to our default container. It's pretty much frozen unless something is outright broken (and wouldn't be a breaking change to fix).

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 14, 2018

Contributor

The thing is, today the effect is that you get different instances because there are different universes

Are scopes universes also?

I still haven't looked closely enough to see what is different about castle core's implementation that makes it a deal breaker.

I would love to explore this a little more. Scopes are completely incompatible between our containers.

That isn't to say they isn't a problem but it's been there since the beginning so we just have to understand why it matters more now.

It always mattered. Just getting to it now.

Maybe but the bar is extremely high for making changes to our default container. It's pretty much frozen unless something is outright broken (and wouldn't be a breaking change to fix).

Please bear with me. The least that can come out of this is that I will have a better understanding.

I am incredibly interested in the CallSiteRuntimeResolver right now. Is there any way I could override the VisitSingleton method to use a static collection of resolved services? This would probably sort the problems in hosting out and also not be a breaking change. Hope you are open to discussing this.

Contributor

fir3pho3nixx commented May 14, 2018

The thing is, today the effect is that you get different instances because there are different universes

Are scopes universes also?

I still haven't looked closely enough to see what is different about castle core's implementation that makes it a deal breaker.

I would love to explore this a little more. Scopes are completely incompatible between our containers.

That isn't to say they isn't a problem but it's been there since the beginning so we just have to understand why it matters more now.

It always mattered. Just getting to it now.

Maybe but the bar is extremely high for making changes to our default container. It's pretty much frozen unless something is outright broken (and wouldn't be a breaking change to fix).

Please bear with me. The least that can come out of this is that I will have a better understanding.

I am incredibly interested in the CallSiteRuntimeResolver right now. Is there any way I could override the VisitSingleton method to use a static collection of resolved services? This would probably sort the problems in hosting out and also not be a breaking change. Hope you are open to discussing this.

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 15, 2018

Are scopes universes also?

No. Scopes are very simple. The scoped IServiceProvider is just a disposable root for scoped and transient services. If something is declared as scoped, then it's a singleton within the scope container it gets resolved from (instance per lifetime scope in autofac speak).

I would love to explore this a little more. Scopes are completely incompatible between our containers.

public interface IThing  { }

public class Thing : IThing, IDisposable
{
   public void Dispose() => Console.WriteLine("Thing.Dispose()");
}

var sp = new ServiceCollection().AddScoped<IThing, Thing>()
                                                       .BuildServiceProvider();

using (var scope = sp.CreateScope())
{
   var thing = scope.ServiceProvider.GetService<IThing>();
   var thingAgain = scope.ServiceProvider.GetService<IThing>();
   
   // These are the same instance since they were resolved from the same scope container
}

When I say different universes I literally mean we create different instances of IServiceProvider:

var universe1 = new ServiceCollection().AddSingleton<IFoo, Foo>().BuildServiceProvider();

var universe2 = new ServiceCollection().AddSingleton<IFoo, Foo>().BuildServiceProvider();

Those are 2 different worlds even if they share the same service descriptors. This is what hosting does because they are stages.

It always mattered. Just getting to it now.

When I say it didn't matter, what I meant is that you get different instances at each stage which is by design. It means if you add a singleton in the hosting container, you get 2 different versions because they are 2 different containers. That's the quirk. It surfaces when you resolve a service from the Startup constructor and then from the Configure method.

I am incredibly interested in the CallSiteRuntimeResolver right now. Is there any way I could override the VisitSingleton method to use a static collection of resolved services? This would probably sort the problems in hosting out and also not be a breaking change. Hope you are open to discussing this.

I don't see that as a viable option to change how the default service provider works to plug in a 3rd party container. As far as I'm concerned those are implementation details of the default container.

davidfowl commented May 15, 2018

Are scopes universes also?

No. Scopes are very simple. The scoped IServiceProvider is just a disposable root for scoped and transient services. If something is declared as scoped, then it's a singleton within the scope container it gets resolved from (instance per lifetime scope in autofac speak).

I would love to explore this a little more. Scopes are completely incompatible between our containers.

public interface IThing  { }

public class Thing : IThing, IDisposable
{
   public void Dispose() => Console.WriteLine("Thing.Dispose()");
}

var sp = new ServiceCollection().AddScoped<IThing, Thing>()
                                                       .BuildServiceProvider();

using (var scope = sp.CreateScope())
{
   var thing = scope.ServiceProvider.GetService<IThing>();
   var thingAgain = scope.ServiceProvider.GetService<IThing>();
   
   // These are the same instance since they were resolved from the same scope container
}

When I say different universes I literally mean we create different instances of IServiceProvider:

var universe1 = new ServiceCollection().AddSingleton<IFoo, Foo>().BuildServiceProvider();

var universe2 = new ServiceCollection().AddSingleton<IFoo, Foo>().BuildServiceProvider();

Those are 2 different worlds even if they share the same service descriptors. This is what hosting does because they are stages.

It always mattered. Just getting to it now.

When I say it didn't matter, what I meant is that you get different instances at each stage which is by design. It means if you add a singleton in the hosting container, you get 2 different versions because they are 2 different containers. That's the quirk. It surfaces when you resolve a service from the Startup constructor and then from the Configure method.

I am incredibly interested in the CallSiteRuntimeResolver right now. Is there any way I could override the VisitSingleton method to use a static collection of resolved services? This would probably sort the problems in hosting out and also not be a breaking change. Hope you are open to discussing this.

I don't see that as a viable option to change how the default service provider works to plug in a 3rd party container. As far as I'm concerned those are implementation details of the default container.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 17, 2018

Contributor

No. Scopes are very simple. The scoped IServiceProvider is just a disposable root for scoped and transient services. If something is declared as scoped, then it's a singleton within the scope container it gets resolved from (instance per lifetime scope in autofac speak).

Good explanation.

In Windsor a service that is registered with a scoped lifestyle will throw an exception if it is resolved from the container(or root scope) unless a scope is created first. Scoped instances live for exactly as long as the scope until it gets disposed. Child scopes yield new scoped lifestyle instances. Transient instances will be instantiated N+1 times dependending how many different service types consumed them as dependencies and their disposables are scoped tracked.

Singletons however are scope agnostic. I think this is a sticking point for me.

When I say different universes I literally mean we create different instances of IServiceProvider:

Two containers. Got it.

Those are 2 different worlds even if they share the same service descriptors. This is what hosting does because they are stages.

Stages of what? We have 2 containers here.

When I say it didn't matter, what I meant is that you get different instances at each stage which is by design.

Why have N+1 containers? We tried parent/child containers, it exposed all sorts of problems on our end.

It means if you add a singleton in the hosting container, you get 2 different versions because they are 2 different containers. That's the quirk.

Yup but do new scopes yield new singletons? This is not right.

I don't see that as a viable option to change how the default service provider works to plug in a 3rd party container. As far as I'm concerned those are implementation details of the default container.

https://en.wikipedia.org/wiki/Singleton_pattern

quote end

Hope you understand that there are some fundamentally different things happening when it comes to memory management here. It affects downstream performance heavily if you keep reinstantiating singletons that pull config in the cloud. Hope you will reconsider this approach.

Contributor

fir3pho3nixx commented May 17, 2018

No. Scopes are very simple. The scoped IServiceProvider is just a disposable root for scoped and transient services. If something is declared as scoped, then it's a singleton within the scope container it gets resolved from (instance per lifetime scope in autofac speak).

Good explanation.

In Windsor a service that is registered with a scoped lifestyle will throw an exception if it is resolved from the container(or root scope) unless a scope is created first. Scoped instances live for exactly as long as the scope until it gets disposed. Child scopes yield new scoped lifestyle instances. Transient instances will be instantiated N+1 times dependending how many different service types consumed them as dependencies and their disposables are scoped tracked.

Singletons however are scope agnostic. I think this is a sticking point for me.

When I say different universes I literally mean we create different instances of IServiceProvider:

Two containers. Got it.

Those are 2 different worlds even if they share the same service descriptors. This is what hosting does because they are stages.

Stages of what? We have 2 containers here.

When I say it didn't matter, what I meant is that you get different instances at each stage which is by design.

Why have N+1 containers? We tried parent/child containers, it exposed all sorts of problems on our end.

It means if you add a singleton in the hosting container, you get 2 different versions because they are 2 different containers. That's the quirk.

Yup but do new scopes yield new singletons? This is not right.

I don't see that as a viable option to change how the default service provider works to plug in a 3rd party container. As far as I'm concerned those are implementation details of the default container.

https://en.wikipedia.org/wiki/Singleton_pattern

quote end

Hope you understand that there are some fundamentally different things happening when it comes to memory management here. It affects downstream performance heavily if you keep reinstantiating singletons that pull config in the cloud. Hope you will reconsider this approach.

@greenmooseSE

This comment has been minimized.

Show comment
Hide comment
@greenmooseSE

greenmooseSE May 17, 2018

Why have N+1 containers?

One use case could be integration testing. You might have 1 container for services used by the test code, and 1 container for services used by the application code. So having 2 "singleton" instances would then make sense since you don't pollute the application environment with components registered for the test environment.

greenmooseSE commented May 17, 2018

Why have N+1 containers?

One use case could be integration testing. You might have 1 container for services used by the test code, and 1 container for services used by the application code. So having 2 "singleton" instances would then make sense since you don't pollute the application environment with components registered for the test environment.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 17, 2018

Contributor

One use case could be integration testing. You might have 1 container for services used by the test code, and 1 container for services used by the application code. So having 2 "singleton" instances would then make sense since you don't pollute the application environment with components registered for the test environment.

This happens in aspnet/hosting so this is not test code.

Contributor

fir3pho3nixx commented May 17, 2018

One use case could be integration testing. You might have 1 container for services used by the test code, and 1 container for services used by the application code. So having 2 "singleton" instances would then make sense since you don't pollute the application environment with components registered for the test environment.

This happens in aspnet/hosting so this is not test code.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 17, 2018

Contributor

@davidfowl

I believe we can fix this by passing the root scope to the call run-time resolver for singletons.

Contributor

fir3pho3nixx commented May 17, 2018

@davidfowl

I believe we can fix this by passing the root scope to the call run-time resolver for singletons.

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 17, 2018

Singletons however are scope agnostic. I think this is a sticking point for me.

They have to be, if they weren't, singletons would be the same as scoped. Put another way singletons are scoped within the root container.

Stages of what? We have 2 containers here.

If you look at how hosting wires things up there are 2 phases to Startup:

public class Startup
{
   public Startup(ILogger<Startup> logger) // Startup gets resolved from the first container (the hosting container)
   {
   }

   public void ConfigureServices(IServiceCollection services) // This is the second container definition (the application container)
   {
   }  
   
   public void Configure(IApplicationBuilder app, ILogger<Startup> logger2) { } // We resolve services from the second container here (the application container)
}

Yup but do new scopes yield new singletons? This is not right.

It's not a new scope, its an entirely new container. There's the hosting container which is used to resolve services in the Startup constructor and there's the application container which uses the service collection provided in ConfigureServices to be created.

Hope you understand that there are some fundamentally different things happening when it comes to memory management here. It affects downstream performance heavily if you keep reinstantiating singletons that pull config in the cloud. Hope you will reconsider this approach.

I'm not sure what you mean.

Singletons however are scope agnostic. I think this is a sticking point for me.

They have to be, if they weren't, singletons would be the same as scoped. Put another way singletons are scoped within the root container.

Stages of what? We have 2 containers here.

If you look at how hosting wires things up there are 2 phases to Startup:

public class Startup
{
   public Startup(ILogger<Startup> logger) // Startup gets resolved from the first container (the hosting container)
   {
   }

   public void ConfigureServices(IServiceCollection services) // This is the second container definition (the application container)
   {
   }  
   
   public void Configure(IApplicationBuilder app, ILogger<Startup> logger2) { } // We resolve services from the second container here (the application container)
}

Yup but do new scopes yield new singletons? This is not right.

It's not a new scope, its an entirely new container. There's the hosting container which is used to resolve services in the Startup constructor and there's the application container which uses the service collection provided in ConfigureServices to be created.

Hope you understand that there are some fundamentally different things happening when it comes to memory management here. It affects downstream performance heavily if you keep reinstantiating singletons that pull config in the cloud. Hope you will reconsider this approach.

I'm not sure what you mean.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 17, 2018

Contributor

They have to be, if they weren't, singletons would be the same as scoped. Put another way singletons are scoped within the root container.

But your call site run-time resolver says you resolve singletons as scoped. https://github.com/aspnet/DependencyInjection/blob/dev/src/DI/ServiceLookup/CallSiteRuntimeResolver.cs#L39

Contributor

fir3pho3nixx commented May 17, 2018

They have to be, if they weren't, singletons would be the same as scoped. Put another way singletons are scoped within the root container.

But your call site run-time resolver says you resolve singletons as scoped. https://github.com/aspnet/DependencyInjection/blob/dev/src/DI/ServiceLookup/CallSiteRuntimeResolver.cs#L39

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 17, 2018

Contributor

It's not a new scope, its an entirely new container

New scope = new container?

Contributor

fir3pho3nixx commented May 17, 2018

It's not a new scope, its an entirely new container

New scope = new container?

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 18, 2018

New scope = new container?

No, a new scope is not a new container. We should stop conflating these things.

This is a new container:

var sp1 = new ServiceCollection()
           .AddScoped<ScopedService>()
           .BuildServiceProvider();

var sp2 = new ServiceCollection()
           .AddSingleton<SingletonService>()
           .BuildServiceProvider();

sp1 and sp2 are different containers, they don't share service descriptors, they are independent of each other. They are both distinct top level root containers and can each create their own scopes independently.

But your call site run-time resolver says you resolve singletons as scoped. https://github.com/aspnet/DependencyInjection/blob/dev/src/DI/ServiceLookup/CallSiteRuntimeResolver.cs#L39

Lets not discuss the implementation details of the default container. That just makes the conversation more confusing.

Let me try to explain again:

  • Scoped services are singletons within a specific scoped container instance.
  • Singleton services are the same instance across all scoped containers.

Example:

var sp  = new ServiceCollection()
             .AddSingleton<SingletonService>()
             .BuildServiceProvider();

var singleton1 = sp.GetService<SingletonService>();

using (var scoped = sp.CreateScope())
{
    var singleton2 = scoped.ServiceProvider.GetService<SingletonService>();
    Assert.Same(singleton1, singleton2);
}

In the above code snippet, the singleton object is the exact same object even though it singleton1 was resolved from the root container (sp) and singleton2 was resolved from the scoped container.

Here's an example of scoped services:

var sp  = new ServiceCollection()
             .AddScoped<ScopedService>()
             .BuildServiceProvider();

using (var scoped = sp.CreateScope())
{
    var scoped1 = scoped.ServiceProvider.GetService<ScopedService>();
    var scoped2 = scoped.ServiceProvider.GetService<ScopedService>();
    Assert.Same(scoped1, scoped2);
}

using (var scoped = sp.CreateScope())
{
    // New instance
    var scoped3 = scoped.ServiceProvider.GetService<ScopedService>();
    var scoped4 = scoped.ServiceProvider.GetService<ScopedService>();
    Assert.Same(scoped3, scoped4);
}

// Resolving a scoped service from the root container will throw by default in ASP.NET Core because
// ValidateScopes is on (a feature of the default container)
sp.GetService<ScopedService>(); 

New scope = new container?

No, a new scope is not a new container. We should stop conflating these things.

This is a new container:

var sp1 = new ServiceCollection()
           .AddScoped<ScopedService>()
           .BuildServiceProvider();

var sp2 = new ServiceCollection()
           .AddSingleton<SingletonService>()
           .BuildServiceProvider();

sp1 and sp2 are different containers, they don't share service descriptors, they are independent of each other. They are both distinct top level root containers and can each create their own scopes independently.

But your call site run-time resolver says you resolve singletons as scoped. https://github.com/aspnet/DependencyInjection/blob/dev/src/DI/ServiceLookup/CallSiteRuntimeResolver.cs#L39

Lets not discuss the implementation details of the default container. That just makes the conversation more confusing.

Let me try to explain again:

  • Scoped services are singletons within a specific scoped container instance.
  • Singleton services are the same instance across all scoped containers.

Example:

var sp  = new ServiceCollection()
             .AddSingleton<SingletonService>()
             .BuildServiceProvider();

var singleton1 = sp.GetService<SingletonService>();

using (var scoped = sp.CreateScope())
{
    var singleton2 = scoped.ServiceProvider.GetService<SingletonService>();
    Assert.Same(singleton1, singleton2);
}

In the above code snippet, the singleton object is the exact same object even though it singleton1 was resolved from the root container (sp) and singleton2 was resolved from the scoped container.

Here's an example of scoped services:

var sp  = new ServiceCollection()
             .AddScoped<ScopedService>()
             .BuildServiceProvider();

using (var scoped = sp.CreateScope())
{
    var scoped1 = scoped.ServiceProvider.GetService<ScopedService>();
    var scoped2 = scoped.ServiceProvider.GetService<ScopedService>();
    Assert.Same(scoped1, scoped2);
}

using (var scoped = sp.CreateScope())
{
    // New instance
    var scoped3 = scoped.ServiceProvider.GetService<ScopedService>();
    var scoped4 = scoped.ServiceProvider.GetService<ScopedService>();
    Assert.Same(scoped3, scoped4);
}

// Resolving a scoped service from the root container will throw by default in ASP.NET Core because
// ValidateScopes is on (a feature of the default container)
sp.GetService<ScopedService>(); 
@quetzalcoatl

This comment has been minimized.

Show comment
Hide comment
@quetzalcoatl

quetzalcoatl May 18, 2018

Stages of what? We have 2 containers here.

If you look at how hosting wires things up there are 2 phases to Startup:

public class Startup
{
   // Startup gets resolved from the first container (the hosting container)
   public Startup(ILogger<Startup> logger)

   // This is the second container definition (the application container)
   public void ConfigureServices(IServiceCollection services)

Sorry for kicking in, but to make sure - so typical WebHost creation like

            return WebHost.CreateDefaultBuilder(args)
                    .UseConfiguration(configuration)
                    .UseStartup<Startup>()
                    .Build();

creates and uses "the first container (the hosting container)" just to help in creating the Startup object, and then (more or less) forgets the container and then, only after creation of the instance of Startup, we lets the Startup bootstrap its own, new, second container instance "(the application container)" thats later used through out the app's lifetime?

That would explain some confusing things I've seen when I tried to setup some common logging for app startup and app runtime.. Ok, but then, once application starts, what happens to the first container? is it totally forgotten and left for GC to cleanup, or is it kept and later something other than Startup is resolved from it as well?

quetzalcoatl commented May 18, 2018

Stages of what? We have 2 containers here.

If you look at how hosting wires things up there are 2 phases to Startup:

public class Startup
{
   // Startup gets resolved from the first container (the hosting container)
   public Startup(ILogger<Startup> logger)

   // This is the second container definition (the application container)
   public void ConfigureServices(IServiceCollection services)

Sorry for kicking in, but to make sure - so typical WebHost creation like

            return WebHost.CreateDefaultBuilder(args)
                    .UseConfiguration(configuration)
                    .UseStartup<Startup>()
                    .Build();

creates and uses "the first container (the hosting container)" just to help in creating the Startup object, and then (more or less) forgets the container and then, only after creation of the instance of Startup, we lets the Startup bootstrap its own, new, second container instance "(the application container)" thats later used through out the app's lifetime?

That would explain some confusing things I've seen when I tried to setup some common logging for app startup and app runtime.. Ok, but then, once application starts, what happens to the first container? is it totally forgotten and left for GC to cleanup, or is it kept and later something other than Startup is resolved from it as well?

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 18, 2018

creates and uses "the first container (the hosting container)" just to help in creating the Startup object, and then (more or less) forgets the container and then, only after creation of the instance of Startup, we lets the Startup bootstrap its own, new, second container instance "(the application container)" thats later used through out the app's lifetime?

@quetzalcoatl that's absolutely correct.

That would explain some confusing things I've seen when I tried to setup some common logging for app startup and app runtime..

Yes, it's a PITA.

Ok, but then, once application starts, what happens to the first container? is it totally forgotten and left for GC to cleanup, or is it kept and later something other than Startup is resolved from it as well?

It sticks around until the WebHost is disposed but nothing else is ever resolved from it (it could really be disposed earlier).

creates and uses "the first container (the hosting container)" just to help in creating the Startup object, and then (more or less) forgets the container and then, only after creation of the instance of Startup, we lets the Startup bootstrap its own, new, second container instance "(the application container)" thats later used through out the app's lifetime?

@quetzalcoatl that's absolutely correct.

That would explain some confusing things I've seen when I tried to setup some common logging for app startup and app runtime..

Yes, it's a PITA.

Ok, but then, once application starts, what happens to the first container? is it totally forgotten and left for GC to cleanup, or is it kept and later something other than Startup is resolved from it as well?

It sticks around until the WebHost is disposed but nothing else is ever resolved from it (it could really be disposed earlier).

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 20, 2018

Contributor

@davidfowl

You have cleared up quite a bit, I managed to clear up quite a few things in my PR as a result. Disposables are working properly and I don't have any issues with singletons anymore. The last thing I need to look at is why enabling scope validation in the facility is triggering exceptions.

Everything you have said so far is making perfect sense. I not sure what stopped us from conforming the first time, we had all sorts of problems making those tests pass but that was a while ago. I might take another look at this later on. Thanks for explaining. 👍

Contributor

fir3pho3nixx commented May 20, 2018

@davidfowl

You have cleared up quite a bit, I managed to clear up quite a few things in my PR as a result. Disposables are working properly and I don't have any issues with singletons anymore. The last thing I need to look at is why enabling scope validation in the facility is triggering exceptions.

Everything you have said so far is making perfect sense. I not sure what stopped us from conforming the first time, we had all sorts of problems making those tests pass but that was a while ago. I might take another look at this later on. Thanks for explaining. 👍

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 26, 2018

Contributor

@davidfowl

We are going ahead with this cross-wiring non-conforming thing for now (I do know there is a problem with scopes just not clear on why yet). I do think our next issue needs to be a conforming one. I am not going to try and address this here because this is an issue already has a long tail. Thank you for your input, look forward to to making this happen.

@dotnetjunkie

I think we have cleared up most of the misconceptions I have had. Thank you for your input.

@ALL

If you can see something I missed @ PR: #394 please chime in. @generik0 this includes you.

Latest artifacts are here: https://ci.appveyor.com/project/castleproject/windsor/build/0.0.423/artifacts

Contributor

fir3pho3nixx commented May 26, 2018

@davidfowl

We are going ahead with this cross-wiring non-conforming thing for now (I do know there is a problem with scopes just not clear on why yet). I do think our next issue needs to be a conforming one. I am not going to try and address this here because this is an issue already has a long tail. Thank you for your input, look forward to to making this happen.

@dotnetjunkie

I think we have cleared up most of the misconceptions I have had. Thank you for your input.

@ALL

If you can see something I missed @ PR: #394 please chime in. @generik0 this includes you.

Latest artifacts are here: https://ci.appveyor.com/project/castleproject/windsor/build/0.0.423/artifacts

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 27, 2018

If you need any more help or clarification let me know. Castle Windsor is a popular container and it would be huge if an adapter could be built.

If you need any more help or clarification let me know. Castle Windsor is a popular container and it would be huge if an adapter could be built.

@cayroso

This comment has been minimized.

Show comment
Hide comment
@cayroso

cayroso May 28, 2018

one of the reasons that was preventing me
from taking brown field projects (upgrading from mvc5 to core)
is not being able to use castle windsor.

it seems that it's going to be possible now. i'm excited
thank you guys!. :)

cayroso commented May 28, 2018

one of the reasons that was preventing me
from taking brown field projects (upgrading from mvc5 to core)
is not being able to use castle windsor.

it seems that it's going to be possible now. i'm excited
thank you guys!. :)

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 29, 2018

@hikalkan looking a your impl, it seems as though scoping is the hardest thing to implement right? A cursory glance tells me there's no built in unit of work style scope that isn't tied to some ambient state (call context, async local etc). I haven't looked but I'm also guessing there's no support for child containers or nested containers in windsor? This is the model that most closely matches the semantics of Microsoft.Extensions.DependencyInjection:

@hikalkan looking a your impl, it seems as though scoping is the hardest thing to implement right? A cursory glance tells me there's no built in unit of work style scope that isn't tied to some ambient state (call context, async local etc). I haven't looked but I'm also guessing there's no support for child containers or nested containers in windsor? This is the model that most closely matches the semantics of Microsoft.Extensions.DependencyInjection:

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx May 29, 2018

Contributor

A cursory glance tells me there's no built in unit of work style scope that isn't tied to some ambient state (call context, async local etc)

We use async local for scoped ambient state here: https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/MicroKernel/Lifestyle/Scoped/CallContextLifetimeScope.cs#L48

I haven't looked but I'm also guessing there's no support for child containers or nested containers in windsor

We have them: https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/Windsor/IWindsorContainer.cs#L56

They behave wierdly in some cases. Wont pollute with detail.

Contributor

fir3pho3nixx commented May 29, 2018

A cursory glance tells me there's no built in unit of work style scope that isn't tied to some ambient state (call context, async local etc)

We use async local for scoped ambient state here: https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/MicroKernel/Lifestyle/Scoped/CallContextLifetimeScope.cs#L48

I haven't looked but I'm also guessing there's no support for child containers or nested containers in windsor

We have them: https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/Windsor/IWindsorContainer.cs#L56

They behave wierdly in some cases. Wont pollute with detail.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
Contributor

fir3pho3nixx commented May 29, 2018

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl May 30, 2018

We use async local for scoped ambient state here: https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/MicroKernel/Lifestyle/Scoped/CallContextLifetimeScope.cs#L48

Right, I was playing around with it last night and there seems to be a few gaps with the build in scope that https://github.com/volosoft/castle-windsor-ms-adapter/blob/master/src/Castle.Windsor.MsDependencyInjection/MsLifeTimeScope.cs#L32 tries to address:

  • Disposing transients, transient disposable services resolved in a scope are disposed (is that configurable?)
  • Disposable order

It's using async local because there's no way to set enough state from the call site of the resolution itself so that you can get it back when the custom scope infrastructure calls you back. Here's my super basic example of one of the problems:

using System;
using Castle.MicroKernel.Context;
using Castle.MicroKernel.Lifestyle;
using Castle.MicroKernel.Lifestyle.Scoped;
using Castle.MicroKernel.Registration;
using Castle.Windsor;

namespace windsor_tests
{
    class Program
    {
        static void Main(string[] args)
        {
            var container = new WindsorContainer();
            container.Register(Component.For<IFoo>().ImplementedBy<Foo>().LifestyleCustom<LifetimeObjectScopeLifestyleManager>());

            // This object represents the lifetime of resolved services. Anything disposable component associated with this lifetimte object
            // should be disposed when it is disposed
            
            using (var lifetime = new DefaultLifetimeScope())
            {
                container.Resolve<IFoo>();
            }
        }
    }
    public class LifetimeObjectScopeLifestyleManager : ScopedLifestyleManager
    {
        public LifetimeObjectScopeLifestyleManager() : base(new LifetimeObjectScopeAccessor())
        {
        }
    }

    public class LifetimeObjectScopeAccessor : IScopeAccessor
    {
        public void Dispose()
        {
            // TODO: Dispose the lifetime instance
        }

        public ILifetimeScope GetScope(CreationContext context)
        {
            // How do I get the lifetime instance here?
        }
    }

    class Foo : IFoo, IDisposable
    {
        public Foo()
        {
            System.Console.WriteLine("Created Foo");
        }

        public void Dispose()
        {
            System.Console.WriteLine("Disposing Foo");
        }
    }

    interface IFoo
    {
    }
}

Ideally, I could shove the DefaultLifetimeScope into the CreationContext so that it was accessible in LifetimeObjectScopeAccessor.GetScope. It would also need to be available when disposing.

I'm sure this isn't the only problem, and I don't really know that much about Castle Windsor so feel free to call me out on anything I misunderstood.

We use async local for scoped ambient state here: https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/MicroKernel/Lifestyle/Scoped/CallContextLifetimeScope.cs#L48

Right, I was playing around with it last night and there seems to be a few gaps with the build in scope that https://github.com/volosoft/castle-windsor-ms-adapter/blob/master/src/Castle.Windsor.MsDependencyInjection/MsLifeTimeScope.cs#L32 tries to address:

  • Disposing transients, transient disposable services resolved in a scope are disposed (is that configurable?)
  • Disposable order

It's using async local because there's no way to set enough state from the call site of the resolution itself so that you can get it back when the custom scope infrastructure calls you back. Here's my super basic example of one of the problems:

using System;
using Castle.MicroKernel.Context;
using Castle.MicroKernel.Lifestyle;
using Castle.MicroKernel.Lifestyle.Scoped;
using Castle.MicroKernel.Registration;
using Castle.Windsor;

namespace windsor_tests
{
    class Program
    {
        static void Main(string[] args)
        {
            var container = new WindsorContainer();
            container.Register(Component.For<IFoo>().ImplementedBy<Foo>().LifestyleCustom<LifetimeObjectScopeLifestyleManager>());

            // This object represents the lifetime of resolved services. Anything disposable component associated with this lifetimte object
            // should be disposed when it is disposed
            
            using (var lifetime = new DefaultLifetimeScope())
            {
                container.Resolve<IFoo>();
            }
        }
    }
    public class LifetimeObjectScopeLifestyleManager : ScopedLifestyleManager
    {
        public LifetimeObjectScopeLifestyleManager() : base(new LifetimeObjectScopeAccessor())
        {
        }
    }

    public class LifetimeObjectScopeAccessor : IScopeAccessor
    {
        public void Dispose()
        {
            // TODO: Dispose the lifetime instance
        }

        public ILifetimeScope GetScope(CreationContext context)
        {
            // How do I get the lifetime instance here?
        }
    }

    class Foo : IFoo, IDisposable
    {
        public Foo()
        {
            System.Console.WriteLine("Created Foo");
        }

        public void Dispose()
        {
            System.Console.WriteLine("Disposing Foo");
        }
    }

    interface IFoo
    {
    }
}

Ideally, I could shove the DefaultLifetimeScope into the CreationContext so that it was accessible in LifetimeObjectScopeAccessor.GetScope. It would also need to be available when disposing.

I'm sure this isn't the only problem, and I don't really know that much about Castle Windsor so feel free to call me out on anything I misunderstood.

@hikalkan

This comment has been minimized.

Show comment
Hide comment
@hikalkan

hikalkan May 30, 2018

Hi @davidfowl, @fir3pho3nixx and all others :)

it seems as though scoping is the hardest thing to implement right?

Yes, definitely. Because Windsor does not have such a scoping feature natively. I implemented it using AsyncLocal and created ScopedWindsorServiceProvider. The secret code is:

https://github.com/volosoft/castle-windsor-ms-adapter/blob/80964d709cd48db208387c04ecf664605f7f2ff6/src/Castle.Windsor.MsDependencyInjection/ScopedWindsorServiceProvider.cs#L43-L69

This ensures to relate the resolved transient and scoped services to the current service scope, so it can dispose them when the service scope is disposed.

hikalkan commented May 30, 2018

Hi @davidfowl, @fir3pho3nixx and all others :)

it seems as though scoping is the hardest thing to implement right?

Yes, definitely. Because Windsor does not have such a scoping feature natively. I implemented it using AsyncLocal and created ScopedWindsorServiceProvider. The secret code is:

https://github.com/volosoft/castle-windsor-ms-adapter/blob/80964d709cd48db208387c04ecf664605f7f2ff6/src/Castle.Windsor.MsDependencyInjection/ScopedWindsorServiceProvider.cs#L43-L69

This ensures to relate the resolved transient and scoped services to the current service scope, so it can dispose them when the service scope is disposed.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx Jun 7, 2018

Contributor

@hikalkan Of course! Modelling the provider impedance mismatches was definitely the right way to go. You really kicked some ass leading this(I followed the same path and reached the same conclusion, I do know this is not the complexity Castle wants to own). Could you kindly provide some more context around your code quote?

using (MsLifetimeScope.Using(OwnMsLifetimeScope))
{
	var isAlreadyInResolving = IsInResolving;

	if (!isAlreadyInResolving)
	{
		IsInResolving = true;
	}

	object instance = null;
	try
	{
		return instance = ResolveInstanceOrNull(serviceType, isOptional);
	}
	finally
	{
		if (!isAlreadyInResolving)
		{
			if (instance != null)
			{
				OwnMsLifetimeScope?.AddInstance(instance);
			}

			IsInResolving = false;
		}
	}
}
Contributor

fir3pho3nixx commented Jun 7, 2018

@hikalkan Of course! Modelling the provider impedance mismatches was definitely the right way to go. You really kicked some ass leading this(I followed the same path and reached the same conclusion, I do know this is not the complexity Castle wants to own). Could you kindly provide some more context around your code quote?

using (MsLifetimeScope.Using(OwnMsLifetimeScope))
{
	var isAlreadyInResolving = IsInResolving;

	if (!isAlreadyInResolving)
	{
		IsInResolving = true;
	}

	object instance = null;
	try
	{
		return instance = ResolveInstanceOrNull(serviceType, isOptional);
	}
	finally
	{
		if (!isAlreadyInResolving)
		{
			if (instance != null)
			{
				OwnMsLifetimeScope?.AddInstance(instance);
			}

			IsInResolving = false;
		}
	}
}
@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx Jun 7, 2018

Contributor

@davidfowl I need more time to respond to this (sorry man I know what you are asking for). Please bear with me.

Contributor

fir3pho3nixx commented Jun 7, 2018

@davidfowl I need more time to respond to this (sorry man I know what you are asking for). Please bear with me.

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl Jun 8, 2018

@davidfowl I need more time to respond to this (sorry man I know what you are asking for). Please bear with me.

@fir3pho3nixx it's cool. I have a suggestion on how to fix it after you respond but I haven't dug into the code to see how hard it would be to implement.

@hikalkan Of course! Modelling the provider impedance mismatches was definitely the right way to go. You really kicked some ass leading this(I followed the same path and reached the same conclusion, I do know this is not the complexity Castle wants to own). Could you kindly provide some more context around your code quote?

The code probably doesn't need to be that complex. For example there is no nested scope requirement. Scopes can be at a single level they don't need to support overlapping. Because of the ambient state model though I see why you feel the need to handle it. Consider this:

using (var lifetime = new DefaultLifetimeScope())
using (var lifetime = new DefaultLifetimeScope())
{
    container.Resolve<IFoo>();
}

There's no way to resolve from a specific scope there. The last one will win because it's the most recent. This might be an edge case though but somebody could run into it.

@davidfowl I need more time to respond to this (sorry man I know what you are asking for). Please bear with me.

@fir3pho3nixx it's cool. I have a suggestion on how to fix it after you respond but I haven't dug into the code to see how hard it would be to implement.

@hikalkan Of course! Modelling the provider impedance mismatches was definitely the right way to go. You really kicked some ass leading this(I followed the same path and reached the same conclusion, I do know this is not the complexity Castle wants to own). Could you kindly provide some more context around your code quote?

The code probably doesn't need to be that complex. For example there is no nested scope requirement. Scopes can be at a single level they don't need to support overlapping. Because of the ambient state model though I see why you feel the need to handle it. Consider this:

using (var lifetime = new DefaultLifetimeScope())
using (var lifetime = new DefaultLifetimeScope())
{
    container.Resolve<IFoo>();
}

There's no way to resolve from a specific scope there. The last one will win because it's the most recent. This might be an edge case though but somebody could run into it.

@fir3pho3nixx

This comment has been minimized.

Show comment
Hide comment
@fir3pho3nixx

fir3pho3nixx Jul 3, 2018

Contributor

Guys/Gals, I am closing this in light of PR: #394

@jonorossi Thanks for reviewing and cleaning up all my misconceptions about Windsor. You are a fucking legend.

@cwe1ss Thank you for raising the issue.

@hikalkan Thanks for leading the way on https://github.com/volosoft/castle-windsor-ms-adapter. Seriously man, good work.

@dotnetjunkie You informed and led this process from eons ago. Thank you.

@davidfowl Thank you for engaging us. Hope you don't mind if we call on you in the future. Seriously thank you.

This issue only took 2 years, 4 months, 18 days excluding the end date to solve(kind of).

Contributor

fir3pho3nixx commented Jul 3, 2018

Guys/Gals, I am closing this in light of PR: #394

@jonorossi Thanks for reviewing and cleaning up all my misconceptions about Windsor. You are a fucking legend.

@cwe1ss Thank you for raising the issue.

@hikalkan Thanks for leading the way on https://github.com/volosoft/castle-windsor-ms-adapter. Seriously man, good work.

@dotnetjunkie You informed and led this process from eons ago. Thank you.

@davidfowl Thank you for engaging us. Hope you don't mind if we call on you in the future. Seriously thank you.

This issue only took 2 years, 4 months, 18 days excluding the end date to solve(kind of).

@castleproject castleproject locked as resolved and limited conversation to collaborators Jul 3, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.