Route gh-aw workflow design/create/debug/upgrade requests to the right prompts.
日本語の概要は準備中です。原文の説明を表示しています。
Guidance for configuring dependency injection in .NET MAUI apps — service registration in MauiProgram.cs, lifetime selection (Singleton / Transient / Scoped), constructor injection, Shell navigation auto-resolution, platform-specific registrations, and testability patterns. USE FOR: "dependency injection", "DI setup", "AddSingleton", "AddTransient", "AddScoped", "service registration", "constructor injection", "IServiceProvider", "MauiProgram DI", "register services", "BindingContext injection". DO NOT USE FOR: data binding (use maui-data-binding), Shell route configuration (use maui-shell-navigation), unit-test mocking frameworks (use standard xUnit and NSubstitute patterns).
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
.NET MAUI uses the same Microsoft.Extensions.DependencyInjection container as ASP.NET Core. All service registration happens in MauiProgram.CreateMauiApp() on builder.Services. The container is built once at startup and is immutable thereafter.
MauiProgram.csAddSingleton, AddTransient, and AddScoped#if directivesMauiProgram.cs file| Situation | Do this | Why |
|---|---|---|
| Registering a Page or ViewModel | Prefer AddTransient | A fresh instance per navigation avoids stale state, and a Singleton page cannot be re-added to the visual tree after it is removed. Singleton is defensible for a genuinely single-instance page (e.g. a root tab you want to keep warm) |
| Registering shared/expensive state | AddSingleton | One instance app-wide (settings, DB connection, HttpClient handler) |
Tempted to use AddScoped | Use AddTransient (or AddSingleton if sharing is intended) | MAUI has no built-in request scope like ASP.NET Core's HTTP pipeline. MAUI does create one IServiceScope per window, so a Scoped service lives as long as that window — and resolved from the root provider it behaves like a Singleton. Neither gives you per-navigation freshness |
| Navigating to a DI-registered page | Register the page and its ViewModel, then Routing.RegisterRoute | Shell.Current.GoToAsync resolves the page through DI and injects its constructor dependencies |
| Platform-specific implementation | #if per platform with every platform covered | A missing platform branch leaves the service unregistered and throws at resolution time |
Do not introduce DI into a project that isn't using it, swap a working service lifetime, or add an interface purely for symmetry — only when the user asked or it fixes a real defect.
Answer narrowly, but completely. When you recommend a lifetime change, show the
registration code, and give the realistic alternatives rather than a single verdict —
for a unit-of-work or DbContext question that means AddTransient, an explicit
IServiceScopeFactory.CreateScope(), and the factory pattern
(AddDbContextFactory), with a note on when each fits. A one-line prescription is
usually a worse answer than a short menu with trade-offs.
// Explicit scope when you genuinely need unit-of-work semantics
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<MyDbContext>();
AddSingleton for shared services, AddTransient for Pages and ViewModels.MauiProgram.CreateMauiApp() on builder.Services, grouping by category (services, HTTP, ViewModels, Pages).AppShell.xaml.cs so Shell navigation auto-resolves the full dependency graph.BindingContext.#if directives, ensuring every target platform is covered or has a fallback.null dependencies or missing-registration exceptions at runtime.| Lifetime | When to Use | Typical Types |
|---|---|---|
AddSingleton<T>() | Shared state, expensive to create, app-wide config | HttpClient factory, settings service, database connection |
AddTransient<T>() | Lightweight, stateless, or needs a fresh instance per use | Pages, ViewModels, per-call API wrappers |
AddScoped<T>() | Per-window lifetime, or a manually created IServiceScope | Scoped unit-of-work (rare in MAUI) |
Key rule: Register Pages and ViewModels as Transient by default. Register shared services as Singleton.
⚠️ Avoid
AddScopedunless you manually manageIServiceScope. MAUI has no built-in request scope like ASP.NET Core. MAUI creates oneIServiceScopeper window, so a Scoped service lives as long as that window; resolved from the root provider it silently behaves as a Singleton. Neither gives per-navigation freshness.
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
// Services — Singleton for shared state
builder.Services.AddSingleton<IDataService, DataService>();
builder.Services.AddSingleton<ISettingsService, SettingsService>();
// HTTP — use typed or named clients via IHttpClientFactory
// Requires NuGet: Microsoft.Extensions.Http
builder.Services.AddHttpClient<IApiClient, ApiClient>();
// ViewModels — Transient for fresh state per navigation
builder.Services.AddTransient<MainViewModel>();
builder.Services.AddTransient<DetailViewModel>();
// Pages — Transient so constructor injection fires each time
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<DetailPage>();
return builder.Build();
}
Inject dependencies through constructor parameters. The container resolves them automatically when the type is itself resolved from DI.
public class MainViewModel
{
private readonly IDataService _dataService;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
}
public async Task LoadAsync() => Items = await _dataService.GetItemsAsync();
}
Register both Page and ViewModel. Inject the ViewModel into the Page and assign it as BindingContext:
public partial class MainPage : ContentPage
{
public MainPage(MainViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
}
When a Page is registered in DI and as a Shell route, Shell resolves it (and its full dependency graph) automatically on navigation:
// MauiProgram.cs
builder.Services.AddTransient<DetailPage>();
builder.Services.AddTransient<DetailViewModel>();
// AppShell.xaml.cs
Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));
// Navigate — DI resolves DetailPage + DetailViewModel
await Shell.Current.GoToAsync(nameof(DetailPage));
DI supplies the ViewModel's dependencies; navigation parameters arrive separately.
Don't try to inject them through the constructor — implement IQueryAttributable
on the ViewModel so it receives both:
public class DetailViewModel : ObservableObject, IQueryAttributable
{
readonly IDataService _data; // ← injected by DI
public DetailViewModel(IDataService data) => _data = data;
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
// ← supplied by navigation
if (query.TryGetValue("id", out var id))
LoadAsync(id.ToString()!);
}
}
// Navigate with a parameter — the page and its ViewModel still come from DI
await Shell.Current.GoToAsync($"{nameof(DetailPage)}?id={product.Id}");
Shell applies query attributes to the page and its BindingContext, so the
ViewModel receives them without any wiring in the page.
Use preprocessor directives to register platform implementations. Always cover every target platform or provide a no-op fallback to avoid runtime null.
#if ANDROID
builder.Services.AddSingleton<INotificationService, AndroidNotificationService>();
#elif IOS || MACCATALYST
builder.Services.AddSingleton<INotificationService, AppleNotificationService>();
#elif WINDOWS
builder.Services.AddSingleton<INotificationService, WindowsNotificationService>();
#else
builder.Services.AddSingleton<INotificationService, NoOpNotificationService>();
#endif
Prefer constructor injection. Use explicit resolution only where injection is genuinely unavailable (custom handlers, platform callbacks):
// From any Element with a Handler
var service = this.Handler.MauiContext.Services.GetService<IDataService>();
For dynamic resolution, inject IServiceProvider:
public class NavigationService(IServiceProvider serviceProvider)
{
public T ResolvePage<T>() where T : Page
=> serviceProvider.GetRequiredService<T>();
}
Define interfaces for every service so implementations can be swapped in tests:
public interface IDataService
{
Task<List<Item>> GetItemsAsync();
}
// Production registration
builder.Services.AddSingleton<IDataService, DataService>();
// Test registration — swap without touching production code
var services = new ServiceCollection();
services.AddSingleton<IDataService, FakeDataService>();
// ❌ ViewModel keeps stale state across navigations
builder.Services.AddSingleton<DetailViewModel>();
// ✅ Fresh instance each navigation
builder.Services.AddTransient<DetailViewModel>();
Pages declared in Shell XAML via <ShellContent ContentTemplate="{DataTemplate views:DetailPage}"> are instantiated with Activator.CreateInstance (ElementTemplate.cs), not through the service provider. Constructor injection does not run on that path: if the page's only constructor takes dependencies, you get a MissingMethodException — not a silently null dependency.
Pages reached through Routing.RegisterRoute + GoToAsync are different: they go through ActivatorUtilities.GetServiceOrCreateInstance (Routing.cs), which injects registered dependencies even if the page type itself was never registered, and throws if a required dependency cannot be resolved.
// Registering the page and its dependencies keeps both paths working
builder.Services.AddTransient<DetailPage>();
builder.Services.AddTransient<DetailViewModel>();
If you need DI for a tab/flyout page, give it a parameterless constructor that resolves what it needs, or navigate to it by route instead of embedding it in ContentTemplate.
XAML resources in App.xaml are parsed during InitializeComponent() — before the container is fully available. Defer service-dependent work to CreateWindow():
public partial class App : Application
{
private readonly IServiceProvider _services;
public App(IServiceProvider services)
{
_services = services;
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
// Safe — container is fully built
// Requires: builder.Services.AddTransient<AppShell>() in MauiProgram.cs
var appShell = _services.GetRequiredService<AppShell>();
return new Window(appShell);
}
}
// ❌ Hides dependencies, hard to test
var svc = this.Handler.MauiContext.Services.GetService<IDataService>();
// ✅ Constructor injection — explicit and testable
public class MyViewModel(IDataService dataService) { }
Forgetting a platform in #if blocks means GetService<T>() returns null at runtime on that platform. Always include an #else fallback or cover every target.
See the rule table above: AddScoped gives you either window lifetime or Singleton behaviour, never per-navigation freshness. Use AddTransient or AddSingleton unless you explicitly create and manage an IServiceScope.
MauiProgram.csAddTransient; shared services use AddSingleton#if registrations cover all target platforms or include a fallbackCreateWindow(), not run during XAML parseAddScoped used only when window lifetime is intended, or alongside a manually created IServiceScopeまだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Route gh-aw workflow design/create/debug/upgrade requests to the right prompts.
日本語の概要は準備中です。原文の説明を表示しています。
Use a repo-root `.editorconfig` to configure free .NET analyzer and style rules. Use when a .NET repo needs rule severity, code-style options, section layout, or analyzer ownership made explicit. USE FOR: the repo needs a root .editorconfig; analyzer severity and style ownership are unclear; the team wants one source of truth for rule configuration. DO NOT USE FOR: choosing analyzers with no config change; formatting-only execution with no config ownership question. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.
日本語の概要は準備中です。原文の説明を表示しています。
Scans .NET code for ~50 performance anti-patterns across async, memory, strings, collections, LINQ, regex, serialization, and I/O with tiered severity classification. Use when analyzing .NET code for optimization opportunities, reviewing hot paths, or auditing allocation-heavy patterns.
日本語の概要は準備中です。原文の説明を表示しています。
Symbolicate the .NET runtime frames in an Android tombstone file. Extracts BuildIds and PC offsets from the native backtrace, downloads debug symbols from the Microsoft symbol server, and runs llvm-symbolizer to produce function names with source file and line numbers. USE FOR triaging a .NET MAUI or Mono Android app crash from a tombstone, resolving native backtrace frames in libmonosgen-2.0.so or libcoreclr.so to .NET runtime source code, or investigating SIGABRT, SIGSEGV, or other native signals originating from the .NET runtime on Android. DO NOT USE FOR pure Java/Kotlin crashes, managed .NET exceptions that are already captured in logcat, or iOS crash logs. INVOKES Symbolicate-Tombstone.ps1 script, llvm-symbolizer, Microsoft symbol server.
日本語の概要は準備中です。原文の説明を表示しています。
Symbolicate .NET runtime frames in Apple platform .ips crash logs (iOS, tvOS, Mac Catalyst, macOS). Extracts UUIDs and addresses from the native backtrace, locates dSYM debug symbols, and runs atos to produce function names with source file and line numbers. Automatically downloads .dwarf symbols from the Microsoft symbol server using Mach-O UUIDs. USE FOR triaging a .NET MAUI or Mono app crash from an .ips file on any Apple platform, resolving native backtrace frames in libcoreclr or libmonosgen-2.0 to .NET runtime source code, retrieving .ips crash logs from a connected iOS device or iPhone, or investigating EXC_CRASH, EXC_BAD_ACCESS, SIGABRT, or SIGSEGV originating from the .NET runtime. DO NOT USE FOR pure Swift/Objective-C crashes with no .NET components, or Android tombstone files. INVOKES Symbolicate-Crash.ps1 script, atos, dwarfdump, idevicecrashreport.
日本語の概要は準備中です。原文の説明を表示しています。
Design or review .NET solution architecture across modular monoliths, clean architecture, vertical slices, microservices, DDD, CQRS, and cloud-native boundaries without over-engineering. USE FOR: .NET architecture choices; layer and domain boundary review; service decomposition; clean architecture, vertical slice, DDD, CQRS, and modular monolith decisions. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.
日本語の概要は準備中です。原文の説明を表示しています。