[SPEC] Allow global ResourceDictionary configuration in MauiProgram to resolve DI and StaticResource lifecycle issues
- Dominant language
- C#
- Stars
- 23.3k
- Forks
- 2k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 290
Description
### Description
Today, app-wide resources in .NET MAUI are typically defined in `App.xaml`, and this dictionary becomes available only once the `Application` instance is constructed. However, this leads to a fundamental lifecycle issue when using dependency injection:
When a page is injected into the `App` constructor (a common and recommended pattern), it may rely on `{StaticResource}` in its XAML. This causes runtime exceptions because the global resource dictionary has not yet been initialized—`Application.Current.Resources` is empty at that point.
This proposal introduces two coordinated enhancements:
1. A new `ConfigureResources()` method on `MauiAppBuilder`, allowing resource dictionaries to be loaded and configured during startup, before any pages are resolved.
2. A modification to the XAML resource resolution pipeline (`StaticResourceExtension`) to introduce a fallback to globally defined resources, maintained in a new static `GlobalResources` class.
This combination allows developers to define application-wide resources earlier and more reliably, avoids runtime XAML failures, and modernises the resource hierarchy to reflect the fact that `Application` is no longer the true entry point in .NET MAUI apps. This pattern also follows the established precedent, e.g. `ConfigureFonts()`.
> **Note:** A proof-of-concept implementation has been completed in a fork and verified to work as intended. See: [https://github.com/matt-goldman/maui](https://github.com/matt-goldman/maui) with a demo of usage in the sandbox app.
#### Resolution Chain Adjustment
Currently, `{StaticResource}` lookup ends at `Application.Current.Resources`. This change adds a final fallback to `GlobalResources.Current`, allowing resource resolution even when `Application` is not yet instantiated.
This supports:
- Dependency-injected pages constructed before `App`
- Resource access from code (e.g., value converters)
- Modular, startup-driven architecture consistent with other DI/hosting patterns
The fallback is conservative: if the resource exists in the visual tree or `Application`, it is used. `GlobalResources` is only checked last.
> **Note:** The current implementation focuses on `StaticResourceExtension`. A similar update would be required for `DynamicResourceExtension`, and this should be considered part of the overall work.
> A question for maintainers: are there any other locations in the resource resolution pipeline that would need to be updated to support this fallback? For example, the proof-of-concept implementation does not support IntelliSense.
### (Public) API Changes
### `MauiAppBuilder` Extensions
#### Properties
| API | Description |
| --- | ----------- |
| `ConfigureResources(Action configure)` | Adds support for configuring the app-wide `ResourceDictionary` during startup, before DI resolves any pages. |
### `ResourceDictionaryBuilder` (in `Microsoft.Maui.Controls.Hosting`)
| API | Description |
| --- | ----------- |
| `void AddXaml(string path)` | Adds a XAML resource dictionary by path (relative or embedded resource). |
| `void Add(string key, object value)` | Adds a key/value pair directly to the dictionary. |
| `IDictionary Build()` | Returns the constructed dictionary contents. |
### `GlobalResources` (in `Microsoft.Maui.Controls`)
```csharp
public static class GlobalResources
{
public static ResourceDictionary Current { get; } = new ResourceDictionary();
}
```
This allows resources to be accessed in code as well:
```csharp
var color = GlobalResources.Current["MyTertiaryColor"] as Color;
```
### Usage Scenarios
### C# Example
```csharp
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
fonts.AddFont("FluentSystemIcons-Filled.ttf", "FluentIcons");
})
.ConfigureResources(resources =>
{
resources.AddXaml("Resources/Styles/Colors.xaml");
resources.AddXaml("Resources/Styles/Styles.xaml");
resources.Add("MyTertiaryColor", Color.FromArgb("#445566"));
});
```
### XAML Example (LoginPage.xaml)
```xml
```
This now resolves without error, even if `LoginPage` is constructed before `App`.
### Code Example (e.g., in a converter)
```csharp
var color = GlobalResources.Current["MyTertiaryColor"] as Color;
```
### Backward Compatibility
- ✅ Fully backward compatible.
- ✅ `App.xaml` and `Application.Current.Resources` continue to work exactly as before.
- ✅ `ConfigureResources` is opt-in and introduces no breaking changes.
- ✅ Template projects could opt-in to use the new approach over time.
### Difficulty
Low
Contributor guide
Research direction
Start by reviewing MauiAppBuilder, ResourceDictionaryBuilder, GlobalResources, and the StaticResourceExtension resolution path described in the proposal. Then assess the corresponding DynamicResourceExtension and IntelliSense implications. Done means startup resource configuration works before page resolution, existing Application resources retain precedence, and the documented C# and XAML usage scenarios resolve correctly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- frontend, mobile-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100