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

mvvm-toolkit-messenger — скилл для GitHub Copilot: pub/sub в MVVM

Скилл для GitHub Copilot по Messenger из CommunityToolkit.Mvvm: связывает ViewModels через pub/sub, без прямых ссылок друг на друга.

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

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

Скилл разбирает, как устроен Messenger в CommunityToolkit.Mvvm 8.x: какой класс брать по умолчанию, как объявлять сообщения, как подписываться и отписываться. Внутри — готовые примеры кода на C# и таблица выбора между WeakReferenceMessenger.Default и StrongReferenceMessenger.Default.

Отдельно разобраны каналы (tokens) для привязки сообщений к окну или подсистеме, семейство RequestMessage для запросов с ответом и жизненный цикл ObservableRecipient с переключением IsActive. Есть список типичных ошибок: захват this в лямбде, забытый Unregister у strong-ref получателей.

Скилл рассчитан на WPF, WinUI 3, .NET MAUI, Uno и Avalonia. Ставьте, если у вас несколько ViewModels должны реагировать на одно событие и вы не хотите плодить ссылки между ними. Если мессенджер в проекте не используется, проходите мимо.

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

  • связать две ViewModels без прямой ссылки друг на друга
  • выбрать между weak- и strong-реализацией мессенджера
  • ограничить сообщения одним окном через токен
  • запросить значение у другой ViewModels и дождаться ответа
  • починить обработчик, который не срабатывает из-за weak-ссылки

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

  • Добавь подписку на ThemeChangedMessage через WeakReferenceMessenger.Default
  • Сделай запрос CurrentUserRequest с ответом от UserService
  • Почему мой обработчик не вызывается при WeakReferenceMessenger?

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

1Через Skills CLI (Vercel)

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

2GitHub CLI

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

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

.github/skills/mvvm-toolkit-messenger/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установок

Похожие

frontend-app-builder официальный OpenAI Помогает спроектировать и собрать интерфейс приложения, сайта или игры, затем проверить его в браузере. Подойдёт для новой frontend-разработки и явного запроса на редизайн. CX Codex GP ChatGPT 252установок frontend-testing-debugging официальный OpenAI Помогает агенту находить баги в интерфейсе, проверять локальный dev-сервер и верстку. Организует пошаговую валидацию через плагин Browser или Playwright. CX Codex GP ChatGPT 159установок game-ui-frontend официальный OpenAI Скилл помогает проектировать интерфейсы браузерных игр: HUD, меню и оверлеи. Защищает игровое поле от перекрытия панелями и настраивает блокировку ввода для 3D-камеры. CX Codex GP ChatGPT 98установок 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установок

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

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

Через npx skills add github/awesome-copilot --skill mvvm-toolkit-messenger или gh skill install github/awesome-copilot mvvm-toolkit-messenger. Файл скилла окажется в .github/skills/mvvm-toolkit-messenger/SKILL.md.

Что делает mvvm-toolkit-messenger?

Описывает pub/sub из CommunityToolkit.Mvvm: объявление сообщений, подписку, каналы, request/reply и отписку. Помогает агенту писать код мессенджера без типичных ошибок с временем жизни получателей.

С какими платформами работает скилл?

Примеры рассчитаны на WPF, WinUI 3, .NET MAUI, Uno и Avalonia. Нужен проект на CommunityToolkit.Mvvm 8.x.

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

SKILL.md · MIT
---
name: mvvm-toolkit-messenger
description: 'CommunityToolkit.Mvvm Messenger pub/sub for decoupled communication between ViewModels (or any objects). Covers WeakReferenceMessenger vs StrongReferenceMessenger, IRecipient<TMessage>, RequestMessage<T> / AsyncRequestMessage<T> / CollectionRequestMessage<T>, ValueChangedMessage<T>, channels (tokens), and the ObservableRecipient activation lifecycle. Use across WPF, WinUI 3, .NET MAUI, Uno, and Avalonia.'
---

# CommunityToolkit.Mvvm Messenger

Pub/sub messaging for ViewModels (or any objects) without forcing a shared
reference graph. Part of `CommunityToolkit.Mvvm` 8.x.

