К основному содержанию
скилл GitHub Copilot официальный · GitHub

mvvm-toolkit-di — скилл для GitHub Copilot: что делает и установка

Помогает связать ViewModels из CommunityToolkit.Mvvm с контейнером Microsoft.Extensions.DependencyInjection в проектах на WPF, WinUI 3, .NET MAUI, Uno и Avalonia.

Установить в GitHub Copilot · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add github/awesome-copilot --skill mvvm-toolkit-di
Открыть на GitHub

Что делает mvvm-toolkit-di

Скилл объясняет, как связать ViewModels и службы в единый контейнер через .NET Generic Host (Host.CreateDefaultBuilder()) или чистый ServiceCollection. В CommunityToolkit.Mvvm намеренно нет собственного DI-контейнера. Скилл учит регистрировать зависимости, выбирать между Singleton, Transient и Scoped, а также передавать IMessenger через конструктор в ObservableRecipient.

Внутри разобраны сценарии разрешения моделей представлений в code-behind через App.GetService(), вызов именованных служб с помощью атрибута [FromKeyedServices] в .NET 8+ и изоляция логики для тестов на базе Moq. Инструкции также помогают избавиться от вызовов Ioc.Default, скрывающих зависимости.

Ставьте этот скилл, если проектируете архитектуру нового десктопного или мобильного XAML-клиента на C#. Если вы уже настроили точку сборки приложения и не трогаете слой DI, руководство не потребуется.

Какие задачи решает

  • Собрать точку композиции через .NET Generic Host в XAML-приложении
  • Назначить время жизни Singleton, Transient или Scoped для сервисов и ViewModels
  • Внедрить единый экземпляр IMessenger через конструктор ObservableRecipient
  • Разрешить корневую ViewModel страницы в коде представления без Service Locator
  • Изолировать зависимости ViewModel для модульных тестов с библиотекой Moq

Примеры запросов агенту

  • Как настроить Generic Host и внедрить ContactViewModel в WPF-приложении?
  • Покажи регистрацию IMessenger и передачу через конструктор без вызова Ioc.Default

Как установить mvvm-toolkit-di

1Через Skills CLI (Vercel)

npx skills add github/awesome-copilot --skill mvvm-toolkit-di

2GitHub CLI

gh skill install github/awesome-copilot mvvm-toolkit-di

3Вручную: положить файлы

.github/skills/mvvm-toolkit-di/SKILL.md

Из того же набора554

Весь набор →
git-commit официальный github Скилл разбирает изменения Git, выбирает связанные файлы и создаёт коммит в формате Conventional Commits. Пригодится, когда нужно закоммитить одну или несколько логических частей изменений. GitHub Copilot 47 165установок excalidraw-diagram-generator официальный github Скилл превращает описание на обычном языке в диаграмму Excalidraw. Подойдёт, когда нужно показать процесс, связи между объектами или устройство системы. GitHub Copilot 31 639установок multi-stage-dockerfile официальный github Скилл помогает создавать многоэтапные Dockerfile для разных языков и фреймворков. Пригодится, когда важны размер и состав runtime-образа. GitHub Copilot 29 688установок documentation-writer официальный github Скилл для создания технической документации по фреймворку Diátaxis: туториалы, гайды, справки и объяснения. Предлагает структуру перед написанием. GitHub Copilot 27 757установок prd официальный github Собирает из идеи структурированный PRD с пользовательскими историями, техническими требованиями и рисками. Подходит для планирования новых функций, в том числе с AI. GitHub Copilot 24 035установок refactor официальный github Скилл помогает менять структуру кода без изменения внешнего поведения. Подойдёт для постепенной уборки сложных функций, дублирования и кодовых запахов. GitHub Copilot 22 321установок java-springboot официальный github Задаёт практики разработки Spring Boot-приложений: от структуры проекта до тестов и безопасности. Пригодится, когда Copilot пишет или проверяет Java-код. GitHub Copilot 21 272установок create-readme официальный github Скилл изучает проект и создаёт структурированный README.md. Подойдёт, когда документацию нужно собрать по коду и файлам проекта. GitHub Copilot 19 081установок

