Tutorial: Module Development
Modules are the core building blocks of Crystal.Avalonia applications. They provide a way to organize your code into independent, reusable units.
What is a Module?
A module is a class that implements IModule and encapsulates:
- Services - Dependencies that the module provides
- Views - UI components specific to the module
- ViewModels - Business logic for the module's views
- Initialization Logic - Setup that runs when the app starts
Creating a Module
Step 1: Define the Module Class
using Crystal.Avalonia;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace MyApp.Modules.UserManagement;
public class UserManagementModule : IModule
{
public void RegisterServices(IServiceCollection services)
{
// Register View/ViewModel pairs
services.AddMvvmTransient<UserListView, UserListViewModel>();
services.AddMvvmTransient<UserDetailView, UserDetailViewModel>();
// Register other services
services.AddSingleton<IUserRepository, UserRepository>();
services.AddTransient<UserService>();
}
public void InitializeModule(IServiceProvider serviceProvider)
{
// Perform initialization tasks
var userService = serviceProvider.GetRequiredService<UserService>();
userService.LoadUsers();
}
}
Step 2: Register the Module
In your App.axaml.cs:
public override void RegisterModules(IModuleRegistrar moduleRegistrar)
{
moduleRegistrar.RegisterModule<UserManagementModule>();
}
Module Project Structure
For larger applications, consider placing each module in its own project:
MySolution/
├── MyApp/ # Main application
│ └── App.axaml.cs
├── MyApp.Modules.UserManagement/ # Module in separate project
│ ├── UserManagementModule.cs
│ ├── Views/
│ ├── ViewModels/
│ └── Services/
└── MyApp.Modules.Settings/ # Another module
└── ...
Sharing Modules Between Apps
Modules can be easily shared across different applications:
- Create a separate project for the module
- Add the module project as a reference to your main app
- Register the module in your app
Module Project File (.csproj)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Crystal.Avalonia" />
</ItemGroup>
</Project>
Consuming in Main App
// In App.axaml.cs of the main application
public override void RegisterModules(IModuleRegistrar moduleRegistrar)
{
// Register modules from referenced projects
moduleRegistrar.RegisterModule<MyApp.Modules.UserManagement.UserManagementModule>();
moduleRegistrar.RegisterModule<MyApp.Modules.Settings.SettingsModule>();
}
Conditional registration
The library always constructs every registered module at startup. To skip a feature, do not call RegisterModule — that decision belongs in your app:
public override void RegisterModules(IModuleRegistrar moduleRegistrar)
{
if (User.IsAdmin)
moduleRegistrar.RegisterModule<AdminModule>();
moduleRegistrar.RegisterModule<CommonModule>();
}
There is no built-in lazy loading, module DependsOn, navigation service, or event aggregator. Share data across modules with ordinary DI services you register yourself.
Best Practices
| Practice | Why |
|---|---|
| Keep modules focused | Each module should represent a single feature area |
| Define module contracts | Use interfaces for cross-module communication |
| Minimize module dependencies | Modules should be as independent as possible |
| Register explicitly | No assembly scanning — every module is listed in RegisterModules |
Next Steps
- Dependency Injection - Advanced DI patterns and service registration
- Navigation - Implementing navigation between views
Further Reading
How it works: Architecture — Module System —
RegisterModule→InitService→InitModulespipeline and why there is no assembly scanning.