> **TL;DR.** Default to `WeakReferenceMessenger.Default`. Register handlers
> with the `(recipient, message)` lambda and the `static` modifier so you
> never capture `this`. Inherit from `ObservableRecipient` and toggle
> `IsActive` at activation/deactivation to get automatic register/unregister.

---

## When to use this skill

- Two or more ViewModels need to react to an event (login, theme change,
  save, navigation) without holding references to each other
- A ViewModel needs to ask another VM for a value (request/reply)
- You're scoping events to a sub-system or window with channel tokens
- Diagnosing "my handler never fires" or weak-reference recipient lifetime
  problems

For source generators, base classes, and commands see the **`mvvm-toolkit`**
skill. For DI wiring (registering an `IMessenger` instance), see
**`mvvm-toolkit-di`**.

---

## Choose an implementation

| Type | When |
|------|------|
| `WeakReferenceMessenger.Default` | **Default.** Recipients held weakly — eligible for GC even while registered. Internal trimming runs during full GCs; no manual `Cleanup()` needed. |
| `StrongReferenceMessenger.Default` | Profiler shows the messenger is hot and allocation matters. Recipients are pinned until you `Unregister`. Forgetting unregistration leaks them. |
| Custom `IMessenger` instance | Per-window/per-scope (e.g., one messenger per app window). Construct directly, inject via DI. |

`ObservableRecipient`'s parameterless constructor uses
`WeakReferenceMessenger.Default`. Pass a different `IMessenger` to its
constructor to override.

---

## Define a message

The toolkit ships base classes; any class works.

```csharp
using CommunityToolkit.Mvvm.Messaging.Messages;

// Single-payload broadcast
public sealed class LoggedInUserChangedMessage(User user)
    : ValueChangedMessage<User>(user);

// Custom shape (records are great for this)
public sealed record ThemeChangedMessage(AppTheme NewTheme);

// Empty signal
public sealed record RefreshRequestedMessage;
```

---

## Register a recipient

### Lambda style (recommended)

```csharp
WeakReferenceMessenger.Default.Register<MyViewModel, ThemeChangedMessage>(
    this,
    static (recipient, message) => recipient.OnThemeChanged(message.NewTheme));
```

The `static` modifier prevents accidental closure allocation and keeps
`this` out of the lambda — use the `recipient` parameter instead.

### `IRecipient<TMessage>` interface style

```csharp
public sealed class MyViewModel : ObservableRecipient,
    IRecipient<ThemeChangedMessage>,
    IRecipient<RefreshRequestedMessage>
{
    public void Receive(ThemeChangedMessage message) { /* ... */ }
    public void Receive(RefreshRequestedMessage message) { /* ... */ }
}
```

`ObservableRecipient.OnActivated()` calls `Messenger.RegisterAll(this)`,
which subscribes every `IRecipient<T>` interface implemented by the type.
If you're not using `ObservableRecipient`, register manually:

```csharp
WeakReferenceMessenger.Default.RegisterAll(this);
```

---

## Send a message

```csharp
WeakReferenceMessenger.Default.Send(new ThemeChangedMessage(AppTheme.Dark));

// Empty payloads use the parameterless overload:
WeakReferenceMessenger.Default.Send<RefreshRequestedMessage>();
```

---

## Channels (tokens)

Scope messages to a sub-system or window with a token (any equatable
value — `int`, `string`, `Guid`):

```csharp
const int LeftPaneChannel = 1;

WeakReferenceMessenger.Default.Register<MyViewModel, RefreshRequestedMessage, int>(
    this, LeftPaneChannel,
    static (r, _) => r.RefreshLeft());

WeakReferenceMessenger.Default.Send(new RefreshRequestedMessage(), LeftPaneChannel);
```

Messages sent without a token use the default shared channel — they are
**not** delivered to channel-scoped recipients.

---

## Request / reply

For ask-style scenarios where a recipient provides a value back to the
sender, use the `RequestMessage<T>` family.

### Sync request

