From 39dfa585e8ca8f8fe7ad5b09395f376c89bb3ca2 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Tue, 17 Sep 2024 14:05:06 +0300 Subject: [PATCH 1/4] Add readme "How To Implement Dependency Injection with Pure.DI code generator" --- ...ement-dependency-injection-with-pure-di.md | 207 ++++++++++++++++++ docs/guides/implementation-guides/index.md | 5 + 2 files changed, 212 insertions(+) create mode 100644 docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md diff --git a/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md b/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md new file mode 100644 index 000000000..5fec33347 --- /dev/null +++ b/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md @@ -0,0 +1,207 @@ +--- +id: how-to-implement-dependency-injection-with-pure-di +title: How To Implement Dependency Injection with Pure.DI code generator +--- + +# How To Implement Dependency Injection in Avalonia with Pure.DI code generator + +This example is based on [another example](how-to-implement-dependency-injection.md) on dependency injection showing the benefits of the pure Dependency Injection approach using the Pure.DI source code generator. + +## Step 0: Presentation model + +Compared to [this example](how-to-implement-dependency-injection.md), only a few properties and methods have been added to demonstrate real-world usage scenarios: + +```csharp +public interface IMainViewModel +{ + string Title { get; } + + string Greetings { get; } +} + +public class MainViewModel(IBusinessService businessService) + : IMainViewModel +{ + public string Title => "Avalonia application"; + + public string Greetings => businessService.CreateGreetings(); +} + +public interface IBusinessService +{ + string CreateGreetings(); +} + +public class BusinessService(IRepository repository) + : IBusinessService +{ + public string CreateGreetings() + { + repository.RegisterSomething(); + return "Example of Dependency Injection implementation using Pure.DI"; + } +} + +public interface IRepository +{ + void RegisterSomething(); +} + +public class Repository: IRepository +{ + public void RegisterSomething() + { + } +} +``` + +## Step 1: Install the NuGet package for DI + +Run the following command in a terminal inside your project directory to install the [Pure.DI](https://www.nuget.org/packages/Pure.DI) source generator package. + +```shell +dotnet add package Pure.DI +``` + +## Step 2: Add a composition class + +This class setups how the composition of objects will be created for the application. + +```csharp +using Pure.DI; +using static Pure.DI.Lifetime; + +public partial class Composition +{ + private static void Setup() => DI.Setup() + .Root(nameof(MainWindow)) + .Root(nameof(MainViewModel)) + + .Bind().As(Singleton).To() + .Bind().To() + .Bind().As(Singleton).To(); +} +``` + +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. + +## Step 3: Modify App.axaml.cs + +Next, the `App.xaml.cs` class should be modified to use the main window created in pure DI paradigm: + +```csharp +public class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop + && Resources[nameof(Composition)] is Composition composition) + { + desktop.MainWindow = composition.MainWindow; + } + + base.OnFrameworkInitializationCompleted(); + } +} +``` + +This code is only needed to create a main window in a pure DI paradigm using a shared composition instance: + +```csharp +if (Resources[nameof(Composition)] is Composition composition) +{ + desktop.MainWindow = composition.MainWindow; +} +``` + +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. +- The code is simpler, more compact, and requires less 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. + +## Step 4: Modify App.axaml + +Next, modify the `App.axaml` file to use view models according to the MVVM approach via a shared resource: + +```xml + + + + + + + + + + + + +``` + +This markup fragment + +```xml + + + +``` + +creates a shared resource of type `Composition` and with key _‘Composition’_, which will be further used as a data context in the views. + +## Step 5: Create your views according to MVVM approach + +You can now use bindings to model views without even editing the views `.cs` code files, for example for the main window in the example this might look like this: + +```xml + +``` + +To use bindings in views: + +- You can set a shared resource as a data context + + `DataContext="{StaticResource Composition}"` + + The resource with key _"Composition"_ was defined in [step 4](#step-4-modify-appaxaml). + +- Specify the data type in the context: + + `xmlns:app="clr-namespace:AvaloniaSimpleApp"` + + `x:DataType="app:Composition"` + +- Use the bindings as usual: + + `Title="{Binding MainViewModel.Title}"` + +Advantages over classical DI container libraries: +- The code-behind `.cs` files for views are free of any logic. +- This approach works just as well during design time. +- You can easily use different view models in a single view. +- Bindings depend on properties through abstractions, which additionally ensures weak coupling of types in application. This is in line with the basic principles of DI. diff --git a/docs/guides/implementation-guides/index.md b/docs/guides/implementation-guides/index.md index 7188b713b..7ce2eaf4e 100644 --- a/docs/guides/implementation-guides/index.md +++ b/docs/guides/implementation-guides/index.md @@ -21,6 +21,11 @@ These guides show you how to use various implementation architectures and techni to="/docs/guides/implementation-guides/how-to-implement-dependency-injection" description="A guide to understanding how to use Dependency Injection (DI) in your apps." /> + Date: Tue, 17 Sep 2024 16:23:55 +0300 Subject: [PATCH 2/4] Add readme "How To Implement Dependency Injection with Pure.DI code generator" - adds sidebar menu item --- sidebars.js | 1 + 1 file changed, 1 insertion(+) diff --git a/sidebars.js b/sidebars.js index 48c06990a..f19ad150e 100644 --- a/sidebars.js +++ b/sidebars.js @@ -252,6 +252,7 @@ const sidebars = { 'items': [ 'guides/implementation-guides/how-to-use-the-mvvm-pattern', 'guides/implementation-guides/how-to-implement-dependency-injection', + 'guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di', 'guides/implementation-guides/developer-tools', 'guides/implementation-guides/logging-errors-and-warnings', 'guides/implementation-guides/ide-support', From c14885000b40816c06d885bb0c07caf538d605c1 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Tue, 17 Sep 2024 18:58:24 +0300 Subject: [PATCH 3/4] Add readme "How To Implement Dependency Injection with Pure.DI code generator" - minor --- .../how-to-implement-dependency-injection-with-pure-di.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md b/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md index 5fec33347..0e03dd86f 100644 --- a/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md +++ b/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md @@ -137,7 +137,7 @@ Next, modify the `App.axaml` file to use view models according to the MVVM appro ```xml @@ -148,7 +148,7 @@ Next, modify the `App.axaml` file to use view models according to the MVVM appro - + @@ -158,7 +158,7 @@ This markup fragment ```xml - + ``` From bf3b4531ca2029371e8aa427e95400d1180a6c15 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sat, 21 Dec 2024 19:27:13 +0300 Subject: [PATCH 4/4] Add readme "How To Implement Dependency Injection with Pure.DI code generator" - feedback fixes --- ...ement-dependency-injection-with-pure-di.md | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md b/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md index 0e03dd86f..6914fea75 100644 --- a/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md +++ b/docs/guides/implementation-guides/how-to-implement-dependency-injection-with-pure-di.md @@ -19,12 +19,18 @@ public interface IMainViewModel string Greetings { get; } } -public class MainViewModel(IBusinessService businessService) - : IMainViewModel +public class MainViewModel : IMainViewModel { + private readonly IBusinessService _businessService; + + public MainViewModel(IBusinessService businessService) + { + _businessService = businessService; + } + public string Title => "Avalonia application"; - public string Greetings => businessService.CreateGreetings(); + public string Greetings => _businessService.CreateGreetings(); } public interface IBusinessService @@ -32,12 +38,18 @@ public interface IBusinessService string CreateGreetings(); } -public class BusinessService(IRepository repository) - : IBusinessService +public class BusinessService : IBusinessService { + private readonly IRepository _repository; + + public BusinessService(IRepository repository) + { + _repository = repository; + } + public string CreateGreetings() { - repository.RegisterSomething(); + _repository.RegisterSomething(); return "Example of Dependency Injection implementation using Pure.DI"; } } @@ -47,7 +59,7 @@ public interface IRepository void RegisterSomething(); } -public class Repository: IRepository +public class Repository : IRepository { public void RegisterSomething() { @@ -127,7 +139,7 @@ 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. -- The code is simpler, more compact, and requires less maintenance effort. +- 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. ## Step 4: Modify App.axaml