z4kn4fein / stashbox

A lightweight, fast, and portable dependency injection framework for .NET-based solutions.
https://z4kn4fein.github.io/stashbox
MIT License
140 stars 10 forks source link

Decorator and ResolveAll<> interaction #141

Closed eValker closed 1 year ago

eValker commented 1 year ago

Hello,

While working on a project I found weird resolver's behaviour. I ma not sure whether I am doing something wrong or if it is just a bug in the library :)

Here is a code sample to reproduce the issue:

using Stashbox;
using Stashbox.Configuration;

var container = new StashboxContainer(c =>
{
    c.WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications);
});

container.Register<AnimalRepository>(c => c.WithSingletonLifetime().AsImplementedTypes());
container.Register<CarRepository>(c => c.WithSingletonLifetime().AsImplementedTypes());
container.Register<PhoneRepository>(c => c.WithSingletonLifetime().AsImplementedTypes());
container.Register<HouseRepository>(c => c.WithSingletonLifetime().AsImplementedTypes());

// first try
//container.RegisterDecorator<CachedAnimalRepository>();
//container.RegisterDecorator<CachedCarRepository>();
//container.RegisterDecorator<CachedPhoneRepository>();

// second try
container.RegisterDecorator<CachedAnimalRepository>(c => c.AsServiceAlso<IDataRepository>());
container.RegisterDecorator<CachedCarRepository>(c => c.AsServiceAlso<IDataRepository>());
container.RegisterDecorator<CachedPhoneRepository>(c => c.AsServiceAlso<IDataRepository>());

container.Register<CollectionOfServices>();

var x = container.Resolve<CollectionOfServices>();

Console.ReadKey();

internal interface IDataRepository
{
    void CommonMethod()
    {
    }
}

internal interface IAnimalRepository : IDataRepository
{
}

internal interface ICarRepository : IDataRepository
{
}

internal interface IPhoneRepository : IDataRepository
{
}

internal interface IHouseRepository : IDataRepository
{
}

internal sealed class AnimalRepository : IAnimalRepository
{
}

internal sealed class CarRepository : ICarRepository
{
}

internal sealed class PhoneRepository : IPhoneRepository
{
}

internal sealed class HouseRepository : IHouseRepository
{
}

internal sealed class CachedAnimalRepository : IAnimalRepository
{
    public CachedAnimalRepository(IAnimalRepository service)
    {
        // never called
    }
}

internal sealed class CachedCarRepository : ICarRepository
{
    public CachedCarRepository(ICarRepository service)
    {
        // never called
    }
}

internal sealed class CachedPhoneRepository : IPhoneRepository
{
    public CachedPhoneRepository(IPhoneRepository service)
    {
        //called with PhoneRepository instance
    }
}

internal sealed class CollectionOfServices
{
    public CollectionOfServices(IEnumerable<IDataRepository> services)
    {
        // got array with 4 instances of CachedPhoneRepository (same instance)
    }
}

Expected value

Inside CollectionOfServices constructor, I'm expecting to recieve an array of 3 different data repositories (for animals, cars and phones) wrapped in cached implementations and also HouseRepositry, e.g.

Actual value

CollectionOfServices constructor recieves an array of 4 CachedPhoneRepository instances. image

I am not sure how can I change the configuration to achive what I need. I little help would be appreciated :)

eValker commented 1 year ago

Ok, I think I figured this out.

container.RegisterDecorator<IAnimalRepository, CachedAnimalRepository>(c => c.AsServiceAlso<IDataRepository>().When(t => t.Type.IsAssignableTo(typeof(IAnimalRepository))));
container.RegisterDecorator<ICarRepository, CachedCarRepository>(c => c.AsServiceAlso<IDataRepository>().When(t => t.Type.IsAssignableTo(typeof(ICarRepository))));
container.RegisterDecorator<IPhoneRepository, CachedPhoneRepository>(c => c.AsServiceAlso<IDataRepository>().When(t => t.Type.IsAssignableTo(typeof(IPhoneRepository))));

Not sure if this is the best approach, but it is working :)

z4kn4fein commented 1 year ago

Hi @eValker, thank you for reporting this. I see you found a workaround but I recognised this as a bug. In 5.12.0-preview-821 it's now possible to register your decorators like:

container.RegisterDecorator<CachedAnimalRepository>(c => c.AsServiceAlso<IDataRepository>());
container.RegisterDecorator<CachedCarRepository>(c => c.AsServiceAlso<IDataRepository>());
container.RegisterDecorator<CachedPhoneRepository>(c => c.AsServiceAlso<IDataRepository>());

or:

container.RegisterDecorator<CachedAnimalRepository>(c => c.AsImplementedTypes());
container.RegisterDecorator<CachedCarRepository>(c => c.AsImplementedTypes());
container.RegisterDecorator<CachedPhoneRepository>(c => c.AsImplementedTypes());

The container will now treat your resolution request correctly. Could you please re-check your setup that it works now as expected? Thanks