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

Scoped Dependencies #3

Closed BrandtWright closed 7 years ago

BrandtWright commented 7 years ago

When registering components as scoped (which is equivalent to perwebrequest) with aspnet core framework via the configureservices(...) method in the startup class things seem to work fine until I request a scoped instance from classes registered with the windsor container.

If I register IFoo with aspnet core via configureservices(...) and then request an instance of it from multiple types that were registered with the windsor container I get a new instance of IFoo in each class that requests an instance of IFoo even though this all happens as part of the same web request.

This is a trivial example but consider trying to implement a unit or work pattern for DB integration and not being able to inject the same instance of the unit of work in multiple types registered via windsor for a single web request.

Am I doing something wrong? Has anyone had any luck with this?

hikalkan commented 7 years ago

Actually, this library passes all tests in Microsoft.Extensions.DependencyInjection.Specification.Tests. So, I was trusting these tests and not used scoped before (I mostly use singleton and transients). Thank you for reporting it. I'll check this in a short time.

hikalkan commented 7 years ago

I could not repeat the problem.

  1. I created a unit test: https://github.com/volosoft/castle-windsor-ms-adapter/commit/c81f543e851cf73b5716db0e2fedcf2d6ddc246b and it's passing.

  2. I created a controller and some services to test in real AspNet Core application: https://github.com/aspnetboilerplate/aspnetboilerplate/commit/5cb3c4bc9e6faff8bba212d5cb29ee02594640c5

A test result:

_myTransientClass1.ScopedClass.Id = 859c35b9674b429985ff56f7e80da00f; _myTransientClass2.ScopedClass.Id = 859c35b9674b429985ff56f7e80da00f

As you see, they are same, so a scoped class is instantiated once per request.

Can you create an empty application to demonstrate it and share with me if possible?

BrandtWright commented 7 years ago

Sure. I'll put that together now and share it when done.

BrandtWright commented 7 years ago

ScopeExample.zip

I have attached a solution which demonstrates the problem. Injection into controller constructors and even nested dependencies works fine. However, anything that uses a call to container.resolve() returns a transient instance. So, any creational types (factories, etc.) or use of AoP to decorate command query infrastructure that depends on IInterceptorSelectors or the like and pulls directly from the container fails to produce MsScoped instances and falls back to transient instantiation.

Do you have any advise/suggestions/workarounds to deal with this behavior?

hikalkan commented 7 years ago

Hi,

You should not directly resolve from IWindsorContainer. You should resolve from IServiceProvider. So, change your code like that:


    using Microsoft.Extensions.DependencyInjection;

    ...

    public class NaivePerWebRequestServiceFactory
    {
        private readonly IServiceProvider _serviceProvider;

        public NaivePerWebRequestServiceFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public IPerWebRequestService GetService()
        {
            return _serviceProvider.GetService<IPerWebRequestService>();  // returns transient instance
                                                                 // despite the fact thatIperWebRequest
                                                                 // was registerd as MsScoped...
        }
    }
hikalkan commented 7 years ago

Because IWindsorContainer does not know about current request, but IServiceProvider does know http request context, since a dedicated service provider is created per web request (by ASP.NET Core).

maxlego commented 6 years ago

So... do i understand correctly that when i register scoped component to IServiceCollection like this: services.AddScoped<IOperationScoped, Operation>();

Then i cant use property resolver through castle windsor?

public class OperationsController : Controller
{
        public IOperationScoped ScopedOperation { get; set; }
}

Instead i have to resolve IOperationScoped through IServiceCollection? Thats ugly

hikalkan commented 6 years ago

You can use property injection if you use AddControllersAsServices() (like .AddMvc().AddControllersAsServices()).

maxlego commented 6 years ago

Yep.. that did the trick!

Thanks!