Похожие

landing Alireza Rezvani Собирает одностраничный лендинг в одном HTML-файле: 3D-анимации на CSS, скролл-эффекты GSAP и параллакс за курсором. Claude Code 404установок frontend-app-builder официальный OpenAI Помогает спроектировать и собрать интерфейс приложения, сайта или игры, затем проверить его в браузере. Подойдёт для новой frontend-разработки и явного запроса на редизайн. CX Codex GP ChatGPT 252установок frontend-design официальный anthropics Скилл задаёт осмысленное визуальное направление для новых интерфейсов и редизайна. Подойдёт, когда стандартный шаблонный UI не устраивает. Claude Code Claude.ai 1 025 790установок design-taste-frontend Leonxlnx Скилл задаёт арт-дирекцию для публичных страниц и рабочих экранов, а затем проверяет результат в браузере по скриншотам. Нужен, когда интерфейс выглядит шаблонным или недоработанным. Claude Code CX Codex Cursor +1 673 100установок prototype Matt Pocock Создаёт временный прототип, чтобы проверить логику, модель состояния или вид интерфейса до работы над основным кодом. Claude Code 986 201установок hyperframes heygen-com Скилл задаёт агенту правила рендера HTML-кода в видео через HyperFrames. Помогает собирать проморолики, эксплейнеры и титры прямо в коде проекта. Claude Code CX Codex Cursor +1 1 200 000установок hyperframes-cli heygen-com Управляет проектами HyperFrames через терминал: собирает таймлайн, проверяет контраст кадров, запускает предпросмотр и рендерит видео локально или в облаке. Claude Code CX Codex Cursor +1 1 200 000установок webapp-testing официальный anthropics Скилл запускает проверки локальных веб-приложений через Playwright: помогает отлаживать UI, делать скриншоты и смотреть логи браузера. Claude Code Claude.ai 186 758установок

Вопросы о mvvm-toolkit-di

Как установить mvvm-toolkit-di в GitHub Copilot?

Выполните команду gh skill install github/awesome-copilot mvvm-toolkit-di в терминале или добавьте файл .github/skills/mvvm-toolkit-di/SKILL.md в репозиторий проекта.

Что делает скилл mvvm-toolkit-di?

Он даёт агенту инструкции по правильной интеграции CommunityToolkit.Mvvm с библиотекой Microsoft.Extensions.DependencyInjection, включая выбор времени жизни сервисов и отказ от сервис-локатора.

Зачем заменять Ioc.Default на внедрение через конструктор?

Внедрение через конструктор делает зависимости явными, упрощает создание моков в тестах и позволяет контейнеру выявлять отсутствующие регистрации сразу при запуске приложения.

Исходный текст

SKILL.md · MIT
---
name: mvvm-toolkit-di
description: 'Wire CommunityToolkit.Mvvm ViewModels into Microsoft.Extensions.DependencyInjection. Covers the .NET Generic Host composition root, constructor injection, service lifetimes (Singleton / Transient / Scoped), IMessenger registration, resolving ViewModels in Views, keyed services, testing seams, and the legacy Ioc.Default escape hatch. Use across WPF, WinUI 3, .NET MAUI, Uno, and Avalonia.'
---

# CommunityToolkit.Mvvm + `Microsoft.Extensions.DependencyInjection`

The MVVM Toolkit deliberately ships **no DI container** — it composes with
`Microsoft.Extensions.DependencyInjection`, the same container ASP.NET
Core, Worker services, and the .NET Generic Host use.

> **TL;DR.** Build the service provider once at startup (prefer
> `Host.CreateDefaultBuilder()`). Register services and ViewModels.
> Inject through constructors. Avoid `Ioc.Default.GetService<T>()`
> in user code.

---

## When to use this skill

- Standing up the composition root for a new XAML app (WPF, WinUI 3,
  MAUI, Uno, Avalonia)
