Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add readme "How To Implement Dependency Injection with Pure.DI code generator" #541

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

NikolayPianikov
Copy link

No description provided.

@thevortexcloud
Copy link
Contributor

Is there a reason this can't be added to the main DI article? There is a lot of overlap between them. Although other people may disagree about merging it.

Copy link
Contributor

@thevortexcloud thevortexcloud left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have put some of my thoughts in. But it's also not really up to me either. I am not in charge around here.

string Greetings { get; }
}

public class MainViewModel(IBusinessService businessService)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Primary constructors are a fairly new feature. They should probably be avoided for now to ensure anybody copying and pasting the code can use it without worrying about their language version.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

void RegisterSomething();
}

public class Repository: IRepository
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the formatting on this line

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

This will be useful if the main window will require dependencies to be injected.

Advantages over classical DI container libraries:
- No explicit initialisation of data contexts is required. Data contexts are configured directly in `.axaml` files according to the MVVM approach.
Copy link
Contributor

@thevortexcloud thevortexcloud Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not clear how what you have done is any different from the MS DI example. You are still explicitly resolving things from the container, just with more steps. This also sounds like you are implying the MS DI approach violates MVVM somehow?

There is also really nothing stopping you from setting up MS DI in a similar way. It just requires slightly more effort (and also a static locator).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pure.DI doesn't create a container like in classic DI container frameworks. Pure.DI is a source code generator. For the example in this document, it generates a class like this:

AvaloniaSimpleApp namespace;

partial class Composition
{
    private readonly Lock _lock = new();

    private MainViewModel? _mainViewModel;
    private Repository? _repository;

    public IMainViewModel MainViewModel
    {
        get
        {
            if (_mainViewModel is null)
            {
                using (_lock.EnterScope())
                {
                    if (_mainViewModel is null)
                    {
                        if (_repository is null)
                        {
                            _repository = new Repository();
                        }

                        _mainViewModel = new MainViewModel(new BusinessService(_repository));
                    }
                }
            }

            return _mainViewModel;
        }
    }

    public MainWindow MainWindow => new()
}

This class is roughly what you would write if you were doing object composition in a pure DI paradigm by hand. There aren't any dependencies on third party libraries, you don't need methods like GetService(Type type type). You have a class property for each root of the composition.

How many more steps are you talking about? All you need to do is to define one instance of the class in the resources to create the roots of the composition, and that's it. Except for the settings themselves for defining the dependency graph, there is no more code, everything is customized by bindings inside xaml files. Just count the number of lines of code in the examples.

There is also really nothing stopping you from setting up MS DI in a similar way. It just requires slightly more effort (and also a static locator).

I suggest you try doing this for MS DI, I'm not sure it will look as easy, at least not because any classic DI library has a Service Locator at the very beginning. I'm not claiming that the MS DI approach somehow violates MVVM, I'm just pointing out that the presentation layer in the Pure.DI approach is even more clearly separated from the other layers and is consistent with MVVM. The presentation layer has no .cs files and no tricks to initialize data contexts.


- You can set a shared resource as a data context

`DataContext="{StaticResource Composition}"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may(?) cause side effects when running inside the previewer, as the previewer will run your real app code. You really should have a separate set of test services that sets the design time data context

Copy link
Author

@NikolayPianikov NikolayPianikov Dec 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are absolutely right, but this is absolutely true for other approaches as well. I didn't take the time to support DesignTime, for the simplicity of the example. But it is easily solved by an alternative class like DesignTimeComposition or by explicitly setting d.DataContext="..." for a view.


Advantages over classical DI container libraries:
- No explicit initialisation of data contexts is required. Data contexts are configured directly in `.axaml` files according to the MVVM approach.
- The code is simpler, more compact, and requires less maintenance effort.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fairly opinionated, which I would argue does not belong in a tutorial like this. You should present facts and let people make their own decision

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree, fixed

`Title="{Binding MainViewModel.Title}"`

Advantages over classical DI container libraries:
- The code-behind `.cs` files for views are free of any logic.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are many ways to do DI without using a static locator or putting any DI logic on code behind that work in basically every DI framework. The original example code for MSDI mentioned one possible way, it was just removed due to the lack of practical examples in the guide.

https://github.com/cerobe/avalonia-docs/blob/9ea95b7540c6eaf9a59ef88776d543ecc707244a/docs/guides/implementation-guides/how-to-implement-dependency-injection.md?plain=1#L11

Copy link
Author

@NikolayPianikov NikolayPianikov Dec 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have much experience using Avalonia, but have had experience with WPF. The main way to work with classic DI containers is to use methods like GetService<T>(). With all their faults. It's basically a ServiceLocator. At runtime you can't be sure when something goes wrong and a method like GetService<T>() throws an exception because you forgot to register some implementation. If you forget something in Pure.DI, you won't get any runtime exceptions, your application just won't compile.

I'm not sure I know of a way to use them in bindings without any tricks. In the link you suggested, as far as I understand we just provide access to the container via a static property. This approach does not reduce the dependency of the view layer compared to the current MS DI approach.


Advantages over classical DI container libraries:
- No performance impact or side effects when creating composition of objects.
- All logic for analyzing the graph of objects, constructors and methods takes place at compile time. Pure.DI notifies the developer at compile time of missing or cyclic dependencies, cases when some dependencies are not suitable for injection, etc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph is just an elaborated version of the previous one

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advantages over classical DI container libraries:

  • No performance impact or side effects when creating composition of objects.
  • All logic for analyzing the graph of objects, constructors and methods takes place at compile time. Pure.DI notifies the developer at compile time of missing or cyclic dependencies, cases when some dependencies are not suitable for injection, etc.
  • Does not add dependencies to any additional assembly.
  • Since the generated code uses primitive language constructs to create object compositions and does not use any libraries, you can easily debug the object composition code as regular code in your application.

This is a paragraph about the general benefits.

Advantages over classical DI container libraries:

  • No explicit initialisation of data contexts is required. Data contexts are configured directly in .axaml files according to the MVVM approach.
  • The code looks simple, compact and doesn't require much maintenance effort.
  • The main window is created in a pure DI paradigm, and it can be easily supplied with all necessary dependencies via DI as regular types.

This paragraph describes the advantages of using Pure.DI in Avalonia applications.

I have not combined them as they refer to different parts of the document.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants