-
Notifications
You must be signed in to change notification settings - Fork 457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update Castle.Windsor.Extensions.DependencyInjection to support .NET8 #668
base: master
Are you sure you want to change the base?
Conversation
…removed null check on scope cache (AsyncLocal can be null on Threads coming from Threadpool.UnsafeQueueUserWorkItem, having no null check was also the original behavior)
Threadpool.UnsafeQueueUserWorkItem.
this improve resolution of singleton object that might not need a root scope, the windsor container should be enough to resolve singletons
scopes from Threadpool.UnsafeQueueUserWorkItem
Major fix was to support keyed registration scenario
The previous handling of root scope is wrong, the code set root scope in an AsyncLocal variable when WindsorServiceProviderFactoryBase was first creatd. The problem arise with kestrel or orleans in .NET 8, they use a Thread Pool that runs code outside AsyncLocal so it will break resolution. It was not possible to reproduce locally, but it was reproduced with production code. A repro for the bug still missing. Also we can support using a Global root scope only if we use only ONE CONTAINER in the .NET core DI, because basic structure does not allow to find the container that is resolving scoped component thus we cannot determin the right root context. Still work to do to support multiple container.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some cleanup to do!
@@ -23,7 +23,7 @@ | |||
</ItemGroup> | |||
|
|||
<ItemGroup Condition="'$(TargetFramework)'=='net6.0'"> | |||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" /> | |||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should support proper multitarget, we decided that only net8.0 should have 8.x references (see Castle.Windsor.Extensions.DependencyInjection)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
buildscripts/common.props
Outdated
@@ -18,7 +18,7 @@ | |||
<Product>Castle Windsor</Product> | |||
<FileVersion>$(BuildVersionNoSuffix)</FileVersion> | |||
<VersionPrefix>$(BuildVersion)</VersionPrefix> | |||
<AssemblyVersion>$(BuildVersionMajor).0.0</AssemblyVersion> | |||
<AssemblyVersion>6.0.0.0</AssemblyVersion> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert this file to original
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted
@@ -25,9 +25,14 @@ internal abstract class ExtensionContainerScopeBase : ILifetimeScope | |||
public static readonly string TransientMarker = "Transient"; | |||
private readonly IScopeCache scopeCache; | |||
|
|||
private static long _counter; | |||
|
|||
public long Counter { get; private set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was added for debugging purposes, we can get rid of this counter
@@ -138,6 +138,10 @@ protected virtual object CreateInstance(CreationContext context, ConstructorCand | |||
protected object CreateInstanceCore(ConstructorCandidate constructor, object[] arguments, Type implType) | |||
{ | |||
object instance; | |||
if (Model.Implementation.FullName.Contains("SiloConnectionFactory")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove this code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Seems there is related issue |
Needed to modify basic Castle.Winsor library to support the ability from IHandler interface to get the current kernel associated with the handler. This is needed to find the correct root scope associated with that kernel instance.
The project uses Windsor 6.0, and after configuring orleans client 8.0, it prompts "This service descriptor is keyed." Your service provider may not support key services |
This pull request introduced keyed registration only in .NET 8, please confirm that your project targets .NET 8. |
Lots of time passed, is there anyone that can comment/reject/accept this Pull Request? |
Yeah, what's taking so long? We're running into the same issues here, this PR fixes that.. |
It would be great to get this support in Castle Windsor. I did notice that this PR has lots of checks like |
From what I know, keyed service provider was introduced in ASP.NET core in .NET 8 and it is not present in previous version. I can be wrong but I was forced to add that directive because code does not compile in .NET 6 (have not tried with 7) |
It was released along with .NET 8, but doesn't require .NET 8. Microsoft.Extensions.DependencyInjection 8.0.0 is supported on multiple frameworks. I've used MS DI with keyed services on .NET Framework just fine. You should just need to update the NuGet package to 8.0.0 in your .NET 6 project and it should compile. |
Perfect, I'll modify the branch as soon as possible. This will force people using castle on .NET 6 to update to version 8 of the DI package but I think that this can be just fine. |
The bug happened when you register a service with one NON keyed component then again with KEYED components. The adapter incorrectly checked only the first returned service for KEYED and returns null. Identified after update to Orleans 8.1.0
If someone used the version locally compiled, I've pushed a fix for a bug that makes impossible to use Microsoft Orleans 8.1.0, the error derived on how the basic Microsoft Dependency injection works with multiple registration of the same NON keyed service. Update the test suite, tests are now green, Microsoft.Orleans 8.1.0 works. |
@jklawrence I do not know if any maintainer is checking this PR. I can do the modification you requested, but this will force people using version 8.0.0 of dependency injection even if they use .NET 6 framework. I do not see any problem but I'd like some maintainer have last word on it. |
If multiple concrete classes are registered in castle, when you resolve castle resolves the first one. With Microsoft DI is the oposite, you want the last registered. The resolution is now fixed to honor IsDefault() because previous code registered every component with IsDefault() if it is registered from the adapter.
If you are interested, I've updated the PR adding a fix that correctly restore the correct use of IsDefault(). In previous version of the adapter all services registered through the adapter with Microsoft DI services registered componente with the IsDefault() (Due to a different rule in resolution). That actually make not possible to correctly use the IsDefault() anymore. Fixed. |
d2042c2
to
b71829c
Compare
We added a new concept, an extendede property that allows the code to understand if the dependency was registered through the adapter (ServiceCollection) or directly through the Container. This allows us to change the resolution rule in case of multiple services registered with the same name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clear out all the other code changes that are not part of this change and look at my comments.
/// Needed to support root scope for multiple container when integrating with .NET 8 | ||
/// DI engine. | ||
/// </summary> | ||
IKernel GetKernel(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a fairly severe breaking change to IHandler
, need to think about this more.
ignore: | ||
sha: [] | ||
merge-message-formats: {} | ||
mode: ContinuousDeployment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep the pull request to just adding support for .NET 8. The diff is 2600+ LOCs.
@@ -1,7 +1,7 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
|
|||
<PropertyGroup> | |||
<TargetFrameworks>netcoreapp3.1;net6.0</TargetFrameworks> | |||
<TargetFrameworks>netcoreapp3.1;net6.0;net8.0</TargetFrameworks> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Others have said we can add support for keyed services without adding another compile target, that sounds like a good idea.
Windsor should then support .NET 6 until at least end of support (November 12, 2024) and then upgrade to the next LTS.
|
||
// // Assert | ||
// Assert.IsType<FakeService>(service); | ||
//} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented out code
...ensions.DependencyInjection.Tests/WindsorScopedServiceProviderCustomWindsorContainerTests.cs
Outdated
Show resolved
Hide resolved
// I don't think we need this lifestyle at all, usual Singleton should be good enough; | ||
// also we maybe don't need the whole rootscope thing. A normal scope set as current should be enough | ||
// otherwise we should revert to static rootscope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO comments
return registration.UsingFactoryMethod((kernel) => { | ||
return registration.UsingFactoryMethod((kernel) => | ||
{ | ||
//TODO: We should register a factory in castle that in turns call the implementation factory?? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO comments
using System; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check out the Castle coding style conventions, they may be a bit strange and different to what is common today, but if we follow them it keeps things more readable and maintainable.
/// <summary>Current scope for the thread. Initial scope will be set when calling BeginRootScope from a ExtensionContainerRootScope instance.</summary> | ||
/// <exception cref="InvalidOperationException">Thrown when there is no scope available.</exception> | ||
internal static ExtensionContainerScopeBase Current | ||
{ | ||
get => current.Value ?? throw new InvalidOperationException("No scope available"); | ||
// AysncLocal can be null in some cases (like Threadpool.UnsafeQueueUserWorkItem) | ||
get => current.Value; // ?? throw new InvalidOperationException("No scope available. Did you forget to call IServiceScopeFactory.CreateScope()?"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented out code
// root scope should be tied to the root IserviceProvider, so | ||
// it has to be disposed with the IserviceProvider to which is tied to | ||
if (!(scope is ExtensionContainerRootScope)) return; | ||
if (disposing) return; | ||
disposing = true; | ||
var disposableScope = scope as IDisposable; | ||
disposableScope?.Dispose(); | ||
// disping the container here is questionable... what if I want to create another IServiceProvider form the factory? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are these comments for?
.NET 6 goes out of support tomorrow and Windsor does not yet properly support .NET 8. What still needs to be done to get this PR over the finish line and get .NET 8 support? |
This Pull Request contains also all modification by Alessandro Giorgetti already made in PR #664 related to issue #646
This Pull Request aim to give Castle.Windsor.Extensions.DependencyInjection full support to .NET 8 new DI model with keyed service. The reason of this Fork was the inability to upgrade ours production code to .NET 8 due to internal error in Castle.Windsor. This problem happens especially with Microsoft Orleans 8 and sometimes code running on Kestrel 8.
We actually start using this code in our production code, because without these modification we have as only option to use Orleans 8 to entirely remove Castle.Windsor from our project (An extemely big work) so this code, starting from today, is running in our solution.
Feel free to ask any information or require modification in code if you feel that something is wrong.
Thanks.