volosoft / castle-windsor-ms-adapter

Castle Windsor ASP.NET Core / Microsoft.Extensions.DependencyInjection Adapter
https://www.nuget.org/packages/Castle.Windsor.MsDependencyInjection
MIT License
85 stars 29 forks source link

Factory services are not supported for MsScopedLifestyleManager #32

Open jirikanda opened 5 years ago

jirikanda commented 5 years ago

Instances from service factories do not respect lifestyle.

Steps to reproduce

  1. Create a service, register it as "per web request" (MsScopedLifestyleManager) (see SomeService below)
  2. Create a service factory, register it as a factory (see ISomeServiceFactory below)
  3. Create a Controller with dependencies - service and also service factory. Create a service from service factory in a method. (see MainController below)
  4. There should be only one instance of SomeService per web request because of the lifestyle. Parameters in constructor shares one instance, but service factory creates new instances (see comments OK and FAIL).
    public class SomeService
    {
    }

    public interface ISomeServiceFactory
    {
        SomeService CreateService();
    }

    public class Startup
    {
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            var container = new WindsorContainer();
            container.AddFacility<TypedFactoryFacility>();
            container.Register(Component.For<ISomeServiceFactory>().AsFactory()); // register service factory
            container.Register(Component.For<SomeService>().LifeStyle.Custom<MsScopedLifestyleManager>()); // register SomeService as "per-web-request"

            return WindsorRegistrationHelper.CreateServiceProvider(container, services);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc();
        }
    }

        [Route("")]
        public class MainController : ControllerBase
        {
        private readonly SomeService someService1;
        private readonly SomeService someService2;
        private readonly ISomeServiceFactory someServiceFactory;

        public MainController(SomeService someService1, SomeService someService2, ISomeServiceFactory someServiceFactory)
        {
            this.someService1 = someService1;
            this.someService2 = someService2;
            this.someServiceFactory = someServiceFactory;
        }

        [HttpGet]
        public void Do()
        {
            var instance1 = someServiceFactory.CreateService();
            var instance2 = someServiceFactory.CreateService();
            Debug.Assert(Object.ReferenceEquals(someService1, someService2)); // OK
            Debug.Assert(Object.ReferenceEquals(instance1, instance2)); // FAIL
            Debug.Assert(Object.ReferenceEquals(someService1, instance1)); // FAIL
        }
        }

    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }

Dependencies

acjh commented 5 years ago

How did you implement ISomeServiceFactory?

jirikanda commented 5 years ago

Thanks for the reply.

I do not implement ISomeServiceFactory myself. I am using Castle Windsor's Typed Factory Facility to let it create the implementation at runtime.

container.AddFacility<TypedFactoryFacility>(); 
container.Register(Component.For<ISomeServiceFactory>().AsFactory());

See https://github.com/castleproject/Windsor/blob/master/docs/typed-factory-facility-interface-based.md

jirikanda commented 5 years ago

I found that #25 partially solves this problem. Using fix in #25, typed factories work well from the second request.

For the very first request, typed factories still resolve new instances.

hikalkan commented 5 years ago

I will check this.