- Choosing service/VM lifetimes
- Wiring `IMessenger` once and injecting it into `ObservableRecipient`
  ViewModels
- Resolving a page's ViewModel without coupling to a service locator
- Diagnosing "Unable to resolve service for type X while attempting to
  activate Y"

For source generators and ViewModel patterns see the **`mvvm-toolkit`**
skill. For Messenger pub/sub see **`mvvm-toolkit-messenger`**.

---

## Recommended composition root (Generic Host)

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using CommunityToolkit.Mvvm.Messaging;

public partial class App : Application
{
    public IHost Host { get; }

    public App()
    {
        Host = Microsoft.Extensions.Hosting.Host
            .CreateDefaultBuilder()
            .ConfigureServices((_, services) =>
            {
                services.AddSingleton<IFilesService, FilesService>();
                services.AddSingleton<ISettingsService, SettingsService>();
                services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default);

                services.AddSingleton<ShellViewModel>();
                services.AddTransient<ContactViewModel>();
                services.AddTransient<EditorViewModel>();
            })
            .Build();
    }

    public static T GetService<T>() where T : class =>
        ((App)Current).Host.Services.GetRequiredService<T>();
}
```

Generic Host benefits:

- `appsettings.json` binding via `Microsoft.Extensions.Configuration`
- Logging via `Microsoft.Extensions.Logging`
- Hosted services (`IHostedService`) for background work
- Scope validation in development builds

> WPF and Windows Forms must integrate the host lifetime with the app
> lifetime — see
> [Use the .NET Generic Host in a WPF app](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/app-development/how-to-use-host-builder).

### Without Generic Host

When you only need a service container and want zero extra dependencies:

```csharp
var services = new ServiceCollection();
services.AddSingleton<IFilesService, FilesService>();
services.AddTransient<ContactViewModel>();
ServiceProvider provider = services.BuildServiceProvider();
```

---

## Constructor injection

Inject services and child ViewModels through the constructor:

```csharp
public sealed partial class ContactViewModel(
    IFilesService files,
    IMessenger messenger,
    ILogger<ContactViewModel> logger)
    : ObservableRecipient(messenger)
{
    [ObservableProperty]
    private string? name;

    [RelayCommand]
    private async Task SaveAsync()
    {
        logger.LogInformation("Saving {Name}", Name);
        await files.SaveAsync(Name!);
    }
}
```

Why constructor injection beats a service locator:

- Dependencies are explicit and visible at the call site
- Unit tests inject fakes/mocks directly
- The DI container validates the dependency graph at startup
- Missing registrations throw immediately, not at first use

---

## Lifetimes

| Lifetime | Method | Typical use in XAML apps |
|----------|--------|--------------------------|
| Singleton | `AddSingleton<T>` | Shell/main-window VM, settings, file/HTTP services, the shared `IMessenger`, app-wide caches |
| Transient | `AddTransient<T>` | Per-page or per-document ViewModels (a fresh instance every resolve) |
| Scoped | `AddScoped<T>` | Rarely needed in client apps; useful with explicit `IServiceScope` (e.g., per-window scopes) |

```csharp
services.AddSingleton<ShellViewModel>();   // 1 instance for app lifetime
services.AddTransient<NoteViewModel>();    // new instance per resolve
services.AddScoped<DialogService>();       // 1 per scope (rare)
```

---

## Resolving in a View

Resolve the page's root ViewModel in code-behind, then let it pull its
own dependencies:

```csharp
public sealed partial class ContactPage : Page
{
    public ContactViewModel ViewModel { get; }

