westermo / FactoryGenerator

FactoryGenerator is an IoC container that uses Roslyn to prepare a container for consumption at compile-time.
MIT License
2 stars 1 forks source link

Error when creating DependencyInjectionContainer #18

Closed ignatandrei closed 16 hours ago

ignatandrei commented 1 week ago

I am trying to evaluate your project to add to my list of RSCG https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples

However, I am getting an error message 1>D:\gth\RSCG_Examples\v2\rscg_examples\FactoryGenerator\src\InjectDemo\Program.cs(3,56,3,61): error CS7036: There is no argument given that corresponds to the required parameter 'con' of 'DependencyInjectionContainer.DependencyInjectionContainer(DatabaseCon)'

Attached the project for your convenience InjectDemo.zip

jorgensigvardsson commented 1 week ago

Any unsatisfied dependency "bubbles up" as a constructor parameter of the container. It basically means that if FactoryGenerator cannot find any implementation of a referenced interface, it requires you to supply an instance outside of the lifetime scope of the container.

Make sure you have an [Inject] anywhere on the implementation of the interface you are trying to use in a dependency.

In a regular non generated container (Autofac, Ninject, ASP.NET's built in, etc.), this compilation error would be a runtime exception.

jorgensigvardsson commented 1 week ago

I think what you have going on is this:

[Inject]
class Foo : IFoo {
   public Foo(IBar bar) { ... }
}

// Missing [Inject]
class Bar : IBar { ... }

Since FactoryGenerator cannot deduce how IBar is to be instantiated, it relegates that to the owner of the container.

ignatandrei commented 1 week ago

Did you try the project attached ? I think I have put

[Inject]

everywhere it is needed

Iacentis commented 3 days ago

Hello!

From the looks of it you have two classes, Database & DatabaseCon that both implement IDatabase. You then [Inject] both of these which will register then into the container as IDatabase. The Database class then takes on a dependency on DatabaseCon (the specific implementation, not the interface) through it's constructor.

If you want DatabaseCon to be explicitly available as resolvable from the container, and not just to be resolvable as IDatabase, consider adding the [Self] attribute to DatabaseCon.

ignatandrei commented 2 days ago

both classes have

[Inject,Scoped]
public partial class DatabaseCon: IDatabase{
}
[Inject, Scoped]
public partial class Database : IDatabase{
}

The problem is that , when compiling , this is giving an error on program.cs

InjectDemo.Generated.DependencyInjectionContainer sc = new();

That's because this is generated :

public partial class DependencyInjectionContainer
{
    InjectDemo.DatabaseCon con;
    public DependencyInjectionContainer(InjectDemo.DatabaseCon con)
    {

( When you download the previous example, please add partial declaration to class Database )

Iacentis commented 2 days ago

That is correct, you have injected the DatabaseCon & Database classes as IDatabase into the container.

Then, when generating your container, the source generator notices that Database has a constructor that takes a DatabaseCon instance, not an IDatabase instance. As such, since the only thing that has been provided to the container are two instances of IDatabase, as per the behavior of the [Inject] attribute in readme.md, the container has nothing to provide that constructor, and as such, bubbles it up to the generated constructor of the container itself.

In order to work around this, you could either:

A: Add the [Self] Attribute to DatabaseCon as per, a suggestion would be to add the [ExceptAs<IDatabase>] attribute as well, since your intent seems to be to Resolve the Database class as the sole implementer of IDatabase, and in that scenario you would not want to expose both as IDatabase to the container

[Inject, Scoped, Self, ExceptAs<IDatabase>]
public partial class DatabaseCon: IDatabase{
}

This will specify to the container that it is to provide this specific implementation of the class interfaces as something directly resolvable from the container. As a DatabaseCon is available to the container now, it will be provided to the constructor of Database and will no longer be generated as part of the container constructor.

B: Construct Database using an IDatabase instance, rather than the specific implementation DatabaseCon. For clarity´s sake as well, you would want to make sure that DatabaseCon is not implementing the exact same interface as Database. Since it has a property "Connection" that we are interested in, let´s extract that into another interface

public interface IConnection : IDatabase
{
    string? Connection { get; set; }
}

And switch DatabaseCon over to implementing that interface instead, as per

[Inject,Scoped]
public partial class DatabaseCon: IConnection
{
    public string? Connection { get; set; }
    public void Open()
    {
        Console.WriteLine("open" + Connection);
    }
}

no actual code modification beyond changing the interface is nessecary.

Then, we edit the Databaseclass to take an IConnection instead of a specific DatabaseCon implementation as follows:

[Inject, Scoped]
public class Database : IDatabase
{
    private readonly IConnection con;

    public Database(IConnection con)
    {
        this.con = con;
    }
    /* Rest of the code */
}

Then the container will no longer need a constructor parameter, since IConnection is exposed to the container via DatabaseCon [Inject] attribute.

ignatandrei commented 16 hours ago

You are right! I have put [Self] and all it is ok now Thanks!

https://ignatandrei.github.io/RSCG_Examples/v2/docs/FactoryGenerator