Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support for .NET Core 3.x #517

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
de1621e
Add support for .NET Core 3.x as a container for IServiceProvider
ltines Apr 15, 2020
f723d87
Add readme
ltines Apr 15, 2020
b6655d5
Fix renaming
ltines Apr 15, 2020
90f31a2
Add to solution
ltines Apr 15, 2020
864e3c6
Remove the need for InternalsVisible
ltines Apr 15, 2020
fb8365c
Remove Nesting and fixed layout
ltines Apr 19, 2020
e8a766a
Fix layout
ltines Apr 19, 2020
d2e2c0a
Remove warning about target framework
ltines Apr 19, 2020
0c1ca73
Small fixes for coding style
ltines Apr 19, 2020
858c4b7
Fix typos
ltines Apr 22, 2020
de7e469
Fix another typo
ltines Apr 22, 2020
d620c7f
Move starting root scope bit earilier
ltines Apr 24, 2020
92891b7
Fix accessing root scope from different thread
ltines Apr 25, 2020
70743f1
Adding .NET 4.6.1 + some explicit dependendecies
ltines May 3, 2020
0c94508
Add comment why we're using MS namespace
ltines May 3, 2020
fea6656
Typos and intendation
ltines May 3, 2020
4f0d338
Remove coverage dependency
ltines May 3, 2020
043fcdb
Fix targets
ltines May 3, 2020
9557c87
Add tests for net461
ltines May 3, 2020
e1b13b8
Rename project and fix intendation to tabs
ltines May 24, 2020
240c9f9
add both restore and tests to build
ltines May 24, 2020
e5537c7
Reverse order of resolved collection.
ltines May 25, 2020
70586b5
Keep registration order of instances when resolving a collection
ltines Jun 6, 2020
e79a539
Remove NetCore from names and split code into folders
ltines Jun 6, 2020
5699f61
Fix intendation and remove debug test
ltines Jun 7, 2020
598740a
Cosmetic changes
ltines Jun 13, 2020
be676fa
Fix rename
ltines Jun 13, 2020
21da7e0
Update docu
ltines Jun 13, 2020
6b86d13
Move to docs
ltines Jun 15, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2004-2020 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.Windsor.Extensions.DependencyInjection.Tests
{
using System;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>Castle.Windsor.Extensions.DependencyInjection</PackageId>
<Title>Castle Windsor extension for .NET Core Dependency Injection </Title>
<Title>Castle Windsor extension for .NET Extensions DependencyInjection</Title>
<Description>Allows to use Castle Windsor as a container using IServiceProvider</Description>
<PackageTags>castle, windsor, inversionOfControl, DependencyInjection, aspnet, core</PackageTags>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Expand All @@ -19,7 +19,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.3" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -16,12 +16,12 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Extensions
{
using System;
using System.Reflection;

using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Castle.Windsor.Extensions.DependencyInjection.Resolvers;
using Castle.Windsor.Extensions.DependencyInjection.Resolvers;

using Microsoft.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
Expand All @@ -34,7 +34,7 @@ public static IWindsorContainer CreateContainer(this IServiceCollection serviceC
new SubSystems.DependencyInjectionNamingSubsystem()
);

if(serviceCollection == null)
if (serviceCollection == null)
{
return container;
}
Expand All @@ -46,7 +46,7 @@ public static IWindsorContainer CreateContainer(this IServiceCollection serviceC
Component
.For<IServiceProvider, ISupportRequiredService>()
.ImplementedBy<WindsorScopedServiceProvider>()
.LifeStyle.ScopedToNetCoreScope(),
.LifeStyle.ScopedToNetServiceScope(),
Component
.For<IServiceScopeFactory>()
.ImplementedBy<WindsorScopeFactory>()
Expand All @@ -61,25 +61,25 @@ public static IWindsorContainer CreateContainer(this IServiceCollection serviceC
container.Kernel.Resolver.AddSubResolver(new OptionsSubResolver(container.Kernel));
container.Kernel.Resolver.AddSubResolver(new LoggerDependencyResolver(container.Kernel));

foreach(var service in serviceCollection)
foreach (var service in serviceCollection)
{
container.Register(service.CreateWindsorRegistration());
}

return container;
}


public static IRegistration CreateWindsorRegistration(this Microsoft.Extensions.DependencyInjection.ServiceDescriptor service)
{
if(service.ServiceType.ContainsGenericParameters)
if (service.ServiceType.ContainsGenericParameters)
{
return RegistrationAdapter.FromOpenGenericServiceDescriptor(service);
}
else
{
var method = typeof(RegistrationAdapter).GetMethod("FromServiceDescriptor", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(service.ServiceType);
return method.Invoke(null, new object[] {service}) as IRegistration;
return method.Invoke(null, new object[] { service }) as IRegistration;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -15,19 +15,19 @@
namespace Castle.Windsor.Extensions.DependencyInjection.Extensions
{
using System;

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.Registration.Lifestyle;

using Castle.Windsor.Extensions.DependencyInjection.Scope;
using Castle.Windsor.Extensions.DependencyInjection.Scope;

public static class WindsorExtensions
public static class WindsorExtensions
{
/// <summary>
/// Scopes the lifestyle of the component to a scope started by <see name="IServiceScopeFactory.CreateScope" />
/// </summary>
/// <typeparam name="TService">Service type</typeparam>
public static ComponentRegistration<TService> ScopedToNetCoreScope<TService>(this LifestyleGroup<TService> lifestyle) where TService : class
public static ComponentRegistration<TService> ScopedToNetServiceScope<TService>(this LifestyleGroup<TService> lifestyle) where TService : class
{
return lifestyle.Scoped<ExtensionContainerScopeAccessor>();
}
Expand All @@ -36,18 +36,18 @@ public static ComponentRegistration<TService> ScopedToNetCoreScope<TService>(thi
/// Returns new instances everytime it's resolved but disposes it on <see name="IServiceScope" /> end
/// </summary>
/// <typeparam name="TService">Service type</typeparam>
public static ComponentRegistration<TService> LifestyleNetCoreTransient<TService>(this ComponentRegistration<TService> registration) where TService : class
public static ComponentRegistration<TService> LifestyleNetTransient<TService>(this ComponentRegistration<TService> registration) where TService : class
{
return registration
.Attribute(ExtensionContainerScope.TransientMarker).Eq(Boolean.TrueString)
.LifeStyle.ScopedToNetCoreScope(); //.NET core expects new instances but release on scope dispose
.LifeStyle.ScopedToNetServiceScope(); //.NET core expects new instances but release on scope dispose
}

/// <summary>
/// Singleton instance with .NET Core semantics
/// </summary>
/// <typeparam name="TService"></typeparam>
public static ComponentRegistration<TService> NetCoreStatic<TService>(this LifestyleGroup<TService> lifestyle) where TService : class
public static ComponentRegistration<TService> NetStatic<TService>(this LifestyleGroup<TService> lifestyle) where TService : class
{
return lifestyle
.Scoped<ExtensionContainerRootScopeAccessor>();
Expand Down
52 changes: 33 additions & 19 deletions src/Castle.Windsor.Extensions.DependencyInjection/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
# windsor-service-provider
Service Provider using Castle Windsor container for ASP.NET Core 3.x. Fully replaces ASP.NET Core Default Service Provider (container). Supports both registering services
* using `IServiceCollection` (`Startup.ConfigureServices`) - these get translated to Castle Windsor registrations
* `Startup.ConfigureContainer`
```
public void ConfigureContainer (IWindsorContainer container)
{
container.Install(new MyInstaller());
}
```
# .NET Core 3.0 Dependency injection extension
jonorossi marked this conversation as resolved.
Show resolved Hide resolved
Service Provider using Castle Windsor container for ASP.NET Core 3.x. Fully replaces ASP.NET Core Default Service Provider (container).

## How does it work?

### Registering services
You can register service either `IServiceCollection` in startup class. Just as you would with standard .NET Dependency Injection add your registration to the `Startup` class.

Alternatively you can create `ConfigureServices` method in `Startup` class that gets passed the `IWindsorContainer` instance and register the services there
```
public void ConfigureContainer (IWindsorContainer container)
{
container.Install(new MyInstaller());
}
```

To match lifestyle of the registered components to .NET Service Scope, use `LifeStyle.ScopedToNetServiceScope()`.

### Services lifestyle
Because there are subtle differences between .NET and Castle Windsor lifestyle semantics services registered via `IServiceCollection` are following .NET semantics:


| .NET lifestyle | Description |
|:-:|:-:|
| Scoped | Lifecycle of the component is bound to the scope associated with `IServiceProvider` that resolved it. Even if it's not the closest one. The instance is disposed when `IServiceScope` is disposed. |
| Transient | New instance is provided when resolved but lifecycle is bound to the scope associated with `IServiceProvider` that resolved it. All instances are disposed when `IServiceScope` is disposed. |
| Singleton | Only one instance ever exists and it's disposed once outermost `IServiceProvider` is disposed (usually application shutdown). |

## Usage
1. Add `UseWindsorContainerServiceProvider()` when creating the Host
## What do I need to set it up?
1. Add `Castle.Windsor.Extensions.DependencyInjection` package to your application.
2. Add `UseWindsorContainerServiceProvider()` when creating the Host
```
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.UseWindsorContainerServiceProvider()
.UseWindsorContainerServiceProvider()
```
This will register `IServiceProviderFactory`
This will register an `IServiceProviderFactory`
2. Any services registred in `Startup.ConfigureServices` will be registered with `IWindsorContainer`. No need to cross-wire since `IWindsorContainer` is the only `IServiceProvider`
3. To match lifecycle your custom components to ASP.NET Core scope, use `LifeStyle.ScopedToNetCoreScope()`. This will use inner-most scope.
4. To access the container inject either `IWindsorContainer` or `IServiceProvider`
4. To access the container directly inject either `IWindsorContainer` or `IServiceProvider`

## License
Apache 2.0
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ namespace Castle.Windsor.Extensions.DependencyInjection
using System;

using Castle.MicroKernel.Registration;
using Castle.Windsor.Extensions.DependencyInjection.Extensions;
using Castle.Windsor.Extensions.DependencyInjection.Extensions;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

internal class RegistrationAdapter
{
Expand Down Expand Up @@ -119,11 +119,11 @@ private static ComponentRegistration<TService> ResolveLifestyle<TService>(Compon
switch(service.Lifetime)
{
case ServiceLifetime.Singleton:
return registration.LifeStyle.NetCoreStatic();
return registration.LifeStyle.NetStatic();
case ServiceLifetime.Scoped:
return registration.LifeStyle.ScopedToNetCoreScope();
return registration.LifeStyle.ScopedToNetServiceScope();
case ServiceLifetime.Transient:
return registration.LifestyleNetCoreTransient();
return registration.LifestyleNetTransient();

default:
throw new System.ArgumentException($"Invalid lifetime {service.Lifetime}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.


namespace Castle.Windsor.Extensions.DependencyInjection.Resolvers
{
using Castle.Core;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// The MIT License (MIT)

// Copyright (c) 2016 Volosoft

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.


namespace Castle.Windsor.Extensions.DependencyInjection.Resolvers
{
using System;
using System.Reflection;
using System;
using System.Reflection;

using Castle.Core;
using Castle.MicroKernel;
Expand All @@ -63,10 +40,7 @@ public bool CanResolve(CreationContext context, ISubDependencyResolver contextHa

public object Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency)
{
var obj = kernel.Resolve(dependency.TargetType);

Console.WriteLine($"opt resolving,\"{model.ComponentName}\",\"{dependency.TargetType}\",{obj}");
return obj;
return kernel.Resolve(dependency.TargetType);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.


namespace Castle.Windsor.Extensions.DependencyInjection.Resolvers
{
using System;
using Castle.Core;
using Castle.Core;
using Castle.MicroKernel;
using Castle.MicroKernel.Context;
using Castle.MicroKernel.Resolvers.SpecializedResolvers;

/// <summary>
/// Use <see name="IKernel.ResolveAll" /> if there is no specific handler for IEnumberable service
/// Use <see name="IKernel.ResolveAll" /> if there is no specific handler for IEnumerable service
/// </summary>
public class RegisteredCollectionResolver : CollectionResolver
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ public Burden GetCachedInstance(ComponentModel model, ScopedInstanceActivationCa
if(model.Configuration.Attributes.Get(TransientMarker) == Boolean.TrueString )
{
var burden = createInstance((_) => {});
var existingBurden = scopeCache[burden];
Console.WriteLine($"transient,\"{model.ComponentName}\",{existingBurden != null}");
scopeCache[burden] = burden;
return burden;
}
Expand All @@ -76,13 +74,9 @@ public Burden GetCachedInstance(ComponentModel model, ScopedInstanceActivationCa
var burden = scopeCache[model];
if (burden == null)
{
Console.WriteLine($"create,\"{model.ComponentName}\"");
scopeCache[model] = burden = createInstance((_) => {});
}
else
{
Console.WriteLine($"cached,\"{model.ComponentName}\"");
}

return burden;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.


namespace Castle.Windsor.Extensions.DependencyInjection.SubSystems
{
using Castle.MicroKernel;
using Castle.MicroKernel.SubSystems.Naming;
using Castle.MicroKernel.Util;
using Castle.MicroKernel;
using Castle.MicroKernel.SubSystems.Naming;
using Castle.MicroKernel.Util;
using System;
jonorossi marked this conversation as resolved.
Show resolved Hide resolved
using System.Collections.Generic;
using System.Reflection;
using System.Collections.Generic;
using System.Reflection;

internal class DependencyInjectionNamingSubsystem : DefaultNamingSubSystem
/// <summary>
/// Naming subsystem based on DefaultNamingSubSystem but GetHandlers returns handlers in registration order
/// </summary>
internal class DependencyInjectionNamingSubsystem : DefaultNamingSubSystem
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this sub-system to an existing container wipes out all its components. Furthermore, the sub-system looks virtually identical to the base implementation.

Can someone tell me why this is needed for .NET core 3.1? Is there something different about .NET core 3.1 that requires this particular class? I've compiled my own version that doesn't include this sub-system, just to test, and it seems to work the same way regardless.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @robertcoltheart
I tried removing the subsystem when i refactored some of this code. From what I remembered it made all or many if the unit tests fail.

If you look at the new implementation you on master you will see you can override the sub system.

Could you please make a new issue, and reference the current implementation and we can look deeper into it together?

{
private readonly IDictionary<Type, IHandler[]> handlerListsRegistrationOrderByTypeCache =
new Dictionary<Type, IHandler[]>(SimpleTypeEqualityComparer.Instance);
Expand Down
Loading