diff --git a/docs/aspnetcore-facility.md b/docs/aspnetcore-facility.md index 524bfc8faa..eb27067937 100644 --- a/docs/aspnetcore-facility.md +++ b/docs/aspnetcore-facility.md @@ -8,40 +8,111 @@ The ASP.NET Core facility provides Castle Windsor integration using a custom act ## How does it work? -Custom activators are injected into the ASP.NET Core framework when the `services.AddCastleWindsor(container)` extension is called +Custom activators are injected into the ASP.NET Core framework when the `services.AddWindsor(container)` extension is called from the `ConfigureServices(IServiceCollection services)` method in the `Startup` class. This allows components to be resolved from Windsor -when web requests are made to the server. +when web requests are made to the server for TagHelpers, ViewComponents and Controllers. -This method also adds a sub resolver for dealing with the resolution of ASP.NET Core framework -types. An example might be something like an [ILoggerFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.iloggerfactory?view=aspnetcore-2.0). -It is important to note that this extension also injects custom middleware for the management of scoped lifestyles. This middleware calls -the `WindsorContainer.BeginScope` extension. It will also dispose the scope once the request is completed. +This method also adds a sub resolver for dealing with the resolution of ASP.NET Core framework types. An example might be something like an +[ILoggerFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.iloggerfactory?view=aspnetcore-2.0). +It is important to note that this extension also injects custom middleware for the management of implicitly windsor scoped lifestyles. It will also +dispose the implicit scope once the request is completed. -`Controllers`, `ViewComponents` and `TagHelpers` are registered automatically for you when the `app.UseCastleWindsor(container)` extension -is called from the `Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)` method in the `Startup` class. -Controllers are registered with a scoped lifestyle, and are assumed to be a single instance for the duration of the web request. The `ViewComponents` -and `TagHelpers` however are transient. It is assumed that they will called multiple times for a single request and cannot share state. -If your controllers, view components or taghelpers are in a different assembly, you call this extension multiple times supplying an example -type for which Windsor will then scan that assembly for framework types. +### Registering Controllers, ViewComponents and TagHelpers -If you would like to change the lifestyles of any of these components, then you do not call the `app.UseCastleWindsor(container)` -extension. You can opt for your own conventional registrations instead like so: +`Controllers`, `ViewComponents` and `TagHelpers` are registered automatically for you when the `services.AddWindsor(container)` extension +is called. All components are registered with a scoped lifestyle, and are assumed to be a single instance for the duration of the web request. +You can optionally override the lifestyles for each of these if you prefer using the `options` callback in the example below: ```csharp -container.Register(Classes.FromAssemblyInThisApplication(typeof(TStartup).Assembly) - .BasedOn().LifestyleScoped()); // <- Change lifestyle -container.Register(Classes.FromAssemblyInThisApplication(typeof(TStartup).Assembly) - .BasedOn().LifestyleTransient()); -container.Register(Classes.FromAssemblyInThisApplication(typeof(TStartup).Assembly) - .BasedOn().LifestyleTransient()); +services.AddWindsor(container, opts => +{ + opts.RegisterControllers(typeof(HomeController).Assembly, LifestyleType.Transient); + opts.RegisterTagHelpers(typeof(EmailTagHelper).Assembly, LifestyleType.Transient); + opts.RegisterViewComponents(typeof(AddressComponent).Assembly, LifestyleType.Transient); +}); +``` + +This is also useful if your framework components live in a separate assemblies or are not defined in the same assembly as your web application. +Alternatively if your framework components all live in one assembly and you dont need to change lifestyles then you can simply use the following: + +```csharp +services.AddWindsor(container, opts => +{ + opts.UseEntryAssembly(typeof(HomeController).Assembly); +}); +``` +This is good for trouble shooting situations where nothing get's registered because of problems in the hosting environment where +GetEntryAssembly/GetCallingAssembly does not work as expected. + +### Cross Wiring into the IServiceCollection + +There is an additional feature you can use to `Cross Wire` components into the IServiceCollection. This is useful +for cases where the framework needs to know how to resolve a component from Windsor. An example would be components +consumed with the Razor @Inject directive in MVC projects. + +```csharp +container.Register(Component.For().ImplementedBy().LifestyleScoped().CrossWired()); +``` + +This would then allow you to consume the IUserService component in your Razor view like so: + +```html +@inject WebApp.IUserService user + +@foreach (var user in user.GetAll()) +{ +
+

@user

