Skip to content

Latest commit

 

History

History
192 lines (147 loc) · 7.8 KB

Avalonia.md

File metadata and controls

192 lines (147 loc) · 7.8 KB

Avalonia application

CSharp

This example demonstrates the creation of a Avalonia application in the pure DI paradigm using the Pure.DI code generator.

Note

Another example with Avalonia shows how to create an application with a single composition root.

The definition of the composition is in Composition.cs. This class setups how the composition of objects will be created for the application. You must not forget to define any necessary composition roots, for example, these can be view models such as ClockViewModel:

using Pure.DI;
using static Pure.DI.Lifetime;

internal partial class Composition
{
    void Setup() => DI.Setup()
        // Provides the composition root for main window
        .Root<MainWindow>(nameof(MainWindow))
        // Provides the composition root for Clock view model
        .Root<IClockViewModel>(nameof(ClockViewModel))
        
        // View Models
        .Bind().As(Singleton).To<ClockViewModel>()

        // Models
        .Bind().To<Log<TT>>()
        .Bind().To(_ => TimeSpan.FromSeconds(1))
        .Bind().As(Singleton).To<Clock.Models.Timer>()
        .Bind().As(PerBlock).To<SystemClock>()
    
        // Infrastructure
        .Bind().To<Dispatcher>();
}

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.

A single instance of the Composition class is defined as a static resource in App.xaml for later use within the xaml markup everywhere:

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="AvaloniaApp.App"
             xmlns:app="using:AvaloniaApp"
             RequestedThemeVariant="Default">

  <!-- "Default" ThemeVariant follows system theme variant.
  "Dark" or "Light" are other available options. -->
  <Application.Styles>
    <FluentTheme />
  </Application.Styles>

  <!--Creates a shared resource of type `Composition` and with key _‘Composition’_,
  which will be further used as a data context in the views.-->
  <Application.Resources>
    <app:Composition x:Key="Composition" />
  </Application.Resources>

</Application>

This markup fragment

<Application.Resources>
    <app:Composition x:Key="Composition" />
</Application.Resources>

creates a shared resource of type Composition and with key ‘Composition’, which will be further used as a data context in the views.

The associated application App.axaml.cs class is looking like:

public class App : Application
{
    public override void Initialize() => AvaloniaXamlLoader.Load(this);

    public override void OnFrameworkInitializationCompleted()
    {
        if (Resources[nameof(Composition)] is Composition composition)
        {
            // Assigns the main window/view
            switch (ApplicationLifetime)
            {
                case IClassicDesktopStyleApplicationLifetime desktop:
                    desktop.MainWindow = composition.MainWindow;
                    break;

                case ISingleViewApplicationLifetime singleViewPlatform:
                    singleViewPlatform.MainView = composition.MainWindow;
                    break;
            }

            // Handles disposables
            if (ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
            {
                controlledApplicationLifetime.Exit += (_, _) => composition.Dispose();
            }
        }

        base.OnFrameworkInitializationCompleted();
    }
}

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.

You can now use bindings to model views without even editing the views .cs code files. All previously defined composition roots are now accessible from markup without any effort, such as ClockViewModel:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="AvaloniaApp.Views.MainWindow"
        DataContext="{StaticResource Composition}"
        xmlns:app="clr-namespace:AvaloniaApp"
        x:DataType="app:Composition"
        Title="{Binding ClockViewModel.Time}"
        Icon="/Assets/avalonia-logo.ico"
        FontFamily="Consolas"
        FontWeight="Bold">

    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" DataContext="{Binding ClockViewModel}">
        <TextBlock Text="{Binding Date}" FontSize="64" HorizontalAlignment="Center" />
        <TextBlock Text="{Binding Time}" FontSize="128" HorizontalAlignment="Center" />
    </StackPanel>

</Window>

To use bindings in views:

  • You can set a shared resource as a data context

    DataContext="{StaticResource Composition}"

  • Specify the data type in the context:

    xmlns:app="clr-namespace:AvaloniaApp"

    x:DataType="app:Composition"

  • Use the bindings as usual:

    Title="{Binding ClockViewModel.Time}"

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.

The project file looks like this:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <OutputType>WinExe</OutputType>
        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
        <ApplicationManifest>app.manifest</ApplicationManifest>
        <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Pure.DI" Version="2.1.41">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>

</Project>

It contains an additional reference to the NuGet package:

Pure.DI NuGet DI Source code generator