```csharp
public sealed class CurrentUserRequest : RequestMessage<User> { }

WeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>(
    this,
    static (r, m) => m.Reply(r.CurrentUser));

User user = WeakReferenceMessenger.Default.Send<CurrentUserRequest>();
```

The implicit conversion from `CurrentUserRequest` to `User` throws if no
recipient called `Reply`. Capture the message to check first:

```csharp
var request = WeakReferenceMessenger.Default.Send<CurrentUserRequest>();
if (request.HasReceivedResponse)
    User user = request.Response;
```

### Async request

```csharp
public sealed class CurrentUserRequest : AsyncRequestMessage<User> { }

WeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>(
    this,
    static (r, m) => m.Reply(r.GetCurrentUserAsync()));

User user = await WeakReferenceMessenger.Default.Send<CurrentUserRequest>();
```

### Collection requests (fan-in)

`CollectionRequestMessage<T>` and `AsyncCollectionRequestMessage<T>` collect
a `Reply` from every responding recipient:

```csharp
public sealed class OpenDocumentsRequest : CollectionRequestMessage<Document> { }

var docs = WeakReferenceMessenger.Default.Send<OpenDocumentsRequest>();
foreach (Document doc in docs) { /* ... */ }
```

---

## Lifecycle

Even with `WeakReferenceMessenger`, unregister explicitly when a recipient
is being torn down — it trims dead entries and improves performance:

```csharp
WeakReferenceMessenger.Default.Unregister<ThemeChangedMessage>(this);
WeakReferenceMessenger.Default.Unregister<ThemeChangedMessage, int>(this, LeftPaneChannel);
WeakReferenceMessenger.Default.UnregisterAll(this);
```

`ObservableRecipient.OnDeactivated()` does this automatically when
`IsActive` flips to `false`. Set it from your activation hook:

```csharp
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    ViewModel.IsActive = true;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    ViewModel.IsActive = false;
    base.OnNavigatedFrom(e);
}
```

---

## Common pitfalls

1. **Capturing `this` in the lambda.** `(r, m) => OnX(m)` implicitly
   captures `this`; allocates a closure and confuses lifetime. Always use
   `(r, m) => r.OnX(m)` with `static`.
2. **Strong-ref recipients without `Unregister`.** With
   `StrongReferenceMessenger`, recipients (and their entire object graph)
   stay pinned forever. Either inherit from `ObservableRecipient`
   (auto-unregisters in `OnDeactivated`) or call `UnregisterAll(this)`.
3. **Inherited message types.** A handler registered for `BaseMessage` is
   **not** invoked for `DerivedMessage : BaseMessage`. Register each
   concrete type.
4. **Wrong messenger instance.** Sending via `WeakReferenceMessenger.Default`
   and registering via an injected per-window messenger means the message
   never arrives. Use the same `IMessenger` everywhere (typically inject
   it via `ObservableRecipient(messenger)`).
5. **`OnActivated` never runs.** `ObservableRecipient` only registers
   `IRecipient<T>` handlers when `IsActive` flips from `false` to `true`.
6. **Cross-thread updates.** The messenger is thread-agnostic. If a
   handler updates UI, marshal manually
   (`DispatcherQueue.TryEnqueue` / `Dispatcher.BeginInvoke`).

---

## Multiple messengers (per-window scoping)

```csharp
services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default); // app-wide
services.AddScoped<WindowScopedMessenger>();                       // per-window
```

Inject the appropriate `IMessenger` into the ViewModel constructor:

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

This isolates broadcasts to a single window — useful for multi-window
desktop apps (WinUI 3, WPF, MAUI desktop, Avalonia).

---

## References

| Topic | File |
|-------|------|
| Full deep dive (more channel/lifecycle examples, diagnostics) | [`references/messenger-patterns.md`](references/messenger-patterns.md) |

External:

- Messenger docs: <https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger>
- `WeakReferenceMessenger` API: <https://learn.microsoft.com/en-us/dotnet/api/communitytoolkit.mvvm.messaging.weakreferencemessenger>
- Source: <https://github.com/CommunityToolkit/dotnet>

© github · MIT · github/awesome-copilot

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

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

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