+
+} +``` + +For this to work you have add the facility in the configure services method before you register anything. A helpful exception +will be thrown in case you forget. + +```csharp +public IServiceProvider ConfigureServices(IServiceCollection services) +{ + // Setup component model contributors for making windsor services available to IServiceProvider + Container.AddFacility(f => f.CrossWiresInto(services)); + + //... +} ``` +:warning: **Nesting CrossWired Dependencies** The ServiceProvider is quite greedy in the way that it manages disposables, if both Windsor and the ServiceProvider try track them together things end up getting disposed more than once. So to make this work Windsor has to relinquish disposable concerns to the ServiceProvider for `Cross Wired` components. If `Cross Wired` components depend to on each other, you might end up introducing a memory leak into your application(except for singletons) especially if they did not get disposed/released properly. Please use `Cross Wired` components sparingly. + +### Registering custom middleware -It is also very important to note that you should never register any framework services in Windsor. This is handled by the framework for you. In the -case of [ILoggerFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.iloggerfactory?view=aspnetcore-2.0) mentioned earlier, -you will notice it is not installed anywhere in the Startup.cs example below. +If you don't have any DI requirements in your middleware you can simply register it in the IServiceCollection like so: + +```csharp +services.AddSingleton(); // Do this if you don't care about using Windsor +``` -You also have an optional extension for validating that you don't have any types installed from the 'Microsoft.AspNetCore' called `AssertNoAspNetCoreRegistrations`. -This could optionally be called from a unit test or you can add it to your Startup to guard against this at runtime. +Alternatively if you would like to inject dependencies into your middleware that are registered with Castle Windsor then you +can use register your middleware using the `AsMiddleware` component registration extension. This should always been done from +the Configure method in Startup. + +```csharp +// Add custom middleware, do this if your middleware uses DI from Windsor +Container.Register(Component.For().DependsOn(Dependency.OnValue(loggerFactory)).LifestyleScoped().AsMiddleware()); +``` + +For this to work, Windsor needs to hold a reference to the `IApplicationBuilder` which becomes available from the 'Configure' +method as a parameter on startup. This is done by calling the `RegistersMiddlewareInto` first like so: + +```csharp +public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) +{ + // For making component registrations aware of IApplicationBuilder + Container.GetFacility().RegistersMiddlewareInto(app); + + // Add custom middleware, do this if your middleware uses DI from Windsor + Container.Register(Component.For().DependsOn(Dependency.OnValue(loggerFactory)).LifestyleScoped().AsMiddleware()); + + // ... +} +``` ## What do I need to set it up? @@ -51,7 +122,7 @@ Here is a complete example: ```csharp public class Startup { - private readonly WindsorContainer container = new WindsorContainer(); + private static readonly WindsorContainer Container = new WindsorContainer(); public Startup(IHostingEnvironment env) { @@ -66,56 +137,79 @@ public class Startup public IConfigurationRoot Configuration { get; } - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + // This method gets called by the runtime. Use this method to add application services to the application. + public IServiceProvider ConfigureServices(IServiceCollection services) { + // Setup component model contributors for making windsor services available to IServiceProvider + Container.AddFacility(f => f.CrossWiresInto(services)); + // Add framework services. services.AddMvc(); - services.AddCastleWindsor(container); // <- Registers activators + services.AddLogging((lb) => lb.AddConsole().AddDebug()); + services.AddSingleton(); // Do this if you don't care about using Windsor to inject dependencies + + // Custom application component registrations, ordering is important here + RegisterApplicationComponents(services); + + // Castle Windsor integration, controllers, tag helpers and view components, this should always come after RegisterApplicationComponents + return services.AddWindsor(Container, + opts => opts.UseEntryAssembly(typeof(HomeController).Assembly), // <- Recommended + () => services.BuildServiceProvider(validateScopes:false)); // <- Optional } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - app.UseCastleWindsor(container); // <- Registers controllers, view components and tag helpers + // For making component registrations of middleware easier + Container.GetFacility().RegistersMiddlewareInto(app); - RegisterApplicationComponents(); + // Add custom middleware, do this if your middleware uses DI from Windsor + Container.Register(Component.For().DependsOn(Dependency.OnValue(loggerFactory)).LifestyleScoped().AsMiddleware()); - // Add custom middleware - app.UseCastleWindsorMiddleware(container); + // Add framework configured middleware, use this if you dont have any DI requirements + app.UseMiddleware(); + // Serve static files app.UseStaticFiles(); + // Mvc default route app.UseMvc(routes => { routes.MapRoute( "default", "{controller=Home}/{action=Index}/{id?}"); }); - - // Validates against aspnet core framework components being accidentally registered - container.AssertNoAspNetCoreRegistrations(); } - private void RegisterApplicationComponents() + private void RegisterApplicationComponents(IServiceCollection services) { - // Custom Windsor registrations - container.Register(Component.For().ImplementedBy().LifestyleScoped()); + // Application components + Container.Register(Component.For().ImplementedBy()); + Container.Register(Component.For().ImplementedBy().LifestyleScoped().IsDefault()); + Container.Register(Component.For().ImplementedBy().LifestyleTransient().IsDefault()); + Container.Register(Component.For().ImplementedBy().LifestyleSingleton().IsDefault()); } } -public interface IUserService { } - -public class AspNetUserService : IUserService { } +// Example of framework configured middleware component, can't consume types registered in Windsor +public class FrameworkMiddleware : IMiddleware +{ + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + // Do something before + await next(context); + // Do something after + } +} -// Example of some custom user-defined middleware component. +// Example of some custom user-defined middleware component. Resolves types from Windsor. public sealed class CustomMiddleware : IMiddleware { - private readonly IUserService userService; + private readonly IScopedDisposableService scopedDisposableService; - public CustomMiddleware(ILoggerFactory loggerFactory, IUserService userService) + public CustomMiddleware(ILoggerFactory loggerFactory, IScopedDisposableService scopedDisposableService) { - this.userService = userService; + this.scopedDisposableService = scopedDisposableService; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) @@ -127,30 +221,29 @@ public sealed class CustomMiddleware : IMiddleware } ``` -## Registering ASP.NET Core framework components in Windsor - -Simply put, don't do this. The instance that Windsor resolves might not always be the same instance that is resolved by the ASP.NET Core framework. If the -instance is stateful or is registered with a competing lifestyle this can lead to problems. - ### Torn lifestyles -If you register a framework component as scoped in Windsor and the ASP.NET Core framework has registered it as scoped also, two instances might appear. This -problem is further obfiscated if those objects are stateful. This is known as a `torn lifestyle`. +If you register an application component such as a singleton in Windsor and ASP.NET Core framework two instances might appear. +This is known as a `torn lifestyle`. We hope to have avoided this in this facility's design but it is useful to know what +symptoms to look for if this does happen. + +Please report any evidence of this on our issue tracker. References: - https://simpleinjector.readthedocs.io/en/latest/tornlifestyle.html ### Captive Dependencies -This is when Windsor services with `long lived`(ie. Singleton) lifestyles, consume framework components that were registered with `short lived`(ie. Transient, Scoped) lifestyles. -The framework service dependency is effectively held captive by it's consumer lifestyle. These `side effects` can be hard to diagnose. +This is when Windsor services with `long lived`(ie. Singleton) lifestyles, consume components that were registered with +`short lived`(ie. Transient, Scoped) lifestyles. The service dependency is effectively held captive by it's consumer +lifestyle. Symptoms here could include dispose method's not firing for transients and scopes that are consumed by singletons. References: - http://blog.ploeh.dk/2014/06/02/captive-dependency/ -Special credit goes to: +### Special credit: - [@dotnetjunkie](https://github.com/dotnetjunkie) for pioneering the discussions with the ASP.NET team for non-conforming containers and providing valuable input on issue: https://github.com/castleproject/Windsor/issues/120 - [@ploeh](https://github.com/ploeh) for defining `Captive Dependencies`: http://blog.ploeh.dk/2014/06/02/captive-dependency/ - [@hikalkan](https://github.com/hikalkan) for implementing https://github.com/volosoft/castle-windsor-ms-adapter. - + - [@generik0](https://github.com/generik0) for providing invaluable feedback into the public API's and test driving this in a real world application. diff --git a/src/Castle.Facilities.AspNetCore.Tests/AspNetCoreFacilityTestCase.cs b/src/Castle.Facilities.AspNetCore.Tests/AspNetCoreFacilityTestCase.cs deleted file mode 100644 index e51f143fa0..0000000000 --- a/src/Castle.Facilities.AspNetCore.Tests/AspNetCoreFacilityTestCase.cs +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2004-2018 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.Facilities.AspNetCore.Tests -{ - using System; - using System.Collections.Generic; - - using Castle.MicroKernel.Lifestyle; - using Castle.MicroKernel.Registration; - using Castle.Windsor; - - using Microsoft.AspNetCore; - using Microsoft.AspNetCore.Builder.Internal; - using Microsoft.AspNetCore.Mvc; - using Microsoft.AspNetCore.Razor.TagHelpers; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; - - using NUnit.Framework; - - [TestFixture] - public class AspNetCoreFacilityTestCase - { - [TearDown] - public void TearDown() - { - scope.Dispose(); - container.Dispose(); - } - - private IDisposable scope; - private WindsorContainer container; - - [TestCase(true)] - [TestCase(false)] - public void Should_be_able_to_resolve_controller_from_container(bool useEntryAssembly) - { - SetUp(useEntryAssembly); - Assert.DoesNotThrow(() => container.Resolve()); - } - - [TestCase(true)] - [TestCase(false)] - public void Should_be_able_to_resolve_tag_helper_from_container(bool useEntryAssembly) - { - SetUp(useEntryAssembly); - Assert.DoesNotThrow(() => container.Resolve()); - } - - [TestCase(true)] - [TestCase(false)] - public void Should_be_able_to_resolve_view_component_from_container(bool useEntryAssembly) - { - SetUp(useEntryAssembly); - Assert.DoesNotThrow(() => container.Resolve()); - } - - [TestCase(true)] - [TestCase(false)] - public void Should_throw_if_framework_components_registered_in_Windsor_to_prevent_torn_lifestyles_once_configuration_is_validated(bool useEntryAssembly) - { - SetUp(useEntryAssembly); - Assert.Throws(() => - { - container.Register(Component.For()); - container.AssertNoAspNetCoreRegistrations(); - }); - } - - private static ServiceCollection BuildServiceCollection() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - serviceCollection.AddTransient(); - serviceCollection.AddTransient(); - serviceCollection.AddTransient, FakeFrameworkComponent>(); - serviceCollection.AddTransient(typeof(IFakeFrameworkComponent<>), typeof(FakeFrameworkComponent<>)); - serviceCollection.AddTransient>((sp) => new List() - { - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - }); - return serviceCollection; - } - - private void BuildWindsorContainer(ServiceCollection serviceCollection) - { - container = new WindsorContainer(); - serviceCollection.AddCastleWindsor(container); - container.Register(Component.For()); - } - - private void BuildApplicationBuilder(ServiceCollection serviceCollection, bool useEntryAssembly) - { - var applicationBuilder = new ApplicationBuilder(serviceCollection.BuildServiceProvider()); - if (useEntryAssembly) - { - applicationBuilder.UseCastleWindsor(container); - } - applicationBuilder.UseCastleWindsor(container); - } - - public class AnyService - { - } - - public class AnyController : Controller - { - private readonly IFakeFrameworkComponent closedGeneric; - private readonly IFakeFrameworkComponent component; - private readonly IList components; - private readonly ILogger logger; - private readonly IFakeFrameworkComponent openGeneric; - private readonly AnyService service; - - public AnyController( - AnyService service, - IFakeFrameworkComponent component, - IList components, - IFakeFrameworkComponent openGeneric, - IFakeFrameworkComponent closedGeneric, - ILogger logger) - { - this.service = service ?? throw new ArgumentNullException(nameof(service)); - this.component = component ?? throw new ArgumentNullException(nameof(component)); - this.components = components ?? throw new ArgumentNullException(nameof(components)); - this.closedGeneric = closedGeneric ?? throw new ArgumentNullException(nameof(closedGeneric)); - this.openGeneric = openGeneric ?? throw new ArgumentNullException(nameof(openGeneric)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - } - - public class AnyTagHelper : TagHelper - { - private readonly IFakeFrameworkComponent component; - private readonly AnyService service; - - public AnyTagHelper(AnyService service, IFakeFrameworkComponent component) - { - this.service = service ?? throw new ArgumentNullException(nameof(service)); - this.component = component ?? throw new ArgumentNullException(nameof(component)); - } - } - - public class AnyViewComponent : ViewComponent - { - private readonly IFakeFrameworkComponent component; - private readonly AnyService service; - - public AnyViewComponent(AnyService service, IFakeFrameworkComponent component) - { - this.service = service ?? throw new ArgumentNullException(nameof(service)); - this.component = component ?? throw new ArgumentNullException(nameof(component)); - } - } - - private void SetUp(bool useEntryAssembly) - { - var serviceCollection = BuildServiceCollection(); - BuildWindsorContainer(serviceCollection); - BuildApplicationBuilder(serviceCollection, useEntryAssembly); - scope = container.BeginScope(); - } - } -} - -namespace Microsoft.AspNetCore -{ - public interface IFakeFrameworkComponent { } - public class FakeFrameworkComponent : IFakeFrameworkComponent { } - - public interface IFakeFrameworkComponent { } - public class FakeFrameworkComponent : IFakeFrameworkComponent { } - - public class FakeFrameworkOptions { } - public interface IFakeFrameworkComponentEnumerable { } - - public class FakeFrameworkComponentEnumerable : IFakeFrameworkComponentEnumerable { } -} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore.Tests/Castle.Facilities.AspNetCore.Tests.csproj b/src/Castle.Facilities.AspNetCore.Tests/Castle.Facilities.AspNetCore.Tests.csproj index 49c8ff83b2..700f6e5f46 100644 --- a/src/Castle.Facilities.AspNetCore.Tests/Castle.Facilities.AspNetCore.Tests.csproj +++ b/src/Castle.Facilities.AspNetCore.Tests/Castle.Facilities.AspNetCore.Tests.csproj @@ -20,4 +20,23 @@ + + + TextTemplatingFileGenerator + ModelFakes.cs + + + + + + + + + + True + True + ModelFakes.tt + + + diff --git a/src/Castle.Facilities.AspNetCore.Tests/Fakes/AnyFakes.cs b/src/Castle.Facilities.AspNetCore.Tests/Fakes/AnyFakes.cs new file mode 100644 index 0000000000..ed87f93ac2 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Fakes/AnyFakes.cs @@ -0,0 +1,64 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests.Fakes +{ + using System; + using System.Threading.Tasks; + + using Microsoft.AspNetCore.Http; + + using Castle.MicroKernel.Lifestyle; + + public class AnyComponent { } + public class AnyComponentWithLifestyleManager : AbstractLifestyleManager + { + public override void Dispose() + { + } + } + + public sealed class AnyMiddleware : IMiddleware + { + private readonly AnyComponent anyComponent; + private readonly ServiceProviderOnlyScopedDisposable serviceProviderOnlyScopedDisposable; + private readonly WindsorOnlyScopedDisposable windsorOnlyScopedDisposable; + private readonly CrossWiredScopedDisposable crossWiredScopedDisposable; + + public AnyMiddleware( + ServiceProviderOnlyScopedDisposable serviceProviderOnlyScopedDisposable, + WindsorOnlyScopedDisposable windsorOnlyScopedDisposable, + CrossWiredScopedDisposable crossWiredScopedDisposable) + { + this.serviceProviderOnlyScopedDisposable = serviceProviderOnlyScopedDisposable ?? throw new ArgumentNullException(nameof(serviceProviderOnlyScopedDisposable)); + this.windsorOnlyScopedDisposable = windsorOnlyScopedDisposable ?? throw new ArgumentNullException(nameof(windsorOnlyScopedDisposable)); + this.crossWiredScopedDisposable = crossWiredScopedDisposable ?? throw new ArgumentNullException(nameof(crossWiredScopedDisposable)); + } + + public AnyMiddleware(AnyComponent anyComponent) + { + // This will never get called because Windsor picks the most greedy constructor + this.anyComponent = anyComponent ?? throw new ArgumentNullException(nameof(anyComponent)); + } + + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + // Do something before + await next(context); + // Do something after + } + } + +} diff --git a/src/Castle.Facilities.AspNetCore.Tests/Fakes/CompositeFakes.cs b/src/Castle.Facilities.AspNetCore.Tests/Fakes/CompositeFakes.cs new file mode 100644 index 0000000000..ca790fbbaf --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Fakes/CompositeFakes.cs @@ -0,0 +1,57 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests.Fakes +{ + using System; + + public class CompositeController + { + public CompositeController( + ControllerCrossWired crossWiredController, + ControllerServiceProviderOnly serviceProviderOnlyController, + ControllerWindsorOnly windsorOnlyController) + { + if (crossWiredController == null) throw new ArgumentNullException(nameof(crossWiredController)); + if (serviceProviderOnlyController == null) throw new ArgumentNullException(nameof(serviceProviderOnlyController)); + if (windsorOnlyController == null) throw new ArgumentNullException(nameof(windsorOnlyController)); + } + } + + public class CompositeTagHelper + { + public CompositeTagHelper( + TagHelperCrossWired crossWiredTagHelper, + TagHelperServiceProviderOnly serviceProviderOnlyTagHelper, + TagHelperWindsorOnly windsorOnlyTagHelper) + { + if (crossWiredTagHelper == null) throw new ArgumentNullException(nameof(crossWiredTagHelper)); + if (serviceProviderOnlyTagHelper == null) throw new ArgumentNullException(nameof(serviceProviderOnlyTagHelper)); + if (windsorOnlyTagHelper == null) throw new ArgumentNullException(nameof(windsorOnlyTagHelper)); + } + } + + public class CompositeViewComponent + { + public CompositeViewComponent( + ViewComponentCrossWired crossWiredViewComponent, + ViewComponentServiceProviderOnly serviceProviderOnlyViewComponent, + ViewComponentWindsorOnly windsorOnlyViewComponent) + { + if (crossWiredViewComponent == null) throw new ArgumentNullException(nameof(crossWiredViewComponent)); + if (serviceProviderOnlyViewComponent == null) throw new ArgumentNullException(nameof(serviceProviderOnlyViewComponent)); + if (windsorOnlyViewComponent == null) throw new ArgumentNullException(nameof(windsorOnlyViewComponent)); + } + } +} diff --git a/src/Castle.Facilities.AspNetCore.Tests/Fakes/ModelFakes.cs b/src/Castle.Facilities.AspNetCore.Tests/Fakes/ModelFakes.cs new file mode 100644 index 0000000000..9a78d478bf --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Fakes/ModelFakes.cs @@ -0,0 +1,583 @@ +// Copyright 2004-2018 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. + +// This file was automatically generated by 'ModelFakes.tt', please don't edit manually. + +namespace Castle.Facilities.AspNetCore.Tests.Fakes +{ + using System; + + using Castle.Core; + using Castle.MicroKernel.Registration; + using Castle.Windsor; + + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Razor.TagHelpers; + using Microsoft.Extensions.DependencyInjection; + + public partial class OpenOptions {} + public partial class ClosedOptions {} + + public interface IDisposableObservable + { + bool Disposed { get;set; } + int DisposedCount { get;set; } + } + + public interface IWeakReferenceObservable + { + bool HasReference { get; } + } + + public partial class ServiceProviderOnlyTransient : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public ServiceProviderOnlyTransient() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } + + public partial class ServiceProviderOnlyTransientGeneric : ServiceProviderOnlyTransient { } + + public partial class ServiceProviderOnlyTransientDisposable : ServiceProviderOnlyTransient, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } + + public partial class ServiceProviderOnlyScoped : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public ServiceProviderOnlyScoped() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } + + public partial class ServiceProviderOnlyScopedGeneric : ServiceProviderOnlyScoped { } + + public partial class ServiceProviderOnlyScopedDisposable : ServiceProviderOnlyScoped, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } + + public partial class ServiceProviderOnlySingleton : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public ServiceProviderOnlySingleton() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } + + public partial class ServiceProviderOnlySingletonGeneric : ServiceProviderOnlySingleton { } + + public partial class ServiceProviderOnlySingletonDisposable : ServiceProviderOnlySingleton, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } + + public partial class WindsorOnlyTransient : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public WindsorOnlyTransient() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } + + public partial class WindsorOnlyTransientGeneric : WindsorOnlyTransient { } + + public partial class WindsorOnlyTransientDisposable : WindsorOnlyTransient, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } + + public partial class WindsorOnlyScoped : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public WindsorOnlyScoped() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } + + public partial class WindsorOnlyScopedGeneric : WindsorOnlyScoped { } + + public partial class WindsorOnlyScopedDisposable : WindsorOnlyScoped, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } + + public partial class WindsorOnlySingleton : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public WindsorOnlySingleton() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } + + public partial class WindsorOnlySingletonGeneric : WindsorOnlySingleton { } + + public partial class WindsorOnlySingletonDisposable : WindsorOnlySingleton, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } + + public partial class CrossWiredTransient : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public CrossWiredTransient() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } + + public partial class CrossWiredTransientGeneric : CrossWiredTransient { } + + public partial class CrossWiredTransientDisposable : CrossWiredTransient, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } + + public partial class CrossWiredScoped : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public CrossWiredScoped() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } + + public partial class CrossWiredScopedGeneric : CrossWiredScoped { } + + public partial class CrossWiredScopedDisposable : CrossWiredScoped, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } + + public partial class CrossWiredSingleton : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public CrossWiredSingleton() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } + + public partial class CrossWiredSingletonGeneric : CrossWiredSingleton { } + + public partial class CrossWiredSingletonDisposable : CrossWiredSingleton, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } + + public partial class ModelInstaller + { + public static void RegisterWindsor(IWindsorContainer container) + { + container.Register(Component.For().LifestyleTransient()); + container.Register(Component.For(typeof(WindsorOnlyTransientGeneric<>)).LifestyleTransient()); + container.Register(Component.For>().LifestyleTransient()); + container.Register(Component.For().LifestyleTransient()); + + container.Register(Component.For().LifestyleScoped()); + container.Register(Component.For(typeof(WindsorOnlyScopedGeneric<>)).LifestyleScoped()); + container.Register(Component.For>().LifestyleScoped()); + container.Register(Component.For().LifestyleScoped()); + + container.Register(Component.For().LifestyleSingleton()); + container.Register(Component.For(typeof(WindsorOnlySingletonGeneric<>)).LifestyleSingleton()); + container.Register(Component.For>().LifestyleSingleton()); + container.Register(Component.For().LifestyleSingleton()); + + container.Register(Component.For().LifestyleScoped()); + container.Register(Component.For().LifestyleScoped()); + container.Register(Component.For().LifestyleScoped()); + + + } + + public static void RegisterServiceCollection(IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(typeof(ServiceProviderOnlyTransientGeneric)); + services.AddTransient>(); + services.AddTransient(); + + services.AddScoped(); + services.AddScoped(typeof(ServiceProviderOnlyScopedGeneric)); + services.AddScoped>(); + services.AddScoped(); + + services.AddSingleton(); + services.AddSingleton(typeof(ServiceProviderOnlySingletonGeneric)); + services.AddSingleton>(); + services.AddSingleton(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + + } + + public static void RegisterCrossWired(IWindsorContainer container, IServiceCollection serviceCollection) + { + container.Register(Component.For().CrossWired().LifestyleTransient()); + container.Register(Component.For>().CrossWired().LifestyleTransient()); + container.Register(Component.For>().CrossWired().LifestyleTransient()); + container.Register(Component.For().CrossWired().LifestyleTransient()); + + container.Register(Component.For().CrossWired().LifestyleScoped()); + container.Register(Component.For>().CrossWired().LifestyleScoped()); + container.Register(Component.For>().CrossWired().LifestyleScoped()); + container.Register(Component.For().CrossWired().LifestyleScoped()); + + container.Register(Component.For().CrossWired().LifestyleSingleton()); + container.Register(Component.For>().CrossWired().LifestyleSingleton()); + container.Register(Component.For>().CrossWired().LifestyleSingleton()); + container.Register(Component.For().CrossWired().LifestyleSingleton()); + + container.Register(Component.For().CrossWired().LifestyleSingleton()); + container.Register(Component.For().CrossWired().LifestyleScoped()); + container.Register(Component.For().CrossWired().LifestyleScoped()); + container.Register(Component.For().CrossWired().LifestyleScoped()); + + } + } + + public partial class ControllerWindsorOnly : Controller + { + public ControllerWindsorOnly + ( + WindsorOnlyTransient WindsorOnlyTransient1, + WindsorOnlyTransientGeneric WindsorOnlyTransient2, + WindsorOnlyTransientGeneric WindsorOnlyTransient3, + WindsorOnlyTransientDisposable WindsorOnlyTransient4 , + WindsorOnlyScoped WindsorOnlyScoped1, + WindsorOnlyScopedGeneric WindsorOnlyScoped2, + WindsorOnlyScopedGeneric WindsorOnlyScoped3, + WindsorOnlyScopedDisposable WindsorOnlyScoped4 , + WindsorOnlySingleton WindsorOnlySingleton1, + WindsorOnlySingletonGeneric WindsorOnlySingleton2, + WindsorOnlySingletonGeneric WindsorOnlySingleton3, + WindsorOnlySingletonDisposable WindsorOnlySingleton4 ) + { + if (WindsorOnlyTransient1 == null) throw new ArgumentException(nameof(WindsorOnlyTransient1)); + if (WindsorOnlyTransient2 == null) throw new ArgumentException(nameof(WindsorOnlyTransient2)); + if (WindsorOnlyTransient3 == null) throw new ArgumentException(nameof(WindsorOnlyTransient3)); + if (WindsorOnlyTransient4 == null) throw new ArgumentException(nameof(WindsorOnlyTransient4)); + if (WindsorOnlyScoped1 == null) throw new ArgumentException(nameof(WindsorOnlyScoped1)); + if (WindsorOnlyScoped2 == null) throw new ArgumentException(nameof(WindsorOnlyScoped2)); + if (WindsorOnlyScoped3 == null) throw new ArgumentException(nameof(WindsorOnlyScoped3)); + if (WindsorOnlyScoped4 == null) throw new ArgumentException(nameof(WindsorOnlyScoped4)); + if (WindsorOnlySingleton1 == null) throw new ArgumentException(nameof(WindsorOnlySingleton1)); + if (WindsorOnlySingleton2 == null) throw new ArgumentException(nameof(WindsorOnlySingleton2)); + if (WindsorOnlySingleton3 == null) throw new ArgumentException(nameof(WindsorOnlySingleton3)); + if (WindsorOnlySingleton4 == null) throw new ArgumentException(nameof(WindsorOnlySingleton4)); + } + } + + public partial class TagHelperWindsorOnly : TagHelper + { + public TagHelperWindsorOnly + ( + WindsorOnlyTransient WindsorOnlyTransient1, + WindsorOnlyTransientGeneric WindsorOnlyTransient2, + WindsorOnlyTransientGeneric WindsorOnlyTransient3, + WindsorOnlyTransientDisposable WindsorOnlyTransient4 , + WindsorOnlyScoped WindsorOnlyScoped1, + WindsorOnlyScopedGeneric WindsorOnlyScoped2, + WindsorOnlyScopedGeneric WindsorOnlyScoped3, + WindsorOnlyScopedDisposable WindsorOnlyScoped4 , + WindsorOnlySingleton WindsorOnlySingleton1, + WindsorOnlySingletonGeneric WindsorOnlySingleton2, + WindsorOnlySingletonGeneric WindsorOnlySingleton3, + WindsorOnlySingletonDisposable WindsorOnlySingleton4 ) + { + if (WindsorOnlyTransient1 == null) throw new ArgumentException(nameof(WindsorOnlyTransient1)); + if (WindsorOnlyTransient2 == null) throw new ArgumentException(nameof(WindsorOnlyTransient2)); + if (WindsorOnlyTransient3 == null) throw new ArgumentException(nameof(WindsorOnlyTransient3)); + if (WindsorOnlyTransient4 == null) throw new ArgumentException(nameof(WindsorOnlyTransient4)); + if (WindsorOnlyScoped1 == null) throw new ArgumentException(nameof(WindsorOnlyScoped1)); + if (WindsorOnlyScoped2 == null) throw new ArgumentException(nameof(WindsorOnlyScoped2)); + if (WindsorOnlyScoped3 == null) throw new ArgumentException(nameof(WindsorOnlyScoped3)); + if (WindsorOnlyScoped4 == null) throw new ArgumentException(nameof(WindsorOnlyScoped4)); + if (WindsorOnlySingleton1 == null) throw new ArgumentException(nameof(WindsorOnlySingleton1)); + if (WindsorOnlySingleton2 == null) throw new ArgumentException(nameof(WindsorOnlySingleton2)); + if (WindsorOnlySingleton3 == null) throw new ArgumentException(nameof(WindsorOnlySingleton3)); + if (WindsorOnlySingleton4 == null) throw new ArgumentException(nameof(WindsorOnlySingleton4)); + } + } + + public class ViewComponentWindsorOnly : ViewComponent + { + public ViewComponentWindsorOnly + ( + WindsorOnlyTransient WindsorOnlyTransient1, + WindsorOnlyTransientGeneric WindsorOnlyTransient2, + WindsorOnlyTransientGeneric WindsorOnlyTransient3, + WindsorOnlyTransientDisposable WindsorOnlyTransient4 , + WindsorOnlyScoped WindsorOnlyScoped1, + WindsorOnlyScopedGeneric WindsorOnlyScoped2, + WindsorOnlyScopedGeneric WindsorOnlyScoped3, + WindsorOnlyScopedDisposable WindsorOnlyScoped4 , + WindsorOnlySingleton WindsorOnlySingleton1, + WindsorOnlySingletonGeneric WindsorOnlySingleton2, + WindsorOnlySingletonGeneric WindsorOnlySingleton3, + WindsorOnlySingletonDisposable WindsorOnlySingleton4 ) + { + if (WindsorOnlyTransient1 == null) throw new ArgumentException(nameof(WindsorOnlyTransient1)); + if (WindsorOnlyTransient2 == null) throw new ArgumentException(nameof(WindsorOnlyTransient2)); + if (WindsorOnlyTransient3 == null) throw new ArgumentException(nameof(WindsorOnlyTransient3)); + if (WindsorOnlyTransient4 == null) throw new ArgumentException(nameof(WindsorOnlyTransient4)); + if (WindsorOnlyScoped1 == null) throw new ArgumentException(nameof(WindsorOnlyScoped1)); + if (WindsorOnlyScoped2 == null) throw new ArgumentException(nameof(WindsorOnlyScoped2)); + if (WindsorOnlyScoped3 == null) throw new ArgumentException(nameof(WindsorOnlyScoped3)); + if (WindsorOnlyScoped4 == null) throw new ArgumentException(nameof(WindsorOnlyScoped4)); + if (WindsorOnlySingleton1 == null) throw new ArgumentException(nameof(WindsorOnlySingleton1)); + if (WindsorOnlySingleton2 == null) throw new ArgumentException(nameof(WindsorOnlySingleton2)); + if (WindsorOnlySingleton3 == null) throw new ArgumentException(nameof(WindsorOnlySingleton3)); + if (WindsorOnlySingleton4 == null) throw new ArgumentException(nameof(WindsorOnlySingleton4)); + } + } + + public partial class ControllerServiceProviderOnly : Controller + { + public ControllerServiceProviderOnly + ( + ServiceProviderOnlyTransient ServiceProviderOnlyTransient1, + ServiceProviderOnlyTransientGeneric ServiceProviderOnlyTransient2, + ServiceProviderOnlyTransientGeneric ServiceProviderOnlyTransient3, + ServiceProviderOnlyTransientDisposable ServiceProviderOnlyTransient4 , + ServiceProviderOnlyScoped ServiceProviderOnlyScoped1, + ServiceProviderOnlyScopedGeneric ServiceProviderOnlyScoped2, + ServiceProviderOnlyScopedGeneric ServiceProviderOnlyScoped3, + ServiceProviderOnlyScopedDisposable ServiceProviderOnlyScoped4 , + ServiceProviderOnlySingleton ServiceProviderOnlySingleton1, + ServiceProviderOnlySingletonGeneric ServiceProviderOnlySingleton2, + ServiceProviderOnlySingletonGeneric ServiceProviderOnlySingleton3, + ServiceProviderOnlySingletonDisposable ServiceProviderOnlySingleton4 ) + { + } + } + + public partial class TagHelperServiceProviderOnly : TagHelper + { + public TagHelperServiceProviderOnly + ( + ServiceProviderOnlyTransient ServiceProviderOnlyTransient1, + ServiceProviderOnlyTransientGeneric ServiceProviderOnlyTransient2, + ServiceProviderOnlyTransientGeneric ServiceProviderOnlyTransient3, + ServiceProviderOnlyTransientDisposable ServiceProviderOnlyTransient4 , + ServiceProviderOnlyScoped ServiceProviderOnlyScoped1, + ServiceProviderOnlyScopedGeneric ServiceProviderOnlyScoped2, + ServiceProviderOnlyScopedGeneric ServiceProviderOnlyScoped3, + ServiceProviderOnlyScopedDisposable ServiceProviderOnlyScoped4 , + ServiceProviderOnlySingleton ServiceProviderOnlySingleton1, + ServiceProviderOnlySingletonGeneric ServiceProviderOnlySingleton2, + ServiceProviderOnlySingletonGeneric ServiceProviderOnlySingleton3, + ServiceProviderOnlySingletonDisposable ServiceProviderOnlySingleton4 ) + { + if (ServiceProviderOnlyTransient1 == null) throw new ArgumentException(nameof(ServiceProviderOnlyTransient1)); + if (ServiceProviderOnlyTransient2 == null) throw new ArgumentException(nameof(ServiceProviderOnlyTransient2)); + if (ServiceProviderOnlyTransient3 == null) throw new ArgumentException(nameof(ServiceProviderOnlyTransient3)); + if (ServiceProviderOnlyTransient4 == null) throw new ArgumentException(nameof(ServiceProviderOnlyTransient4)); + if (ServiceProviderOnlyScoped1 == null) throw new ArgumentException(nameof(ServiceProviderOnlyScoped1)); + if (ServiceProviderOnlyScoped2 == null) throw new ArgumentException(nameof(ServiceProviderOnlyScoped2)); + if (ServiceProviderOnlyScoped3 == null) throw new ArgumentException(nameof(ServiceProviderOnlyScoped3)); + if (ServiceProviderOnlyScoped4 == null) throw new ArgumentException(nameof(ServiceProviderOnlyScoped4)); + if (ServiceProviderOnlySingleton1 == null) throw new ArgumentException(nameof(ServiceProviderOnlySingleton1)); + if (ServiceProviderOnlySingleton2 == null) throw new ArgumentException(nameof(ServiceProviderOnlySingleton2)); + if (ServiceProviderOnlySingleton3 == null) throw new ArgumentException(nameof(ServiceProviderOnlySingleton3)); + if (ServiceProviderOnlySingleton4 == null) throw new ArgumentException(nameof(ServiceProviderOnlySingleton4)); + } + } + + public partial class ViewComponentServiceProviderOnly : ViewComponent + { + public ViewComponentServiceProviderOnly + ( + ServiceProviderOnlyTransient ServiceProviderOnlyTransient1, + ServiceProviderOnlyTransientGeneric ServiceProviderOnlyTransient2, + ServiceProviderOnlyTransientGeneric ServiceProviderOnlyTransient3, + ServiceProviderOnlyTransientDisposable ServiceProviderOnlyTransient4 , + ServiceProviderOnlyScoped ServiceProviderOnlyScoped1, + ServiceProviderOnlyScopedGeneric ServiceProviderOnlyScoped2, + ServiceProviderOnlyScopedGeneric ServiceProviderOnlyScoped3, + ServiceProviderOnlyScopedDisposable ServiceProviderOnlyScoped4 , + ServiceProviderOnlySingleton ServiceProviderOnlySingleton1, + ServiceProviderOnlySingletonGeneric ServiceProviderOnlySingleton2, + ServiceProviderOnlySingletonGeneric ServiceProviderOnlySingleton3, + ServiceProviderOnlySingletonDisposable ServiceProviderOnlySingleton4 ) + { + if (ServiceProviderOnlyTransient1 == null) throw new ArgumentException(nameof(ServiceProviderOnlyTransient1)); + if (ServiceProviderOnlyTransient2 == null) throw new ArgumentException(nameof(ServiceProviderOnlyTransient2)); + if (ServiceProviderOnlyTransient3 == null) throw new ArgumentException(nameof(ServiceProviderOnlyTransient3)); + if (ServiceProviderOnlyTransient4 == null) throw new ArgumentException(nameof(ServiceProviderOnlyTransient4)); + if (ServiceProviderOnlyScoped1 == null) throw new ArgumentException(nameof(ServiceProviderOnlyScoped1)); + if (ServiceProviderOnlyScoped2 == null) throw new ArgumentException(nameof(ServiceProviderOnlyScoped2)); + if (ServiceProviderOnlyScoped3 == null) throw new ArgumentException(nameof(ServiceProviderOnlyScoped3)); + if (ServiceProviderOnlyScoped4 == null) throw new ArgumentException(nameof(ServiceProviderOnlyScoped4)); + if (ServiceProviderOnlySingleton1 == null) throw new ArgumentException(nameof(ServiceProviderOnlySingleton1)); + if (ServiceProviderOnlySingleton2 == null) throw new ArgumentException(nameof(ServiceProviderOnlySingleton2)); + if (ServiceProviderOnlySingleton3 == null) throw new ArgumentException(nameof(ServiceProviderOnlySingleton3)); + if (ServiceProviderOnlySingleton4 == null) throw new ArgumentException(nameof(ServiceProviderOnlySingleton4)); + } + } + + public partial class ControllerCrossWired : Controller + { + public ControllerCrossWired + ( + CrossWiredTransient CrossWiredTransient1, + CrossWiredTransientGeneric CrossWiredTransient2, + CrossWiredTransientGeneric CrossWiredTransient3, + CrossWiredTransientDisposable CrossWiredTransient4 , + CrossWiredScoped CrossWiredScoped1, + CrossWiredScopedGeneric CrossWiredScoped2, + CrossWiredScopedGeneric CrossWiredScoped3, + CrossWiredScopedDisposable CrossWiredScoped4 , + CrossWiredSingleton CrossWiredSingleton1, + CrossWiredSingletonGeneric CrossWiredSingleton2, + CrossWiredSingletonGeneric CrossWiredSingleton3, + CrossWiredSingletonDisposable CrossWiredSingleton4 ) + { + if (CrossWiredTransient1 == null) throw new ArgumentException(nameof(CrossWiredTransient1)); + if (CrossWiredTransient2 == null) throw new ArgumentException(nameof(CrossWiredTransient2)); + if (CrossWiredTransient3 == null) throw new ArgumentException(nameof(CrossWiredTransient3)); + if (CrossWiredTransient4 == null) throw new ArgumentException(nameof(CrossWiredTransient4)); + if (CrossWiredScoped1 == null) throw new ArgumentException(nameof(CrossWiredScoped1)); + if (CrossWiredScoped2 == null) throw new ArgumentException(nameof(CrossWiredScoped2)); + if (CrossWiredScoped3 == null) throw new ArgumentException(nameof(CrossWiredScoped3)); + if (CrossWiredScoped4 == null) throw new ArgumentException(nameof(CrossWiredScoped4)); + if (CrossWiredSingleton1 == null) throw new ArgumentException(nameof(CrossWiredSingleton1)); + if (CrossWiredSingleton2 == null) throw new ArgumentException(nameof(CrossWiredSingleton2)); + if (CrossWiredSingleton3 == null) throw new ArgumentException(nameof(CrossWiredSingleton3)); + if (CrossWiredSingleton4 == null) throw new ArgumentException(nameof(CrossWiredSingleton4)); + } + } + + public partial class TagHelperCrossWired : TagHelper + { + public TagHelperCrossWired + ( + CrossWiredTransient CrossWiredTransient1, + CrossWiredTransientGeneric CrossWiredTransient2, + CrossWiredTransientGeneric CrossWiredTransient3, + CrossWiredTransientDisposable CrossWiredTransient4 , + CrossWiredScoped CrossWiredScoped1, + CrossWiredScopedGeneric CrossWiredScoped2, + CrossWiredScopedGeneric CrossWiredScoped3, + CrossWiredScopedDisposable CrossWiredScoped4 , + CrossWiredSingleton CrossWiredSingleton1, + CrossWiredSingletonGeneric CrossWiredSingleton2, + CrossWiredSingletonGeneric CrossWiredSingleton3, + CrossWiredSingletonDisposable CrossWiredSingleton4 ) + { + if (CrossWiredTransient1 == null) throw new ArgumentException(nameof(CrossWiredTransient1)); + if (CrossWiredTransient2 == null) throw new ArgumentException(nameof(CrossWiredTransient2)); + if (CrossWiredTransient3 == null) throw new ArgumentException(nameof(CrossWiredTransient3)); + if (CrossWiredTransient4 == null) throw new ArgumentException(nameof(CrossWiredTransient4)); + if (CrossWiredScoped1 == null) throw new ArgumentException(nameof(CrossWiredScoped1)); + if (CrossWiredScoped2 == null) throw new ArgumentException(nameof(CrossWiredScoped2)); + if (CrossWiredScoped3 == null) throw new ArgumentException(nameof(CrossWiredScoped3)); + if (CrossWiredScoped4 == null) throw new ArgumentException(nameof(CrossWiredScoped4)); + if (CrossWiredSingleton1 == null) throw new ArgumentException(nameof(CrossWiredSingleton1)); + if (CrossWiredSingleton2 == null) throw new ArgumentException(nameof(CrossWiredSingleton2)); + if (CrossWiredSingleton3 == null) throw new ArgumentException(nameof(CrossWiredSingleton3)); + if (CrossWiredSingleton4 == null) throw new ArgumentException(nameof(CrossWiredSingleton4)); + } + } + + public partial class ViewComponentCrossWired : ViewComponent + { + public ViewComponentCrossWired + ( + CrossWiredTransient CrossWiredTransient1, + CrossWiredTransientGeneric CrossWiredTransient2, + CrossWiredTransientGeneric CrossWiredTransient3, + CrossWiredTransientDisposable CrossWiredTransient4 , + CrossWiredScoped CrossWiredScoped1, + CrossWiredScopedGeneric CrossWiredScoped2, + CrossWiredScopedGeneric CrossWiredScoped3, + CrossWiredScopedDisposable CrossWiredScoped4 , + CrossWiredSingleton CrossWiredSingleton1, + CrossWiredSingletonGeneric CrossWiredSingleton2, + CrossWiredSingletonGeneric CrossWiredSingleton3, + CrossWiredSingletonDisposable CrossWiredSingleton4 ) + + { + if (CrossWiredTransient1 == null) throw new ArgumentException(nameof(CrossWiredTransient1)); + if (CrossWiredTransient2 == null) throw new ArgumentException(nameof(CrossWiredTransient2)); + if (CrossWiredTransient3 == null) throw new ArgumentException(nameof(CrossWiredTransient3)); + if (CrossWiredTransient4 == null) throw new ArgumentException(nameof(CrossWiredTransient4)); + if (CrossWiredScoped1 == null) throw new ArgumentException(nameof(CrossWiredScoped1)); + if (CrossWiredScoped2 == null) throw new ArgumentException(nameof(CrossWiredScoped2)); + if (CrossWiredScoped3 == null) throw new ArgumentException(nameof(CrossWiredScoped3)); + if (CrossWiredScoped4 == null) throw new ArgumentException(nameof(CrossWiredScoped4)); + if (CrossWiredSingleton1 == null) throw new ArgumentException(nameof(CrossWiredSingleton1)); + if (CrossWiredSingleton2 == null) throw new ArgumentException(nameof(CrossWiredSingleton2)); + if (CrossWiredSingleton3 == null) throw new ArgumentException(nameof(CrossWiredSingleton3)); + if (CrossWiredSingleton4 == null) throw new ArgumentException(nameof(CrossWiredSingleton4)); + } + } +} diff --git a/src/Castle.Facilities.AspNetCore.Tests/Fakes/ModelFakes.tt b/src/Castle.Facilities.AspNetCore.Tests/Fakes/ModelFakes.tt new file mode 100644 index 0000000000..724eaee31a --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Fakes/ModelFakes.tt @@ -0,0 +1,493 @@ +<#@ template debug="false" hostspecific="false" language="C#" #><#@ assembly name="System.Core" #><#@ import namespace="System.Linq" #><#@ import namespace="System.Text" #><#@ import namespace="System.Collections.Generic" #><#@ output extension=".cs" #><# + var lifestyles = new[] { + "Transient", + "Scoped", + "Singleton" + }; + + var lifestyleTargets = new[] { + "ServiceProviderOnly", + "WindsorOnly", + "CrossWired" + }; +#>// Copyright 2004-2018 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. + +// This file was automatically generated by 'ModelFakes.tt', please don't edit manually. + +namespace Castle.Facilities.AspNetCore.Tests.Fakes +{ + using System; + + using Castle.Core; + using Castle.MicroKernel.Registration; + using Castle.Windsor; + + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Razor.TagHelpers; + using Microsoft.Extensions.DependencyInjection; + + public partial class OpenOptions {} + public partial class ClosedOptions {} + + public interface IDisposableObservable + { + bool Disposed { get;set; } + int DisposedCount { get;set; } + } + + public interface IWeakReferenceObservable + { + bool HasReference { get; } + } +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + foreach(var lifestyle in lifestyles) + { +#> + public partial class <#=lifestyleTarget#><#=lifestyle#> : IWeakReferenceObservable + { + private readonly WeakReference reference; + + public <#=lifestyleTarget#><#=lifestyle#>() + { + reference = new WeakReference(this, false); + } + + public bool HasReference => reference.IsAlive; + } +<# +#> + public partial class <#=lifestyleTarget#><#=lifestyle#>Generic : <#=lifestyleTarget#><#=lifestyle#> { } +<# +#> + public partial class <#=lifestyleTarget#><#=lifestyle#>Disposable : <#=lifestyleTarget#><#=lifestyle#>, IDisposable, IDisposableObservable, IWeakReferenceObservable + { + public bool Disposed { get;set; } + public int DisposedCount { get;set; } + public void Dispose() { Disposed = true; DisposedCount++; } + } +<# + } + } +#> + public partial class ModelInstaller + { + public static void RegisterWindsor(IWindsorContainer container) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "WindsorOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> container.Register(Component.For<<#=lifestyleTarget#><#=lifestyle#>>().Lifestyle<#=lifestyle#>()); <#="\r\n"#><# +#> container.Register(Component.For(typeof(<#=lifestyleTarget#><#=lifestyle#>Generic<>)).Lifestyle<#=lifestyle#>()); <#="\r\n"#><# +#> container.Register(Component.For<<#=lifestyleTarget#><#=lifestyle#>Generic>().Lifestyle<#=lifestyle#>()); <#="\r\n"#><# +#> container.Register(Component.For<<#=lifestyleTarget#><#=lifestyle#>Disposable>().Lifestyle<#=lifestyle#>()); <#="\r\n\r\n"#><# + } +#> container.Register(Component.For().LifestyleScoped()); <#="\r\n"#><# +#> container.Register(Component.For().LifestyleScoped()); <#="\r\n"#><# +#> container.Register(Component.For().LifestyleScoped()); <#="\r\n"#><# + } +#> + + } + + public static void RegisterServiceCollection(IServiceCollection services) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "ServiceProviderOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> services.Add<#=lifestyle#><<#=lifestyleTarget#><#=lifestyle#>>(); <#="\r\n"#><# +#> services.Add<#=lifestyle#>(typeof(<#=lifestyleTarget#><#=lifestyle#>Generic)); <#="\r\n"#><# +#> services.Add<#=lifestyle#><<#=lifestyleTarget#><#=lifestyle#>Generic>(); <#="\r\n"#><# +#> services.Add<#=lifestyle#><<#=lifestyleTarget#><#=lifestyle#>Disposable>(); <#="\r\n\r\n"#><# + } +#> services.AddScoped(); <#="\r\n"#><# +#> services.AddScoped(); <#="\r\n"#><# +#> services.AddScoped(); <#="\r\n"#><# + } +#> + + } + + public static void RegisterCrossWired(IWindsorContainer container, IServiceCollection serviceCollection) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "CrossWired") continue; + + foreach(var lifestyle in lifestyles) + { +#> container.Register(Component.For<<#=lifestyleTarget#><#=lifestyle#>>().CrossWired().Lifestyle<#=lifestyle#>()); <#="\r\n"#><# +#> container.Register(Component.For<<#=lifestyleTarget#><#=lifestyle#>Generic>().CrossWired().Lifestyle<#=lifestyle#>()); <#="\r\n"#><# +#> container.Register(Component.For<<#=lifestyleTarget#><#=lifestyle#>Generic>().CrossWired().Lifestyle<#=lifestyle#>()); <#="\r\n"#><# +#> container.Register(Component.For<<#=lifestyleTarget#><#=lifestyle#>Disposable>().CrossWired().Lifestyle<#=lifestyle#>()); <#="\r\n\r\n"#><# + } +#> container.Register(Component.For().CrossWired().LifestyleSingleton()); <#="\r\n"#><# +#> container.Register(Component.For().CrossWired().LifestyleScoped()); <#="\r\n"#><# +#> container.Register(Component.For().CrossWired().LifestyleScoped()); <#="\r\n"#><# +#> container.Register(Component.For().CrossWired().LifestyleScoped()); <#="\r\n"#><# + } +#> + } + } + + public partial class ControllerWindsorOnly : Controller + { + public ControllerWindsorOnly + ( +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "WindsorOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> <#=lifestyleTarget#><#=lifestyle#> <#=lifestyleTarget#><#=lifestyle#>1, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>2, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>3, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Disposable <#=lifestyleTarget#><#=lifestyle#>4<# + + var a = Array.IndexOf(lifestyles, lifestyle)+1; + var b = Array.IndexOf(lifestyleTargets, lifestyleTarget)+1; + var index = (a) * (b); #> <#= index < 6 ? ",\r\n" : "" #><# + } + } +#>) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "WindsorOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> if (<#=lifestyleTarget#><#=lifestyle#>1 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>1)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>2 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>2)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>3 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>3)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>4 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>4)); <#="\r\n"#><# + } + } +#> } + } + + public partial class TagHelperWindsorOnly : TagHelper + { + public TagHelperWindsorOnly + ( +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "WindsorOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> <#=lifestyleTarget#><#=lifestyle#> <#=lifestyleTarget#><#=lifestyle#>1, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>2, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>3, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Disposable <#=lifestyleTarget#><#=lifestyle#>4<# + + var a = Array.IndexOf(lifestyles, lifestyle)+1; + var b = Array.IndexOf(lifestyleTargets, lifestyleTarget)+1; + var index = (a) * (b); #> <#= index < 6 ? ",\r\n" : "" #><# + } + } +#>) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "WindsorOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> if (<#=lifestyleTarget#><#=lifestyle#>1 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>1)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>2 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>2)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>3 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>3)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>4 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>4)); <#="\r\n"#><# + } + } +#> } + } + + public class ViewComponentWindsorOnly : ViewComponent + { + public ViewComponentWindsorOnly + ( +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "WindsorOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> <#=lifestyleTarget#><#=lifestyle#> <#=lifestyleTarget#><#=lifestyle#>1, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>2, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>3, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Disposable <#=lifestyleTarget#><#=lifestyle#>4<# + + var a = Array.IndexOf(lifestyles, lifestyle)+1; + var b = Array.IndexOf(lifestyleTargets, lifestyleTarget)+1; + var index = (a) * (b); #> <#= index < 6 ? ",\r\n" : "" #><# + } + } +#>) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "WindsorOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> if (<#=lifestyleTarget#><#=lifestyle#>1 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>1)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>2 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>2)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>3 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>3)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>4 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>4)); <#="\r\n"#><# + } + } +#> } + } + + public partial class ControllerServiceProviderOnly : Controller + { + public ControllerServiceProviderOnly + ( +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "ServiceProviderOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> <#=lifestyleTarget#><#=lifestyle#> <#=lifestyleTarget#><#=lifestyle#>1, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>2, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>3, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Disposable <#=lifestyleTarget#><#=lifestyle#>4<# + + var a = Array.IndexOf(lifestyles, lifestyle)+1; + var b = Array.IndexOf(lifestyleTargets, lifestyleTarget)+1; + var index = (a) * (b); #> <#= index < 3 ? ",\r\n" : "" #><# + } + } +#>) + { + } + } + + public partial class TagHelperServiceProviderOnly : TagHelper + { + public TagHelperServiceProviderOnly + ( +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "ServiceProviderOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> <#=lifestyleTarget#><#=lifestyle#> <#=lifestyleTarget#><#=lifestyle#>1, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>2, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>3, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Disposable <#=lifestyleTarget#><#=lifestyle#>4<# + + var a = Array.IndexOf(lifestyles, lifestyle)+1; + var b = Array.IndexOf(lifestyleTargets, lifestyleTarget)+1; + var index = (a) * (b); #> <#= index < 3 ? ",\r\n" : "" #><# + } + } +#>) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "ServiceProviderOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> if (<#=lifestyleTarget#><#=lifestyle#>1 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>1)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>2 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>2)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>3 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>3)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>4 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>4)); <#="\r\n"#><# + } + } +#> } + } + + public partial class ViewComponentServiceProviderOnly : ViewComponent + { + public ViewComponentServiceProviderOnly + ( +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "ServiceProviderOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> <#=lifestyleTarget#><#=lifestyle#> <#=lifestyleTarget#><#=lifestyle#>1, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>2, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>3, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Disposable <#=lifestyleTarget#><#=lifestyle#>4<# + + var a = Array.IndexOf(lifestyles, lifestyle)+1; + var b = Array.IndexOf(lifestyleTargets, lifestyleTarget)+1; + var index = (a) * (b); #> <#= index < 3 ? ",\r\n" : "" #><# + } + } +#>) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "ServiceProviderOnly") continue; + + foreach(var lifestyle in lifestyles) + { +#> if (<#=lifestyleTarget#><#=lifestyle#>1 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>1)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>2 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>2)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>3 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>3)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>4 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>4)); <#="\r\n"#><# + } + } +#> } + } + + public partial class ControllerCrossWired : Controller + { + public ControllerCrossWired + ( +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "CrossWired") continue; + + foreach(var lifestyle in lifestyles) + { +#> <#=lifestyleTarget#><#=lifestyle#> <#=lifestyleTarget#><#=lifestyle#>1, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>2, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>3, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Disposable <#=lifestyleTarget#><#=lifestyle#>4<# + + var a = Array.IndexOf(lifestyles, lifestyle)+1; + var b = Array.IndexOf(lifestyleTargets, lifestyleTarget)+1; + var index = (a) * (b); #> <#= index < 8 ? ",\r\n" : "" #><# + } + } +#>) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "CrossWired") continue; + + foreach(var lifestyle in lifestyles) + { +#> if (<#=lifestyleTarget#><#=lifestyle#>1 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>1)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>2 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>2)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>3 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>3)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>4 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>4)); <#="\r\n"#><# + } + } +#> } + } + + public partial class TagHelperCrossWired : TagHelper + { + public TagHelperCrossWired + ( +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "CrossWired") continue; + + foreach(var lifestyle in lifestyles) + { +#> <#=lifestyleTarget#><#=lifestyle#> <#=lifestyleTarget#><#=lifestyle#>1, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>2, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>3, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Disposable <#=lifestyleTarget#><#=lifestyle#>4<# + + var a = Array.IndexOf(lifestyles, lifestyle)+1; + var b = Array.IndexOf(lifestyleTargets, lifestyleTarget)+1; + var index = (a) * (b); #> <#= index < 8 ? ",\r\n" : "" #><# + } + } +#>) + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "CrossWired") continue; + + foreach(var lifestyle in lifestyles) + { +#> if (<#=lifestyleTarget#><#=lifestyle#>1 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>1)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>2 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>2)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>3 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>3)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>4 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>4)); <#="\r\n"#><# + } + } +#> } + } + + public partial class ViewComponentCrossWired : ViewComponent + { + public ViewComponentCrossWired + ( +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "CrossWired") continue; + + foreach(var lifestyle in lifestyles) + { +#> <#=lifestyleTarget#><#=lifestyle#> <#=lifestyleTarget#><#=lifestyle#>1, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>2, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Generic <#=lifestyleTarget#><#=lifestyle#>3, <#="\r\n"#><# +#> <#=lifestyleTarget#><#=lifestyle#>Disposable <#=lifestyleTarget#><#=lifestyle#>4<# + + var a = Array.IndexOf(lifestyles, lifestyle)+1; + var b = Array.IndexOf(lifestyleTargets, lifestyleTarget)+1; + var index = (a) * (b); #> <#= index < 8 ? ",\r\n" : "" #><# + } + } +#>) + + { +<# + foreach(var lifestyleTarget in lifestyleTargets) + { + if (lifestyleTarget != "CrossWired") continue; + + foreach(var lifestyle in lifestyles) + { +#> if (<#=lifestyleTarget#><#=lifestyle#>1 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>1)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>2 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>2)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>3 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>3)); <#="\r\n"#><# +#> if (<#=lifestyleTarget#><#=lifestyle#>4 == null) throw new ArgumentException(nameof(<#=lifestyleTarget#><#=lifestyle#>4)); <#="\r\n"#><# + } + } +#> } + } +} diff --git a/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/ApplicationBuilder.cs b/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/ApplicationBuilder.cs new file mode 100644 index 0000000000..e9682b5bd4 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/ApplicationBuilder.cs @@ -0,0 +1,29 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests.Framework.Builders +{ + using System; + + using Microsoft.AspNetCore.Builder; + + public class ApplicationBuilder + { + public static IApplicationBuilder New(IServiceProvider serviceProvider) + { + var applicationBuilder = new Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder(serviceProvider); + return applicationBuilder; + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/ServiceCollectionBuilder.cs b/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/ServiceCollectionBuilder.cs new file mode 100644 index 0000000000..0f838d3038 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/ServiceCollectionBuilder.cs @@ -0,0 +1,29 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests.Framework.Builders +{ + using Castle.Facilities.AspNetCore.Tests.Fakes; + using Microsoft.Extensions.DependencyInjection; + + public class ServiceCollectionBuilder + { + public static ServiceCollection New() + { + var serviceCollection = new ServiceCollection(); + ModelInstaller.RegisterServiceCollection(serviceCollection); + return serviceCollection; + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/ServiceProviderBuilder.cs b/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/ServiceProviderBuilder.cs new file mode 100644 index 0000000000..cc410f8913 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/ServiceProviderBuilder.cs @@ -0,0 +1,28 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests.Framework.Builders +{ + using System; + + using Microsoft.Extensions.DependencyInjection; + + public class ServiceProviderBuilder + { + public static IServiceProvider New(IServiceCollection services) + { + return services.BuildServiceProvider(validateScopes: false); + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/WindsorContainerBuilder.cs b/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/WindsorContainerBuilder.cs new file mode 100644 index 0000000000..958def10ae --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Framework/Builders/WindsorContainerBuilder.cs @@ -0,0 +1,49 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests.Framework.Builders +{ + using System; + + using Castle.Facilities.AspNetCore.Tests.Fakes; + using Castle.Windsor; + + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + + public class WindsorContainerBuilder + { + public static IWindsorContainer New(IServiceCollection services, Action configure, Func serviceProviderFactory) + { + return BuildWindsorContainer(services, configure, serviceProviderFactory); + } + + private static IWindsorContainer BuildWindsorContainer(IServiceCollection services, Action configure = null, Func serviceProviderFactory = null) + { + var container = new WindsorContainer().AddFacility(f => f.CrossWiresInto(services)); + + RegisterApplicationComponents(container, services); + + services.AddWindsor(container, configure, serviceProviderFactory); + + return container; + } + + private static void RegisterApplicationComponents(IWindsorContainer container, IServiceCollection serviceCollection) + { + ModelInstaller.RegisterWindsor(container); + ModelInstaller.RegisterCrossWired(container, serviceCollection); + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore.Tests/Framework/TestContext.cs b/src/Castle.Facilities.AspNetCore.Tests/Framework/TestContext.cs new file mode 100644 index 0000000000..a92bc88940 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Framework/TestContext.cs @@ -0,0 +1,67 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests.Framework +{ + using System; + + using Castle.Windsor; + + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + + public class TestContext : IDisposable + { + public TestContext(IServiceCollection serviceCollection, IServiceProvider serviceProvider, IApplicationBuilder applicationBuilder, IWindsorContainer container, IDisposable windsorScope) + { + ServiceCollection = serviceCollection; + ServiceProvider = serviceProvider; + ApplicationBuilder = applicationBuilder; + WindsorContainer = container; + WindsorScope = windsorScope; + } + + public IApplicationBuilder ApplicationBuilder { get; } + public IServiceCollection ServiceCollection { get; } + public IServiceProvider ServiceProvider { get; private set; } + + public IDisposable WindsorScope { get; private set; } + public IWindsorContainer WindsorContainer { get; private set; } + + public void DisposeServiceProvider() + { + (ServiceProvider as IDisposable)?.Dispose(); + ServiceProvider = null; + } + + public void DisposeWindsorContainer() + { + WindsorContainer.Dispose(); + WindsorContainer = null; + } + + public void DisposeWindsorScope() + { + WindsorScope.Dispose(); + WindsorScope = null; + } + + public void Dispose() + { + WindsorScope?.Dispose(); + WindsorContainer?.Dispose(); + (ServiceProvider as IDisposable)?.Dispose(); + } + } +} diff --git a/src/Castle.Facilities.AspNetCore.Tests/Framework/TestContextFactory.cs b/src/Castle.Facilities.AspNetCore.Tests/Framework/TestContextFactory.cs new file mode 100644 index 0000000000..b59e455da1 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Framework/TestContextFactory.cs @@ -0,0 +1,39 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests.Framework +{ + using System; + + using Castle.MicroKernel.Lifestyle; + using Castle.Facilities.AspNetCore.Tests.Framework.Builders; + + public class TestContextFactory + { + public static TestContext Get(Action configure = null, Func serviceProviderFactory = null) + { + IServiceProvider serviceProvider = null; + + var serviceCollection = ServiceCollectionBuilder.New(); + + var container = WindsorContainerBuilder.New(serviceCollection, + configure ?? (opts => opts.UseEntryAssembly(typeof(TestContextFactory).Assembly)), + serviceProviderFactory ?? (() => serviceProvider = ServiceProviderBuilder.New(serviceCollection))); + + var applicationBuilder = ApplicationBuilder.New(serviceProvider); + + return new TestContext(serviceCollection, serviceProvider, applicationBuilder, container, container.RequireScope()); + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore.Tests/Resolvers/FrameworkDependencyResolverTestCase.cs b/src/Castle.Facilities.AspNetCore.Tests/Resolvers/FrameworkDependencyResolverTestCase.cs new file mode 100644 index 0000000000..05c2618f56 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/Resolvers/FrameworkDependencyResolverTestCase.cs @@ -0,0 +1,170 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests.Resolvers +{ + using System; + + using Castle.Facilities.AspNetCore.Tests.Fakes; + using Castle.Facilities.AspNetCore.Resolvers; + using Castle.Facilities.AspNetCore.Tests.Framework; + + using Microsoft.Extensions.DependencyInjection; + + using NUnit.Framework; + + [TestFixture] + public class FrameworkDependencyResolverTestCase + { + private Framework.TestContext testContext; + private FrameworkDependencyResolver frameworkDependencyResolver; + + [SetUp] + public void SetUp() + { + testContext = TestContextFactory.Get(); + frameworkDependencyResolver = new FrameworkDependencyResolver(testContext.ServiceCollection); + frameworkDependencyResolver.AcceptServiceProvider(testContext.ServiceProvider); + } + + [TearDown] + public void TearDown() + { + testContext.Dispose(); + } + + [TestCase(typeof(ServiceProviderOnlyTransient))] + [TestCase(typeof(ServiceProviderOnlyTransientGeneric))] + [TestCase(typeof(ServiceProviderOnlyTransientGeneric))] + [TestCase(typeof(ServiceProviderOnlyTransientDisposable))] + [TestCase(typeof(ServiceProviderOnlyScoped))] + [TestCase(typeof(ServiceProviderOnlyScopedGeneric))] + [TestCase(typeof(ServiceProviderOnlyScopedGeneric))] + [TestCase(typeof(ServiceProviderOnlyScopedDisposable))] + [TestCase(typeof(ServiceProviderOnlySingleton))] + [TestCase(typeof(ServiceProviderOnlySingletonGeneric))] + [TestCase(typeof(ServiceProviderOnlySingletonGeneric))] + [TestCase(typeof(ServiceProviderOnlySingletonDisposable))] + [TestCase(typeof(ControllerServiceProviderOnly))] + [TestCase(typeof(TagHelperServiceProviderOnly))] + [TestCase(typeof(ViewComponentServiceProviderOnly))] + public void Should_match_ServiceProvider_services(Type serviceType) + { + Assert.That(frameworkDependencyResolver.HasMatchingType(serviceType), Is.True); + } + + [TestCase(typeof(CrossWiredTransient))] + [TestCase(typeof(CrossWiredTransientGeneric))] + [TestCase(typeof(CrossWiredTransientGeneric))] + [TestCase(typeof(CrossWiredTransientDisposable))] + [TestCase(typeof(CrossWiredScoped))] + [TestCase(typeof(CrossWiredScopedGeneric))] + [TestCase(typeof(CrossWiredScopedGeneric))] + [TestCase(typeof(CrossWiredScopedDisposable))] + [TestCase(typeof(CrossWiredSingleton))] + [TestCase(typeof(CrossWiredSingletonGeneric))] + [TestCase(typeof(CrossWiredSingletonGeneric))] + [TestCase(typeof(CrossWiredSingletonDisposable))] + [TestCase(typeof(ControllerCrossWired))] + [TestCase(typeof(TagHelperCrossWired))] + [TestCase(typeof(ViewComponentCrossWired))] + public void Should_match_CrossWired_services(Type serviceType) + { + Assert.That(frameworkDependencyResolver.HasMatchingType(serviceType), Is.True); + } + + [TestCase(typeof(WindsorOnlyTransient))] + [TestCase(typeof(WindsorOnlyTransientGeneric))] + [TestCase(typeof(WindsorOnlyTransientGeneric))] + [TestCase(typeof(WindsorOnlyTransientDisposable))] + [TestCase(typeof(WindsorOnlyScoped))] + [TestCase(typeof(WindsorOnlyScopedGeneric))] + [TestCase(typeof(WindsorOnlyScopedGeneric))] + [TestCase(typeof(WindsorOnlyScopedDisposable))] + [TestCase(typeof(WindsorOnlySingleton))] + [TestCase(typeof(WindsorOnlySingletonGeneric))] + [TestCase(typeof(WindsorOnlySingletonGeneric))] + [TestCase(typeof(WindsorOnlySingletonDisposable))] + [TestCase(typeof(ControllerWindsorOnly))] + [TestCase(typeof(TagHelperWindsorOnly))] + [TestCase(typeof(ViewComponentWindsorOnly))] + public void Should_not_match_WindsorOnly_services(Type serviceType) + { + Assert.That(!frameworkDependencyResolver.HasMatchingType(serviceType), Is.True); + } + + [TestCase(typeof(ServiceProviderOnlyTransient))] + [TestCase(typeof(ServiceProviderOnlyTransientGeneric))] + [TestCase(typeof(ServiceProviderOnlyTransientGeneric))] + [TestCase(typeof(ServiceProviderOnlyTransientDisposable))] + [TestCase(typeof(ServiceProviderOnlyScoped))] + [TestCase(typeof(ServiceProviderOnlyScopedGeneric))] + [TestCase(typeof(ServiceProviderOnlyScopedGeneric))] + [TestCase(typeof(ServiceProviderOnlyScopedDisposable))] + [TestCase(typeof(ServiceProviderOnlySingleton))] + [TestCase(typeof(ServiceProviderOnlySingletonGeneric))] + [TestCase(typeof(ServiceProviderOnlySingletonGeneric))] + [TestCase(typeof(ServiceProviderOnlySingletonDisposable))] + [TestCase(typeof(ControllerServiceProviderOnly))] + [TestCase(typeof(TagHelperServiceProviderOnly))] + [TestCase(typeof(ViewComponentServiceProviderOnly))] + public void Should_resolve_all_ServiceProviderOnly_services_from_ServiceProvider(Type serviceType) + { + Assert.DoesNotThrow(() => + { + testContext.ServiceProvider.GetRequiredService(serviceType); + }); + } + + [TestCase(typeof(CrossWiredTransient))] + [TestCase(typeof(CrossWiredTransientGeneric))] + [TestCase(typeof(CrossWiredTransientGeneric))] + [TestCase(typeof(CrossWiredTransientDisposable))] + [TestCase(typeof(CrossWiredScoped))] + [TestCase(typeof(CrossWiredScopedGeneric))] + [TestCase(typeof(CrossWiredScopedGeneric))] + [TestCase(typeof(CrossWiredScopedDisposable))] + [TestCase(typeof(CrossWiredSingleton))] + [TestCase(typeof(CrossWiredSingletonGeneric))] + [TestCase(typeof(CrossWiredSingletonGeneric))] + [TestCase(typeof(CrossWiredSingletonDisposable))] + [TestCase(typeof(ControllerCrossWired))] + [TestCase(typeof(TagHelperCrossWired))] + [TestCase(typeof(ViewComponentCrossWired))] + public void Should_resolve_all_CrossWiredOnly_services_from_ServiceProvider(Type serviceType) + { + Assert.DoesNotThrow(() => + { + testContext.ServiceProvider.GetRequiredService(serviceType); + }); + } + + [TestCase(typeof(ControllerCrossWired))] + [TestCase(typeof(TagHelperCrossWired))] + [TestCase(typeof(ViewComponentCrossWired))] + [TestCase(typeof(ControllerWindsorOnly))] + [TestCase(typeof(TagHelperWindsorOnly))] + [TestCase(typeof(ViewComponentWindsorOnly))] + [TestCase(typeof(ControllerServiceProviderOnly))] + [TestCase(typeof(TagHelperServiceProviderOnly))] + [TestCase(typeof(ViewComponentServiceProviderOnly))] + public void Should_resolve_ServiceProviderOnly_and_WindsorOnly_and_CrossWired_registered_Controllers_TagHelpers_and_ViewComponents_from_WindsorContainer(Type serviceType) + { + Assert.DoesNotThrow(() => + { + testContext.WindsorContainer.Resolve(serviceType); + }); + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore.Tests/WindsorRegistrationExtensionsTestCase.cs b/src/Castle.Facilities.AspNetCore.Tests/WindsorRegistrationExtensionsTestCase.cs new file mode 100644 index 0000000000..9b0cca52b2 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/WindsorRegistrationExtensionsTestCase.cs @@ -0,0 +1,342 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests +{ + using System; + + using Castle.Core; + using Castle.Facilities.AspNetCore.Tests.Fakes; + using Castle.Facilities.AspNetCore.Tests.Framework; + using Castle.MicroKernel.Registration; + using Castle.Windsor; + + using Microsoft.Extensions.DependencyInjection; + + using NUnit.Framework; + + [TestFixture] + public class WindsorRegistrationExtensionsTestCase + { + [SetUp] + public void SetUp() + { + testContext = TestContextFactory.Get(); + } + + [TearDown] + public void TearDown() + { + testContext?.Dispose(); + } + + private Framework.TestContext testContext; + + [TestCase(typeof(ControllerWindsorOnly))] + [TestCase(typeof(TagHelperWindsorOnly))] + [TestCase(typeof(ViewComponentWindsorOnly))] + public void Should_resolve_WindsorOnly_Controllers_TagHelpers_and_ViewComponents_from_WindsorContainer(Type serviceType) + { + Assert.DoesNotThrow(() => + { + testContext.WindsorContainer.Resolve(serviceType); + }); + } + + [TestCase(typeof(ControllerServiceProviderOnly))] + [TestCase(typeof(TagHelperServiceProviderOnly))] + [TestCase(typeof(ViewComponentServiceProviderOnly))] + public void Should_resolve_ServiceProviderOnly_Controllers_TagHelpers_and_ViewComponents_from_ServiceProvider(Type serviceType) + { + Assert.DoesNotThrow(() => + { + testContext.ServiceProvider.GetRequiredService(serviceType); + }); + } + + + [TestCase(typeof(ControllerCrossWired))] + [TestCase(typeof(TagHelperCrossWired))] + [TestCase(typeof(ViewComponentCrossWired))] + [TestCase(typeof(ControllerServiceProviderOnly))] + [TestCase(typeof(TagHelperServiceProviderOnly))] + [TestCase(typeof(ViewComponentServiceProviderOnly))] + public void Should_resolve_ServiceProviderOnly_and_CrossWired_Controllers_TagHelpers_and_ViewComponents_from_WindsorContainer_and_ServiceProvider(Type serviceType) + { + Assert.DoesNotThrow(() => + { + testContext.WindsorContainer.Resolve(serviceType); + testContext.ServiceProvider.GetRequiredService(serviceType); + }); + } + + [TestCase(typeof(CrossWiredScoped))] + [TestCase(typeof(CrossWiredSingleton))] + public void Should_resolve_CrossWired_Singleton_and_Scoped_as_same_instance_from_WindsorContainer_and_ServiceProvider(Type serviceType) + { + var instanceA = testContext.WindsorContainer.Resolve(serviceType); + var instanceB = testContext.ServiceProvider.GetRequiredService(serviceType); + + Assert.That(instanceA, Is.EqualTo(instanceB)); + } + + [TestCase(typeof(CrossWiredTransient))] + public void Should_resolve_CrossWired_Transient_as_different_instances_from_WindsorContainer_and_ServiceProvider(Type serviceType) + { + var instanceA = testContext.WindsorContainer.Resolve(serviceType); + var instanceB = testContext.ServiceProvider.GetRequiredService(serviceType); + + Assert.That(instanceA, Is.Not.EqualTo(instanceB)); + } + + [TestCase(typeof(CrossWiredSingletonDisposable))] + [TestCase(typeof(WindsorOnlySingletonDisposable))] + public void Should_not_Dispose_CrossWired_or_WindsorOnly_Singleton_disposables_when_Disposing_Windsor_Scope(Type serviceType) + { + var singleton = (IDisposableObservable)testContext.WindsorContainer.Resolve(serviceType); + testContext.DisposeWindsorScope(); + + Assert.That(singleton.Disposed, Is.False); + Assert.That(singleton.DisposedCount, Is.EqualTo(0)); + } + + [TestCase(typeof(WindsorOnlySingletonDisposable))] + public void Should_Dispose_WindsorOnly_Singleton_disposables_only_when_Disposing_WindsorContainer(Type serviceType) + { + var singleton = (IDisposableObservable)testContext.WindsorContainer.Resolve(serviceType); + testContext.DisposeWindsorContainer(); + + Assert.That(singleton.Disposed, Is.True); + Assert.That(singleton.DisposedCount, Is.EqualTo(1)); + } + + [TestCase(typeof(CrossWiredSingletonDisposable))] + public void Should_not_Dispose_CrossWired_Singleton_disposables_when_Disposing_WindsorContainer_because_it_is_tracked_by_the_ServiceProvider(Type serviceType) + { + var singleton = (IDisposableObservable)testContext.WindsorContainer.Resolve(serviceType); + testContext.DisposeWindsorContainer(); + + Assert.That(singleton.Disposed, Is.False); + Assert.That(singleton.DisposedCount, Is.EqualTo(0)); + } + + [TestCase(typeof(CrossWiredSingletonDisposable))] + [TestCase(typeof(ServiceProviderOnlySingletonDisposable))] + public void Should_Dispose_CrossWired_and_ServiceProviderOnly_Singleton_disposables_when_Disposing_ServiceProvider(Type serviceType) + { + var singleton = (IDisposableObservable)testContext.ServiceProvider.GetRequiredService(serviceType); + testContext.DisposeServiceProvider(); + + Assert.That(singleton.Disposed, Is.True); + Assert.That(singleton.DisposedCount, Is.EqualTo(1)); + } + + [TestCase(typeof(WindsorOnlyScopedDisposable))] + public void Should_Dispose_WindsorOnly_Scoped_disposables_when_Disposing_Windsor_Scope(Type serviceType) + { + var singleton = (IDisposableObservable)testContext.WindsorContainer.Resolve(serviceType); + testContext.DisposeWindsorScope(); + + Assert.That(singleton.Disposed, Is.True); + Assert.That(singleton.DisposedCount, Is.EqualTo(1)); + } + + [TestCase(typeof(CrossWiredScopedDisposable))] + public void Should_not_Dispose_CrossWired_Scoped_disposables_when_Disposing_Windsor_Scope_because_it_is_tracked_by_the_ServiceProvider(Type serviceType) + { + var singleton = (IDisposableObservable)testContext.WindsorContainer.Resolve(serviceType); + testContext.DisposeWindsorScope(); + + Assert.That(singleton.Disposed, Is.False); + Assert.That(singleton.DisposedCount, Is.EqualTo(0)); + } + + [TestCase(typeof(CrossWiredScopedDisposable))] + [TestCase(typeof(CrossWiredTransientDisposable))] + public void Should_Dispose_CrossWired_Scoped_and_Transient_disposables_when_Disposing_ServiceProvider_Scope(Type serviceType) + { + IDisposableObservable scoped; + using (var serviceProviderScope = testContext.ServiceProvider.CreateScope()) + { + scoped = (IDisposableObservable)serviceProviderScope.ServiceProvider.GetRequiredService(serviceType); + } + + Assert.That(scoped.Disposed, Is.True); + Assert.That(scoped.DisposedCount, Is.EqualTo(1)); + } + + [TestCase(typeof(CrossWiredTransientDisposable))] + public void Should_not_Dispose_CrossWired_Transient_disposables_when_Disposing_Windsor_Scope_because_is_tracked_by_the_ServiceProvider(Type serviceType) + { + var singleton = (IDisposableObservable)testContext.WindsorContainer.Resolve(serviceType); + testContext.DisposeWindsorScope(); + + Assert.That(singleton.Disposed, Is.False); + Assert.That(singleton.DisposedCount, Is.EqualTo(0)); + } + + [TestCase(typeof(CrossWiredSingletonDisposable))] + [TestCase(typeof(ServiceProviderOnlySingletonDisposable))] + public void Should_not_Dispose_CrossWired_or_ServiceOnly_Singleton_disposables_when_Disposing_ServiceProviderScope(Type serviceType) + { + IDisposableObservable singleton; + + using (var serviceProviderScope = testContext.ServiceProvider.CreateScope()) + { + singleton = (IDisposableObservable)serviceProviderScope.ServiceProvider.GetRequiredService(serviceType); + } + + Assert.That(singleton.Disposed, Is.False); + Assert.That(singleton.DisposedCount, Is.EqualTo(0)); + } + + [TestCase(typeof(CompositeTagHelper))] + [TestCase(typeof(CompositeController))] + [TestCase(typeof(CompositeViewComponent))] + public void Should_resolve_Composite_Singleton_from_WindsorContainer(Type compositeType) + { + testContext.WindsorContainer.Register(Component.For(compositeType).LifestyleSingleton()); + + Assert.DoesNotThrow(() => + { + testContext.WindsorContainer.Resolve(compositeType); + }); + } + + [TestCase(typeof(CompositeTagHelper))] + [TestCase(typeof(CompositeController))] + [TestCase(typeof(CompositeViewComponent))] + public void Should_resolve_Composite_Scoped_from_WindsorContainer(Type compositeType) + { + testContext.WindsorContainer.Register(Component.For(compositeType).LifestyleScoped()); + + Assert.DoesNotThrow(() => + { + testContext.WindsorContainer.Resolve(compositeType); + }); + } + + [TestCase(typeof(CompositeTagHelper))] + [TestCase(typeof(CompositeController))] + [TestCase(typeof(CompositeViewComponent))] + public void Should_resolve_Composite_Transient_from_WindsorContainer(Type compositeType) + { + testContext.WindsorContainer.Register(Component.For(compositeType).LifestyleTransient()); + + Assert.DoesNotThrow(() => + { + testContext.WindsorContainer.Resolve(compositeType); + }); + } + + [TestCase(typeof(CompositeTagHelper))] + [TestCase(typeof(CompositeController))] + [TestCase(typeof(CompositeViewComponent))] + public void Should_resolve_Composite_Singleton_CrossWired_from_ServiceProvider(Type compositeType) + { + testContext.WindsorContainer.Register(Component.For(compositeType).CrossWired().LifestyleSingleton()); + + Assert.DoesNotThrow(() => + { + using (var sp = testContext.ServiceCollection.BuildServiceProvider()) + { + sp.GetRequiredService(compositeType); + } + }); + } + + [TestCase(typeof(CompositeTagHelper))] + [TestCase(typeof(CompositeController))] + [TestCase(typeof(CompositeViewComponent))] + public void Should_resolve_Composite_Scoped_CrossWired_from_ServiceProvider(Type compositeType) + { + testContext.WindsorContainer.Register(Component.For(compositeType).CrossWired().LifestyleScoped()); + + Assert.DoesNotThrow(() => + { + using (var sp = testContext.ServiceCollection.BuildServiceProvider()) + { + sp.GetRequiredService(compositeType); + } + }); + } + + [TestCase(typeof(CompositeTagHelper))] + [TestCase(typeof(CompositeController))] + [TestCase(typeof(CompositeViewComponent))] + public void Should_resolve_Composite_Transient_CrossWired_from_ServiceProvider(Type compositeType) + { + testContext.WindsorContainer.Register(Component.For(compositeType).CrossWired().LifestyleTransient()); + + Assert.DoesNotThrow(() => + { + using (var sp = testContext.ServiceCollection.BuildServiceProvider()) + { + sp.GetRequiredService(compositeType); + } + }); + } + + [TestCase(LifestyleType.Bound)] + [TestCase(LifestyleType.Custom)] + [TestCase(LifestyleType.Pooled)] + [TestCase(LifestyleType.Thread)] + //[TestCase(LifestyleType.Undefined)] // Already throws System.ArgumentOutOfRangeException: Undefined is not a valid lifestyle type + public void Should_throw_if_CrossWired_with_these_LifestyleTypes(LifestyleType unsupportedLifestyleType) + { + Assert.Throws(() => + { + var componentRegistration = Component.For().CrossWired(); + componentRegistration.LifeStyle.Is(unsupportedLifestyleType); + if (unsupportedLifestyleType == LifestyleType.Custom) + { + componentRegistration.LifestyleCustom(); + } + testContext.WindsorContainer.Register(componentRegistration); + }); + } + + [Test] + public void Should_throw_if_Facility_is_added_without_calling_CrossWiresInto_on_IWindsorContainer_AddFacility() + { + using (var container = new WindsorContainer()) + { + Assert.Throws(() => + { + container.AddFacility(); + }); + } + } + + [Test] // https://github.com/castleproject/Windsor/issues/411 + public void Should_resolve_IMiddleware_from_Windsor() + { + testContext.WindsorContainer.GetFacility().RegistersMiddlewareInto(testContext.ApplicationBuilder); + + testContext.WindsorContainer.Register(Component.For().LifestyleScoped().AsMiddleware()); + + Assert.DoesNotThrow(() => { testContext.WindsorContainer.Resolve(); }); + } + + [Test] // https://github.com/castleproject/Windsor/issues/411 + public void Should_resolve_IMiddleware_from_Windsor_with_custom_dependencies() + { + testContext.WindsorContainer.GetFacility().RegistersMiddlewareInto(testContext.ApplicationBuilder); + + testContext.WindsorContainer.Register(Component.For().DependsOn(Dependency.OnValue(new AnyComponent())).LifestyleScoped().AsMiddleware()); + + Assert.DoesNotThrow(() => { testContext.WindsorContainer.Resolve(); }); + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore.Tests/WindsorRegistrationOptionsTestCase.cs b/src/Castle.Facilities.AspNetCore.Tests/WindsorRegistrationOptionsTestCase.cs new file mode 100644 index 0000000000..51e2a0e6ec --- /dev/null +++ b/src/Castle.Facilities.AspNetCore.Tests/WindsorRegistrationOptionsTestCase.cs @@ -0,0 +1,67 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Tests +{ + using System; + + using Castle.Facilities.AspNetCore.Tests.Framework; + + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Razor.TagHelpers; + + using NUnit.Framework; + + [TestFixture, Order(3)] + public class WindsorRegistrationOptionsTestCase + { + [SetUp] + public void SetUp() + { + testContext = TestContextFactory.Get(opts => opts + .UseEntryAssembly(typeof(WindsorRegistrationOptionsTestCase).Assembly) + .RegisterTagHelpers(typeof(OverrideTagHelper).Assembly) + .RegisterControllers(typeof(OverrideController).Assembly) + .RegisterTagHelpers(typeof(OverrideViewComponent).Assembly)); + } + + [TearDown] + public void TearDown() + { + testContext.Dispose(); + } + + private Framework.TestContext testContext; + + [TestCase(typeof(OverrideTagHelper))] + [TestCase(typeof(OverrideController))] + [TestCase(typeof(OverrideViewComponent))] + public void Should_resolve_overidden_Controllers_TagHelpers_and_ViewComponents_using_WindsorRegistrationOptions(Type optionsResolvableType) + { + Assert.DoesNotThrow(() => { testContext.WindsorContainer.Resolve(optionsResolvableType); }); + } + + public class OverrideTagHelper : TagHelper + { + } + + public class OverrideController : Controller + { + } + + public class OverrideViewComponent : ViewComponent + { + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/Activators/DelegatingControllerActivator.cs b/src/Castle.Facilities.AspNetCore/Activators/DelegatingControllerActivator.cs index cde96a75cd..db5a88cf9a 100644 --- a/src/Castle.Facilities.AspNetCore/Activators/DelegatingControllerActivator.cs +++ b/src/Castle.Facilities.AspNetCore/Activators/DelegatingControllerActivator.cs @@ -24,10 +24,12 @@ internal sealed class DelegatingControllerActivator : IControllerActivator private readonly Func controllerCreator; private readonly Action controllerReleaser; - public DelegatingControllerActivator(Func controllerCreator, Action controllerReleaser = null) + public DelegatingControllerActivator( + Func controllerCreator, + Action controllerReleaser) { this.controllerCreator = controllerCreator ?? throw new ArgumentNullException(nameof(controllerCreator)); - this.controllerReleaser = controllerReleaser ?? ((_, __) => { }); + this.controllerReleaser = controllerReleaser ?? throw new ArgumentNullException(nameof(controllerReleaser)); } public object Create(ControllerContext context) diff --git a/src/Castle.Facilities.AspNetCore/Activators/DelegatingTagHelperActivator.cs b/src/Castle.Facilities.AspNetCore/Activators/DelegatingTagHelperActivator.cs index 8164dd9a63..b27557f6d2 100644 --- a/src/Castle.Facilities.AspNetCore/Activators/DelegatingTagHelperActivator.cs +++ b/src/Castle.Facilities.AspNetCore/Activators/DelegatingTagHelperActivator.cs @@ -26,7 +26,10 @@ internal sealed class DelegatingTagHelperActivator : ITagHelperActivator private readonly Func customTagHelperCreator; private readonly ITagHelperActivator defaultTagHelperActivator; - public DelegatingTagHelperActivator(Predicate customCreatorSelector, Func customTagHelperCreator, ITagHelperActivator defaultTagHelperActivator) + public DelegatingTagHelperActivator( + Predicate customCreatorSelector, + Func customTagHelperCreator, + ITagHelperActivator defaultTagHelperActivator) { this.customCreatorSelector = customCreatorSelector ?? throw new ArgumentNullException(nameof(customCreatorSelector)); this.customTagHelperCreator = customTagHelperCreator ?? throw new ArgumentNullException(nameof(customTagHelperCreator)); diff --git a/src/Castle.Facilities.AspNetCore/Activators/DelegatingViewComponentActivator.cs b/src/Castle.Facilities.AspNetCore/Activators/DelegatingViewComponentActivator.cs index 1d9a1455a0..30f205df5e 100644 --- a/src/Castle.Facilities.AspNetCore/Activators/DelegatingViewComponentActivator.cs +++ b/src/Castle.Facilities.AspNetCore/Activators/DelegatingViewComponentActivator.cs @@ -23,10 +23,10 @@ internal sealed class DelegatingViewComponentActivator : IViewComponentActivator private readonly Func viewComponentCreator; private readonly Action viewComponentReleaser; - public DelegatingViewComponentActivator(Func viewComponentCreator, Action viewComponentReleaser = null) + public DelegatingViewComponentActivator(Func viewComponentCreator, Action viewComponentReleaser) { this.viewComponentCreator = viewComponentCreator ?? throw new ArgumentNullException(nameof(viewComponentCreator)); - this.viewComponentReleaser = viewComponentReleaser ?? (_ => { }); + this.viewComponentReleaser = viewComponentReleaser ?? throw new ArgumentNullException(nameof(viewComponentReleaser)); } public object Create(ViewComponentContext context) diff --git a/src/Castle.Facilities.AspNetCore/AspNetCoreExtensions.cs b/src/Castle.Facilities.AspNetCore/AspNetCoreExtensions.cs index afd977ff59..43c44515dc 100644 --- a/src/Castle.Facilities.AspNetCore/AspNetCoreExtensions.cs +++ b/src/Castle.Facilities.AspNetCore/AspNetCoreExtensions.cs @@ -15,6 +15,7 @@ namespace Castle.Facilities.AspNetCore { using System; + using System.Collections.Generic; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -22,7 +23,7 @@ namespace Castle.Facilities.AspNetCore internal static class AspNetCoreExtensions { - public static void AddRequestScopingMiddleware(this IServiceCollection services, Func requestScopeProvider) + public static void AddRequestScopingMiddleware(this IServiceCollection services, Func> requestScopeProvider) { if (services == null) throw new ArgumentNullException(nameof(services)); @@ -33,9 +34,9 @@ public static void AddRequestScopingMiddleware(this IServiceCollection services, private sealed class RequestScopingStartupFilter : IStartupFilter { - private readonly Func requestScopeProvider; + private readonly Func> requestScopeProvider; - public RequestScopingStartupFilter(Func requestScopeProvider) + public RequestScopingStartupFilter(Func> requestScopeProvider) { this.requestScopeProvider = requestScopeProvider ?? throw new ArgumentNullException(nameof(requestScopeProvider)); } @@ -54,10 +55,18 @@ private void ConfigureRequestScoping(IApplicationBuilder builder) { builder.Use(async (context, next) => { - using (requestScopeProvider()) + var scopes = requestScopeProvider(); + try { await next(); } + finally + { + foreach (var scope in scopes) + { + scope.Dispose(); + } + } }); } } diff --git a/src/Castle.Facilities.AspNetCore/AspNetCoreFacility.cs b/src/Castle.Facilities.AspNetCore/AspNetCoreFacility.cs new file mode 100644 index 0000000000..a65a032af2 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore/AspNetCoreFacility.cs @@ -0,0 +1,59 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore +{ + using System; + + using Castle.Facilities.AspNetCore.Contributors; + using Castle.MicroKernel.Facilities; + using Castle.Windsor; + + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + + public class AspNetCoreFacility : AbstractFacility + { + internal const string IsCrossWiredIntoServiceCollectionKey = "windsor-registration-is-also-registered-in-service-collection"; + internal const string IsRegisteredAsMiddlewareIntoApplicationBuilderKey = "windsor-registration-is-also-registered-as-middleware"; + + private CrossWiringComponentModelContributor crossWiringComponentModelContributor; + private MiddlewareComponentModelContributor middlewareComponentModelContributor; + + protected override void Init() + { + Kernel.ComponentModelBuilder.AddContributor(crossWiringComponentModelContributor ?? throw new InvalidOperationException("Please call `Container.AddFacility(f => f.CrossWiresInto(services));` first. This should happen before any cross wiring registration. Please see https://github.com/castleproject/Windsor/blob/master/docs/aspnetcore-facility.md")); + } + + /// + /// Installation of the for registering components in both the and the via the component registration extension + /// + /// + public void CrossWiresInto(IServiceCollection services) + { + crossWiringComponentModelContributor = new CrossWiringComponentModelContributor(services); + } + + /// + /// Registers Windsor `aware` into the via the component registration extension + /// + /// + public void RegistersMiddlewareInto(IApplicationBuilder applicationBuilder) + { + middlewareComponentModelContributor = new MiddlewareComponentModelContributor(crossWiringComponentModelContributor.Services, applicationBuilder); + Kernel.ComponentModelBuilder.AddContributor(middlewareComponentModelContributor); // Happens after Init() in Startup.Configure(IApplicationBuilder, ...) + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/AspNetCoreMvcExtensions.cs b/src/Castle.Facilities.AspNetCore/AspNetCoreMvcExtensions.cs index 1ebbab5c94..b7aa4222b5 100644 --- a/src/Castle.Facilities.AspNetCore/AspNetCoreMvcExtensions.cs +++ b/src/Castle.Facilities.AspNetCore/AspNetCoreMvcExtensions.cs @@ -28,20 +28,20 @@ namespace Castle.Facilities.AspNetCore internal static class AspNetCoreMvcExtensions { - public static void AddCustomControllerActivation(this IServiceCollection services, Func activator) + public static void AddCustomControllerActivation(this IServiceCollection services, Func activator, Action releaser) { if (services == null) throw new ArgumentNullException(nameof(services)); if (activator == null) throw new ArgumentNullException(nameof(activator)); - services.AddSingleton(new DelegatingControllerActivator(context => activator(context.ActionDescriptor.ControllerTypeInfo.AsType()))); + services.AddSingleton(new DelegatingControllerActivator(context => activator(context.ActionDescriptor.ControllerTypeInfo.AsType()), (context, instance) => releaser(instance))); } - public static void AddCustomViewComponentActivation(this IServiceCollection services, Func activator) + public static void AddCustomViewComponentActivation(this IServiceCollection services, Func activator, Action releaser) { if (services == null) throw new ArgumentNullException(nameof(services)); if (activator == null) throw new ArgumentNullException(nameof(activator)); - services.AddSingleton(new DelegatingViewComponentActivator(activator)); + services.AddSingleton(new DelegatingViewComponentActivator(activator, releaser)); } public static void AddCustomTagHelperActivation(this IServiceCollection services, Func activator, Predicate applicationTypeSelector = null) diff --git a/src/Castle.Facilities.AspNetCore/CastleWindsorRegistrationExtensions.cs b/src/Castle.Facilities.AspNetCore/CastleWindsorRegistrationExtensions.cs deleted file mode 100644 index d3b17300b0..0000000000 --- a/src/Castle.Facilities.AspNetCore/CastleWindsorRegistrationExtensions.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2004-2018 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.Facilities.AspNetCore -{ - using System; - using System.Linq; - using System.Reflection; - - using Castle.Facilities.AspNetCore.Resolvers; - using Castle.MicroKernel.Lifestyle; - using Castle.MicroKernel.Registration; - using Castle.Windsor; - - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Mvc; - using Microsoft.AspNetCore.Razor.TagHelpers; - using Microsoft.Extensions.DependencyInjection; - - public static class CastleWindsorRegistrationExtensions - { - /// - /// Sets up framework level activators for Controllers, TagHelpers and ViewComponents and adds additional sub dependency resolvers - /// - /// ASP.NET Core service collection from Microsoft.Extensions.DependencyInjection - /// Windsor container which activators call resolve against - public static void AddCastleWindsor(this IServiceCollection services, IWindsorContainer container) - { - services.AddRequestScopingMiddleware(container.BeginScope); - services.AddCustomControllerActivation(container.Resolve); - services.AddCustomTagHelperActivation(container.Resolve); - services.AddCustomViewComponentActivation(container.Resolve); - container.Kernel.Resolver.AddSubResolver(new LoggerDependencyResolver(services)); - container.Kernel.Resolver.AddSubResolver(new FrameworkConfigurationDependencyResolver(services)); - } - - /// - /// Use this to register all Controllers, ViewComponents and TagHelpers - /// - /// Type from assembly to scan and register ASP.NET Core components - /// Application builder retained as extension - /// Windsor container to register framework types in - public static void UseCastleWindsor(this IApplicationBuilder app, IWindsorContainer container) - { - var assembly = typeof(TTypeAssembly).Assembly; - UseCastleWindsor(container, assembly); - } - - /// - /// Use this to register all Controllers, ViewComponents and TagHelpers. Will register the entry assembly and its referenced items - /// - /// Application builder retained as extension - /// Windsor container to register framework types in - public static void UseCastleWindsor(this IApplicationBuilder app, IWindsorContainer container) - { - var assembly = Assembly.GetEntryAssembly(); - UseCastleWindsor(container, assembly); - } - - /// - /// For registering middleware that consumes services in the constructor known to Castle Windsor. You can use - /// conventional methods for registering your middleware but then you have to re-register your dependencies - /// in the ASP.NET IServiceCollection. You should avoid doing this if possible and use this extension instead. - /// - /// Type of service that implements Microsoft.AspNetCore.Http.IMiddleware - /// Application builder - /// Windsor container - public static void UseCastleWindsorMiddleware(this IApplicationBuilder app, IWindsorContainer container) where T : class, IMiddleware - { - container.Register(Component.For()); - app.Use(async (context, next) => - { - var resolve = container.Resolve(); - await resolve.InvokeAsync(context, async (ctx) => await next()); - }); - } - - /// - /// Optional helpful exception to validate against types being installed into Windsor from the 'Microsoft.AspNetCore' - /// namespace. This guards against 'Torn Lifestyles' and 'Captive Dependencies' and will probably need to be extended. - /// - /// Windsor container - public static void AssertNoAspNetCoreRegistrations(this IWindsorContainer container) - { - var handlers = container.Kernel.GetHandlers(); - var hasAspNetCoreFrameworkRegistrations = handlers.Any(x => x.ComponentModel.Implementation.Namespace.StartsWith("Microsoft.AspNetCore")); - if (hasAspNetCoreFrameworkRegistrations) - { - throw new Exception( - "Looks like you have implementations registered from 'Microsoft.AspNetCore'. " + - "Please do not do this as it could lead to torn lifestyles and captive dependencies. " + - "Please remove the registrations from Castle.Windsor."); - } - } - - private static void UseCastleWindsor(IWindsorContainer container, Assembly assembly) - { - container.Register(Classes.FromAssemblyInThisApplication(assembly).BasedOn().LifestyleScoped()); - container.Register(Classes.FromAssemblyInThisApplication(assembly).BasedOn().LifestyleTransient()); - container.Register(Classes.FromAssemblyInThisApplication(assembly).BasedOn().LifestyleTransient()); - } - } -} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/Contributors/CrossWiringComponentModelContributor.cs b/src/Castle.Facilities.AspNetCore/Contributors/CrossWiringComponentModelContributor.cs new file mode 100644 index 0000000000..0dd63793cb --- /dev/null +++ b/src/Castle.Facilities.AspNetCore/Contributors/CrossWiringComponentModelContributor.cs @@ -0,0 +1,81 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Contributors +{ + using System; + using System.Linq; + + using Castle.Core; + using Castle.MicroKernel; + using Castle.MicroKernel.LifecycleConcerns; + using Castle.MicroKernel.Lifestyle; + using Castle.MicroKernel.ModelBuilder; + + using Microsoft.Extensions.DependencyInjection; + + public class CrossWiringComponentModelContributor : IContributeComponentModelConstruction + { + private readonly IServiceCollection services; + + public IServiceCollection Services => services; + + public CrossWiringComponentModelContributor(IServiceCollection services) + { + this.services = services ?? throw new InvalidOperationException("Please call `Container.AddFacility(f => f.CrossWiresInto(services));` first. This should happen before any cross wiring registration. Please see https://github.com/castleproject/Windsor/blob/master/docs/aspnetcore-facility.md"); + } + + public void ProcessModel(IKernel kernel, ComponentModel model) + { + if (model.Configuration.Attributes.Get(AspNetCoreFacility.IsCrossWiredIntoServiceCollectionKey) == Boolean.TrueString) + { + if (model.Lifecycle.HasDecommissionConcerns) + { + var disposableConcern = model.Lifecycle.DecommissionConcerns.OfType().FirstOrDefault(); + if (disposableConcern != null) + { + model.Lifecycle.Remove(disposableConcern); + } + } + + foreach (var serviceType in model.Services) + { + if (model.LifestyleType == LifestyleType.Transient) + { + services.AddTransient(serviceType, p => + { + return kernel.Resolve(serviceType); + }); + } + else if (model.LifestyleType == LifestyleType.Scoped) + { + services.AddScoped(serviceType, p => + { + kernel.RequireScope(); + return kernel.Resolve(serviceType); + }); + } + else if (model.LifestyleType == LifestyleType.Singleton) + { + services.AddSingleton(serviceType, p => kernel.Resolve(serviceType)); + } + else + { + throw new NotSupportedException($"The Castle Windsor ASP.NET Core facility only supports the following lifestyles: {nameof(LifestyleType.Transient)}, {nameof(LifestyleType.Scoped)} and {nameof(LifestyleType.Singleton)}."); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/Contributors/MiddlewareComponentModelContributor.cs b/src/Castle.Facilities.AspNetCore/Contributors/MiddlewareComponentModelContributor.cs new file mode 100644 index 0000000000..255d27d50d --- /dev/null +++ b/src/Castle.Facilities.AspNetCore/Contributors/MiddlewareComponentModelContributor.cs @@ -0,0 +1,65 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Contributors +{ + using System; + + using Castle.MicroKernel.Lifestyle; + using Castle.Core; + using Castle.MicroKernel; + using Castle.MicroKernel.ModelBuilder; + + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + + public class MiddlewareComponentModelContributor : IContributeComponentModelConstruction + { + private IServiceProvider provider; + private readonly IServiceCollection services; + private readonly IApplicationBuilder applicationBuilder; + + public MiddlewareComponentModelContributor(IServiceCollection services, IApplicationBuilder applicationBuilder) + { + this.services = services ?? throw new ArgumentNullException(nameof(services)); + this.applicationBuilder = applicationBuilder ?? throw new InvalidOperationException("Please call `Container.GetFacility(f => f.RegistersMiddlewareInto(applicationBuilder));` first. This should happen before any middleware registration. Please see https://github.com/castleproject/Windsor/blob/master/docs/aspnetcore-facility.md"); + } + + public void ProcessModel(IKernel kernel, ComponentModel model) + { + if (model.Configuration.Attributes.Get(AspNetCoreFacility.IsRegisteredAsMiddlewareIntoApplicationBuilderKey) == Boolean.TrueString) + { + foreach (var service in model.Services) + { + applicationBuilder.Use(async (context, next) => + { + var windsorScope = kernel.BeginScope(); + var serviceProviderScope = (provider = provider ?? services.BuildServiceProvider()).CreateScope(); + try + { + var middleware = (IMiddleware) kernel.Resolve(service); + await middleware.InvokeAsync(context, async (ctx) => await next()); + } + finally + { + serviceProviderScope.Dispose(); + windsorScope.Dispose(); + } + }); + } + } + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/Resolvers/FrameworkConfigurationDependencyResolver.cs b/src/Castle.Facilities.AspNetCore/Resolvers/FrameworkConfigurationDependencyResolver.cs deleted file mode 100644 index 0e0413ab30..0000000000 --- a/src/Castle.Facilities.AspNetCore/Resolvers/FrameworkConfigurationDependencyResolver.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2004-2018 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.Facilities.AspNetCore.Resolvers -{ - using System; - using System.Collections.Generic; - using System.Linq; - - using Castle.Core; - using Castle.MicroKernel; - using Castle.MicroKernel.Context; - - using Microsoft.Extensions.DependencyInjection; - - public class FrameworkConfigurationDependencyResolver : ISubDependencyResolver - { - private readonly ServiceProvider serviceProvider; - private readonly IServiceCollection serviceCollection; - - public FrameworkConfigurationDependencyResolver(IServiceCollection serviceCollection) - { - this.serviceCollection = serviceCollection; - serviceProvider = serviceCollection.BuildServiceProvider(); - } - - public bool CanResolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) - { - var dependencyType = dependency.TargetType; - - if (dependencyType.IsGenericType && HasMatchingGenericTypeDefinitions(dependencyType)) - { - return true; - } - - return serviceCollection.Any(x => x.ServiceType == dependencyType); - } - - public object Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) - { - return serviceProvider.GetService(dependency.TargetType); - } - - private bool HasMatchingGenericTypeDefinitions(Type dependencyType) - { - var dependencyGenericType = dependencyType.GetGenericTypeDefinition(); - var genericServiceTypes = serviceCollection.Where(x => x.ServiceType.IsGenericType).ToList(); - - if (HasMatchingGenericTypesWithArguments(dependencyType, dependencyGenericType, genericServiceTypes)) - { - return true; - } - - return HasMatchingGenericTypesWithoutArguments(dependencyGenericType, genericServiceTypes); - } - - private static bool HasMatchingGenericTypesWithArguments(Type dependencyType, Type dependencyGenericType, IEnumerable genericServiceTypes) - { - var genericTypesWithParameters = genericServiceTypes.Where( - x => x.ServiceType.GenericTypeArguments.Length > 0).ToList(); - - return genericTypesWithParameters.Any( - x => x.ServiceType.GetGenericTypeDefinition() == dependencyGenericType - && x.ServiceType.GenericTypeArguments.All( - sy => dependencyType.GenericTypeArguments.Contains(sy) - && dependencyType.GenericTypeArguments.Length == - x.ServiceType.GenericTypeArguments.Length)); - } - - private static bool HasMatchingGenericTypesWithoutArguments(Type dependencyGenericType, List genericServiceTypes) - { - var genericTypesWithoutParameters = genericServiceTypes.Where( - x => x.ServiceType.GenericTypeArguments.Length == 0 - && x.ImplementationType.GenericTypeArguments.Length == 0).ToList(); - - return genericTypesWithoutParameters.Any( - x => x.ServiceType == dependencyGenericType); - } - } -} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/Resolvers/FrameworkDependencyResolver.cs b/src/Castle.Facilities.AspNetCore/Resolvers/FrameworkDependencyResolver.cs new file mode 100644 index 0000000000..08b34d772a --- /dev/null +++ b/src/Castle.Facilities.AspNetCore/Resolvers/FrameworkDependencyResolver.cs @@ -0,0 +1,75 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Resolvers +{ + using System; + using System.Linq; + + using Castle.Core; + using Castle.MicroKernel; + using Castle.MicroKernel.Context; + + using Microsoft.Extensions.DependencyInjection; + + public class FrameworkDependencyResolver : ISubDependencyResolver, IAcceptServiceProvider + { + private IServiceProvider serviceProvider; + private readonly IServiceCollection serviceCollection; + + public FrameworkDependencyResolver(IServiceCollection serviceCollection) + { + this.serviceCollection = serviceCollection; + } + + public void AcceptServiceProvider(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public bool CanResolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) + { + return HasMatchingType(dependency.TargetType); + } + + public object Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) + { + ThrowIfServiceProviderIsNull(); + return serviceProvider.GetService(dependency.TargetType); + } + + public bool HasMatchingType(Type dependencyType) + { + return serviceCollection.Any(x => x.ServiceType.MatchesType(dependencyType)); + } + + private void ThrowIfServiceProviderIsNull() + { + if (serviceProvider == null) + { + throw new InvalidOperationException($"The serviceProvider for this resolver is null. Please call AcceptServiceProvider first."); + } + } + } + + internal static class GenericTypeExtensions + { + public static bool MatchesType(this Type type, Type otherType) + { + var genericType = type.IsGenericType ? type.GetGenericTypeDefinition() : type; + var genericOtherType = otherType.IsGenericType ? otherType.GetGenericTypeDefinition() : otherType; + return genericType == genericOtherType || genericOtherType == genericType; + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/Resolvers/IAcceptServiceProvider.cs b/src/Castle.Facilities.AspNetCore/Resolvers/IAcceptServiceProvider.cs new file mode 100644 index 0000000000..e1d40fa19b --- /dev/null +++ b/src/Castle.Facilities.AspNetCore/Resolvers/IAcceptServiceProvider.cs @@ -0,0 +1,23 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore.Resolvers +{ + using System; + + public interface IAcceptServiceProvider + { + void AcceptServiceProvider(IServiceProvider serviceProvider); + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/Resolvers/LoggerDependencyResolver.cs b/src/Castle.Facilities.AspNetCore/Resolvers/LoggerDependencyResolver.cs index 241b74bce0..18d1e8461e 100644 --- a/src/Castle.Facilities.AspNetCore/Resolvers/LoggerDependencyResolver.cs +++ b/src/Castle.Facilities.AspNetCore/Resolvers/LoggerDependencyResolver.cs @@ -14,6 +14,8 @@ namespace Castle.Facilities.AspNetCore.Resolvers { + using System; + using Castle.Core; using Castle.MicroKernel; using Castle.MicroKernel.Context; @@ -21,14 +23,13 @@ namespace Castle.Facilities.AspNetCore.Resolvers using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; - public class LoggerDependencyResolver : ISubDependencyResolver + public class LoggerDependencyResolver : ISubDependencyResolver, IAcceptServiceProvider { - private readonly ILoggerFactory loggingFactory; + private IServiceProvider serviceProvider; - public LoggerDependencyResolver(IServiceCollection serviceCollection) + public void AcceptServiceProvider(IServiceProvider serviceProvider) { - var serviceProvider = serviceCollection.BuildServiceProvider(); - this.loggingFactory = serviceProvider.GetService(); + this.serviceProvider = serviceProvider; } public bool CanResolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) @@ -38,7 +39,16 @@ public bool CanResolve(CreationContext context, ISubDependencyResolver contextHa public object Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) { - return loggingFactory.CreateLogger(model.Name); + ThrowIfServiceProviderIsNull(); + return serviceProvider.GetService().CreateLogger(model.Name); + } + + private void ThrowIfServiceProviderIsNull() + { + if (serviceProvider == null) + { + throw new InvalidOperationException($"The serviceProvider for this resolver is null. Please call AcceptServiceProvider first."); + } } } } \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/WindsorContainerExtensions.cs b/src/Castle.Facilities.AspNetCore/WindsorContainerExtensions.cs new file mode 100644 index 0000000000..5cdf812377 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore/WindsorContainerExtensions.cs @@ -0,0 +1,35 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore +{ + using System.Linq; + + using Castle.MicroKernel; + using Castle.Windsor; + + public static class WindsorContainerExtensions + { + /// + /// For grabbing a hold of the during middleware registration from the Configure(IApplicationBuilder, IHostingEnvironment, ILoggerFactory) method in Startup. + /// + /// The implementation + /// A reference to + /// An implementation of + public static T GetFacility(this IWindsorContainer container) where T : IFacility + { + return (T) container.Kernel.GetFacilities().FirstOrDefault(x => x.GetType() == typeof(T)); + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/WindsorRegistrationExtensions.cs b/src/Castle.Facilities.AspNetCore/WindsorRegistrationExtensions.cs new file mode 100644 index 0000000000..f097a9bb13 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore/WindsorRegistrationExtensions.cs @@ -0,0 +1,159 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore +{ + using System; + using System.Linq; + + using Castle.Facilities.AspNetCore.Resolvers; + using Castle.MicroKernel.Lifestyle; + using Castle.MicroKernel.Registration; + using Castle.MicroKernel.Resolvers.SpecializedResolvers; + using Castle.Windsor; + + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Razor.TagHelpers; + using Microsoft.Extensions.DependencyInjection; + + public static class WindsorRegistrationExtensions + { + /// + /// Sets up framework level activators for Controllers, TagHelpers and ViewComponents and adds additional sub dependency resolvers + /// + /// + /// + /// Configuration options for controlling registration and lifestyles of controllers, tagHelpers and viewComponents + /// Optional factory for creating a custom + public static IServiceProvider AddWindsor(this IServiceCollection services, IWindsorContainer container, Action configure = null, Func serviceProviderFactory = null) + { + var options = new WindsorRegistrationOptions(); + configure?.Invoke(options); + + InstallWindsorIntegration(services, container); + AddApplicationComponentsToWindsor(container, options); + InstallFrameworkIntegration(services, container); + return InitialiseFrameworkServiceProvider(services, serviceProviderFactory, container); + } + + /// + /// For making types available to the using 'late bound' factories which resolve from Windsor. This makes things like the @Inject directive in Razor work. + /// + /// The component registration that gets copied across to the + public static ComponentRegistration CrossWired(this ComponentRegistration registration) + { + registration.Attribute(AspNetCoreFacility.IsCrossWiredIntoServiceCollectionKey).Eq(Boolean.TrueString); + return registration; + } + + /// + /// For making types available to the using 'late bound' factories which resolve from Windsor. This makes things like the @Inject directive in Razor work. + /// + /// The component registration that gets copied across to the IServiceCollection + public static ComponentRegistration CrossWired(this ComponentRegistration registration) where T : class + { + registration.Attribute(AspNetCoreFacility.IsCrossWiredIntoServiceCollectionKey).Eq(Boolean.TrueString); + return registration; + } + + /// + /// For registering middleware that is resolved from Windsor + /// + /// + /// + public static ComponentRegistration AsMiddleware(this ComponentRegistration registration) + { + registration.Attribute(AspNetCoreFacility.IsRegisteredAsMiddlewareIntoApplicationBuilderKey).Eq(Boolean.TrueString); + return registration; + } + + /// + /// For registering middleware that is resolved from Windsor + /// + /// A generic type that implements + /// + /// + public static ComponentRegistration AsMiddleware(this ComponentRegistration registration) where T : class, IMiddleware + { + registration.Attribute(AspNetCoreFacility.IsRegisteredAsMiddlewareIntoApplicationBuilderKey).Eq(Boolean.TrueString); + return registration; + } + + private static void AddApplicationComponentsToWindsor(IWindsorContainer container, WindsorRegistrationOptions options) + { + if (!options.ControllerRegistrations.Any()) + { + container.Register(Classes.FromAssemblyInThisApplication(options.EntryAssembly).BasedOn().LifestyleScoped()); + } + + foreach (var controllerRegistration in options.ControllerRegistrations) + { + container.Register(Classes.FromAssemblyInThisApplication(controllerRegistration.Item1).BasedOn().Configure(x => x.LifeStyle.Is(controllerRegistration.Item2))); + } + + if (!options.TagHelperRegistrations.Any()) + { + container.Register(Classes.FromAssemblyInThisApplication(options.EntryAssembly).BasedOn().LifestyleScoped()); + } + + foreach (var controllerRegistration in options.TagHelperRegistrations) + { + container.Register(Classes.FromAssemblyInThisApplication(controllerRegistration.Item1).BasedOn().Configure(x => x.LifeStyle.Is(controllerRegistration.Item2))); + } + + if (!options.ViewComponentRegistrations.Any()) + { + container.Register(Classes.FromAssemblyInThisApplication(options.EntryAssembly).BasedOn().LifestyleScoped()); + } + + foreach (var controllerRegistration in options.ViewComponentRegistrations) + { + container.Register(Classes.FromAssemblyInThisApplication(controllerRegistration.Item1).BasedOn().Configure(x => x.LifeStyle.Is(controllerRegistration.Item2))); + } + } + + private static IServiceProvider InitialiseFrameworkServiceProvider(IServiceCollection services, Func serviceProviderFactory, IWindsorContainer container) + { + var serviceProvider = serviceProviderFactory?.Invoke() ?? services.BuildServiceProvider(validateScopes: false); + container.Register(Component.For().Instance(serviceProvider)); + foreach (var acceptServiceProvider in container.ResolveAll()) + { + acceptServiceProvider.AcceptServiceProvider(serviceProvider); + } + return serviceProvider; + } + + private static void InstallFrameworkIntegration(IServiceCollection services, IWindsorContainer container) + { + services.AddRequestScopingMiddleware(() => new []{ container.RequireScope(), container.Resolve().CreateScope() }); + services.AddCustomTagHelperActivation(container.Resolve); + services.AddCustomControllerActivation(container.Resolve, container.Release); + services.AddCustomViewComponentActivation(container.Resolve, container.Release); + } + + private static void InstallWindsorIntegration(IServiceCollection services, IWindsorContainer container) + { + container.Kernel.Resolver.AddSubResolver(new ArrayResolver(container.Kernel)); + + var loggerDependencyResolver = new LoggerDependencyResolver(); + container.Register(Component.For().Instance(loggerDependencyResolver)); + container.Kernel.Resolver.AddSubResolver(loggerDependencyResolver); + + var frameworkDependencyResolver = new FrameworkDependencyResolver(services); + container.Register(Component.For().Instance(frameworkDependencyResolver)); + container.Kernel.Resolver.AddSubResolver(frameworkDependencyResolver); + } + } +} \ No newline at end of file diff --git a/src/Castle.Facilities.AspNetCore/WindsorRegistrationOptions.cs b/src/Castle.Facilities.AspNetCore/WindsorRegistrationOptions.cs new file mode 100644 index 0000000000..7ba58d6630 --- /dev/null +++ b/src/Castle.Facilities.AspNetCore/WindsorRegistrationOptions.cs @@ -0,0 +1,100 @@ +// Copyright 2004-2018 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.Facilities.AspNetCore +{ + using System.Collections.Generic; + using System.Reflection; + + using Castle.Core; + + /// + /// For overriding default registration and lifestyles behaviour + /// + public class WindsorRegistrationOptions + { + private Assembly entryAssembly = null; + + internal Assembly EntryAssembly + { + get + { + try + { + entryAssembly = entryAssembly ?? Assembly.GetEntryAssembly(); + } + catch + { + entryAssembly = entryAssembly ?? Assembly.GetCallingAssembly(); + } + + return entryAssembly; + } + } + + internal List<(Assembly, LifestyleType)> ControllerRegistrations = new List<(Assembly, LifestyleType)>(); + internal List<(Assembly, LifestyleType)> TagHelperRegistrations = new List<(Assembly, LifestyleType)>(); + internal List<(Assembly, LifestyleType)> ViewComponentRegistrations = new List<(Assembly, LifestyleType)>(); + + /// + /// Use this method to specify where controllers, tagHelpers and viewComponents are registered from. Use this method + /// if the facility starts throwing ComponentNotFoundExceptions because of problems with /. + /// You can optionally use // if you need more fine grained + /// control for sourcing these framework components. + /// + /// + /// + public WindsorRegistrationOptions UseEntryAssembly(Assembly entryAssembly) + { + this.entryAssembly = entryAssembly; + return this; + } + + /// + /// Use this method to customise the registration/lifestyle of controllers. + /// + /// Assembly where the controllers are defined. Defaults to . + /// The lifestyle of the controllers. Defaults to . + /// as a fluent interface + public WindsorRegistrationOptions RegisterControllers(Assembly controllersAssembly = null, LifestyleType lifestyleType = LifestyleType.Scoped) + { + ControllerRegistrations.Add((controllersAssembly ?? EntryAssembly, lifestyleType)); + return this; + } + + /// + /// Use this method to customise the registration/lifestyle of tagHelpers. + /// + /// Assembly where the tag helpers are defined. Defaults to Assembly.GetCallingAssembly(). + /// The lifestyle of the controllers. Defaults to . + /// as a fluent interface + public WindsorRegistrationOptions RegisterTagHelpers(Assembly tagHelpersAssembly = null, LifestyleType lifestyleType = LifestyleType.Scoped) + { + TagHelperRegistrations.Add((tagHelpersAssembly ?? EntryAssembly, lifestyleType)); + return this; + } + + /// + /// Use this method to customise the registration/lifestyle of view components. + /// + /// Assembly where the view components are defined. Defaults to Assembly.GetCallingAssembly(). + /// The lifestyle of the controllers. Defaults to . + /// as a fluent interface + public WindsorRegistrationOptions RegisterViewComponents(Assembly viewComponentsAssembly = null, LifestyleType lifestyleType = LifestyleType.Scoped) + { + ViewComponentRegistrations.Add((viewComponentsAssembly ?? EntryAssembly, lifestyleType)); + return this; + } + } +} \ No newline at end of file diff --git a/src/Castle.Windsor/Core/LifecycleConcernsCollection.cs b/src/Castle.Windsor/Core/LifecycleConcernsCollection.cs index 9808960d4c..3c02e0c6ed 100644 --- a/src/Castle.Windsor/Core/LifecycleConcernsCollection.cs +++ b/src/Castle.Windsor/Core/LifecycleConcernsCollection.cs @@ -127,7 +127,6 @@ public void Add(IDecommissionConcern concern) { throw new ArgumentNullException("concern"); } - Decommission.Add(concern); } @@ -146,8 +145,25 @@ public void AddFirst(IDecommissionConcern concern) { throw new ArgumentNullException("concern"); } - Decommission.Insert(0, concern); } + + public void Remove(ICommissionConcern concern) + { + if (concern == null) + { + throw new ArgumentNullException("concern"); + } + Commission.Remove(concern); + } + + public void Remove(IDecommissionConcern concern) + { + if (concern == null) + { + throw new ArgumentNullException("concern"); + } + Decommission.Remove(concern); + } } } \ No newline at end of file