    public ContactPage()
    {
        ViewModel = App.GetService<ContactViewModel>();
        InitializeComponent();
    }
}
```

Bind in XAML with `{x:Bind ViewModel.Xxx}` (compiled bindings) or
`{Binding Xxx}` against `DataContext`.

For navigation frameworks (WinUI 3 `Frame.Navigate`, MAUI Shell, Prism,
MVVMCross), let the framework resolve the page and the page resolves its
ViewModel from DI. Don't `new` ViewModels manually.

---

## `IMessenger` registration

Register the messenger you want once, inject `IMessenger` everywhere:

```csharp
services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default);
// or
services.AddSingleton<IMessenger>(StrongReferenceMessenger.Default);
```

Then:

```csharp
public sealed partial class MyViewModel(IMessenger messenger)
    : ObservableRecipient(messenger) { }
```

For per-window messengers, register with keyed services or as scoped
instances and inject into per-window ViewModels.

See the **`mvvm-toolkit-messenger`** skill for the messenger surface area.

---

## Keyed services (.NET 8+)

Resolve different implementations of the same interface by key:

```csharp
services.AddKeyedSingleton<IExporter, CsvExporter>("csv");
services.AddKeyedSingleton<IExporter, JsonExporter>("json");

public sealed partial class ExportViewModel(
    [FromKeyedServices("csv")] IExporter csvExporter,
    [FromKeyedServices("json")] IExporter jsonExporter)
    : ObservableObject { /* ... */ }
```

---

## Testing seams

Constructor-injected dependencies are trivial to swap in tests. With
`Moq`:

```csharp
[Fact]
public async Task Save_calls_files_service()
{
    var files = new Mock<IFilesService>();
    var messenger = new WeakReferenceMessenger();
    var logger = NullLogger<ContactViewModel>.Instance;

    var vm = new ContactViewModel(files.Object, messenger, logger)
    {
        Name = "Ada"
    };

    await vm.SaveCommand.ExecuteAsync(null);

    files.Verify(f => f.SaveAsync("Ada"), Times.Once);
}
```

If you're mocking `Ioc.Default` or static state, the ViewModel is using a
service locator — refactor to constructor injection.

---

## Legacy: `Ioc.Default`

`CommunityToolkit.Mvvm.DependencyInjection.Ioc` is an escape hatch for
cases where constructor injection is impossible — XAML-instantiated VMs
for design-time data, `ValueConverter`s, control templates.

```csharp
Ioc.Default.ConfigureServices(
    new ServiceCollection()
        .AddSingleton<IFilesService, FilesService>()
        .AddTransient<ContactViewModel>()
        .BuildServiceProvider());

var files = Ioc.Default.GetRequiredService<IFilesService>();
```

Treat it as the last resort. Inside ViewModels, services, and any class
the DI container can construct, prefer constructor injection.

---

## Common pitfalls

1. **`Ioc.Default.GetService<T>()` inside a VM constructor.** Hides the
   dependency, breaks unit tests, prevents startup graph validation.
2. **Everything `Singleton`.** A "per-document" VM registered as singleton
   becomes shared state across all documents — subtle data corruption.
   Use `AddTransient` for per-instance VMs.
3. **Multiple `BuildServiceProvider()` calls.** Each call is a fresh
   container — singletons aren't shared. Build once at startup.
4. **Capturing `IServiceProvider` in long-lived objects.** Indicates a
   service-locator pattern. Inject the specific dependencies you need.
5. **No scope validation in development.** Use `Host.CreateDefaultBuilder()`
   (which sets `ValidateScopes` and `ValidateOnBuild` in development) so
   registration mistakes fail at startup, not at first use.
6. **Resolving scoped services from the root provider.** They're
   effectively promoted to singleton lifetime — the warning is silent
   without scope validation. Either change the lifetime or resolve from
   an explicit `IServiceScope`.

---

## References

| Topic | File |
|-------|------|
| Full deep dive (Generic Host setup, lifetimes, keyed services, testing patterns, legacy Ioc) | [`references/dependency-injection.md`](references/dependency-injection.md) |

External:

- DI overview: <https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection>
- DI usage: <https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-usage>
- MVVM Toolkit Ioc page: <https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/ioc>
- Generic Host: <https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host>

© github · MIT · github/awesome-copilot

Смотрите также

Спонсоры проекта

Компании, которые помогают держать аналитику открытой