Skip to content
This repository was archived by the owner on Nov 8, 2025. It is now read-only.

2 Using the dependency registration source generator

Matt Goldman edited this page Oct 22, 2024 · 9 revisions

2.1 Introduction

With Maui.Plugins.PageResolver, you can automatically register all of your dependencies (Pages, ViewModels, Services and associated interfaces) with a source generator.

The source generator is conventions based, so make sure you follow the conventions for it to work as expected.

The source generator will scan your project and automatically register dependencies for you, and because it is a source generator, the code is included at compile-time, which has no runtime impact (compared to, say, something using reflection).

Follow the instructions below to simplify dependency registration, use PageResolver, and remove a whole load of common boilerplate code from your .NET MAUI projects.

Once you have completed these steps, use the PageResolver as per the instructions in step 1.4 of the using PageResolver instructions.

2.2 Requirements

The source generator currently only works for single-project .NET MAUI apps. If you have created additional class libraries for your Pages, ViewModels or Services, you will need to manually register these dependencies in code.

Setup

The source generator is included in the main PageResolver package, so ensure you have this installed:

dotnet add package Goldie.MauiPlugins.PageResolver

Instead of calling the UsePageResolver extension method, call UseAutodependencies:

namespace MyApp
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
            .UseMauiApp<App>()				
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            })
            .UseAutodependencies();

            // YOU DON'T NEED THESE ANYMORE
            // builder.Services.AddSingleton<IMyService, MyService>();
            // builder.Services.AddTransient<MyViewModel>();
            // builder.Services.AddTransient<MyPage>();
            // builder.Services.UsePageResolver();

            return builder.Build();
        }
    }
}

That's it! If you've followed these steps, and the conventions below, all of your Pages, ViewModels and Services (and their associated interfaces) will be automatically registered on build, and you can use await Navigation.PushAsync<MyPage>(); without any further effort.

Note that the UseAutodependencies extension method is created by the source generator itself, so if you get an error when trying to use it try to rebuild your solution first.

2.3 Conventions

PageResolver will automatically register Pages, ViewModels and Services for you based on the following conventions.

You can still use it without following the conventions, but anything you want to register in the DI container that does not follow these conventions, you will need to register yourself.

2.3.1 Pages

Any class with a class name that ends with Page will be automatically registered with the DI container.

All pages are registered with TRANSIENT scope by default (see overriding conventions below).

Examples:

Files Class name
MainPage.xaml, MainPage.xaml.cs MainPage <-- This will be registered
ProductPage.cs ProductPage <-- This will be registered
ProductView.xaml, ProductView.xaml.cs ProductView <-- This will NOT be automatically registered

2.3.2 ViewModels

Any class with a class name that ends with ViewModel will be automatically registered with the DI container.

All ViewModels are registered with TRANSIENT scope by default (see overriding conventions below).

Examples:

Files Class name
MainPageViewModel.cs MainPageViewModel <-- This will be registered
ProductViewModel.cs ProductViewModel <-- This will be registered
ProductModel.cs ProductModel <-- This will NOT be automatically registered

2.3.4 Services

Any class with a class name that ends with Service will be automatically registered with the DI container.

All Services are registered with Singleton scope by default (see overriding conventions below).

For interfaces with a name that matches the pattern I[...]Service, services are registered as an implementation of they interface they match.

Example #1: Automatic interface implementation registration

Let's say you define an interface called IMappingService, and create a class that implements it called MappingService. These will be automatically registered for you:

services.AddSingleton<IMappingService, MappingService>();

Example #2: Automatic concrete implementation

Let's say you have a class in your project called NetworkService, but it does not implement any interface, this will be automatically registered for you:

services.AddSingleton<NetworkService>();

This can still be injected into the constructor of your Pages or ViewModels:

public MyViewModel(NetworkService networkService)
{
   ...

Example #3: Multiple automatic concrete implementations

Let's say you have an interface called IPaymentService, and you have two classes that implement it: StripeService and MockPaymentService. Both of these will be registered automatically, but neither will be registered as an implementation of the interface:

services.AddSingleton<StripeService>();
services.AddSingleton<MockPaymentService>();

You will need to register these implementations yourself in code.

2.4 Overriding lifetime conventions

By default, Pages and ViewModels will be registered as Transient, and Services will be registered as Singelton. You can override these by decorating any class with the [Singleton], [Transient], or [Ignore] attributes (these are in the Maui.Plugins.PageResolver.Attributes namespace).

Any Page, ViewModel, or Service decorated with the [Singleton] or [Transient] attributes will be explicitly registered according to the attribute, as in the following example.

[Singleton]
public class ProfilePage : ContentPage
{
}
[Transient]
public class AuthenticationService : IAuthService

This will generate:

// pages
builder.Services.AddSingleton<ProfilePage>();

//services
builder.Services.AddTransient<AuthenticationService, IAuthService>();

It may be necessary to not register certain types with the container, for example abstract classes that match the naming pattern, or excluding mock dependencies used only for scratch testing. Any Page, ViewModel, or Service decorated with the [Ignore] attribute will not be registered, as in the following example.

public class PaymentPage: ContentPage
{
}
[Ignore]
public class MockPaymentService: IPaymentService
{
    //...
}
public class PaymentService: IPaymentService
{
    //...
}

This will generate:

// pages
builder.Services.AddTransient<PaymentPage>();

//services
builder.Services.AddSingleton<PaymentService, IPaymentService>();

// MockPaymentService is not registered
// Without using ignore, it would be registered like so:

// builder.Services.AddSingleton<MockPaymentService>();