Skip to content

Подводные камни Store #4: .NET Trimming обрезал привязки WinUI 3

2026-05-31

Tags: Windows · Microsoft Store · Подводные камни Store


Эта проблема возникла ещё на раннем этапе. Недавно, пока писал серию про подводные камни, наткнулся на старое письмо с отказом — надеюсь, эта запись поможет разработчикам, которые только начинают разбираться с процессом публикации приложений WinUI 3 / .NET.

Что произошло

Моё приложение WinUI 3 было отправлено на сертификацию в Microsoft Store. Отчёт о сертификации вернулся со следующим:

Status: Attention needed

10.1.2.10 Functionality

Unusable Feature: "Download to local cache", "Open File" - The product fails to download copied items to local cache and "Open File" button in the product is not usable

Подождите — я же тестировал локально, и «Скачать в локальный кэш» и «Открыть файл» работали нормально. Почему они не работали на тестовой машине Microsoft?

Расследование

Самое подозрительное отличие: локально я использовал сборку Debug, а в Store отправлялась сборка Release. Проверив файл .csproj, я обнаружил ключевое различие между конфигурациями Debug и Release:

xml
<!-- Debug -->
<PublishTrimmed>False</PublishTrimmed>

<!-- Release -->
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>

В сборке Release был включён .NET Trimming.

Что такое .NET Trimming

.NET Trimming — это функция оптимизации в SDK .NET. При публикации приложения триммер выполняет статический анализ, чтобы найти сборки, типы и методы, на которые нет прямых ссылок в коде, и удаляет их для уменьшения размера приложения.

Проблема в том, что триммер видит только статические ссылки. Если ваш код вызывает тип через рефлексию во время выполнения, а при компиляции нет никаких статических ссылок на него, триммер считает, что тип «не используется», и удаляет его.

Почему WinUI 3 особенно уязвим

WinUI 3 и его уровень взаимодействия WinRT в значительной степени полагаются на рефлексию и динамические возможности. Традиционные {Binding}, конвертеры значений и рефлексия метаданных взаимодействия WinRT — всё это создаёт вызовы времени выполнения, которые статический анализ триммера не может отследить. Когда триммер удаляет модели данных, команды, конвертеры и другие типы, необходимые для привязки, считая их «неиспользуемыми», привязки не могут найти свои цели во время выполнения — кнопки перестают работать, текст перестаёт обновляться. Без ошибок, без падений — просто перестаёт работать.

Решение

Самый простой способ: отключить тримминг в .csproj:

diff
- <PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
+ <PublishTrimmed>False</PublishTrimmed>

Пересобрал и отправил заново — сертификация пройдена.

Если вам действительно нужен тримминг для уменьшения размера пакета, можно обеспечить совместимость следующими способами:

  • Пометить нужные типы атрибутами [DynamicDependency] или [DynamicallyAccessedMembers], указав триммеру сохранить их
  • Настроить <TrimmerRootAssembly> в .csproj для сохранения целой сборки

Но для большинства приложений WinUI 3 прямое отключение тримминга — самый простой вариант.

Итоги

  • .NET Trimming удаляет код, который он считает «неиспользуемым», но не распознаёт вызовы через рефлексию
  • Привязка данных WinUI 3 зависит от рефлексии и легко может быть обрезана по ошибке
  • Если приложение WinUI 3 не проходит сертификацию Store с сообщением о «неработоспособной функции», но локально работает нормально, первое, что нужно проверить — это тримминг
  • Сборки Debug по умолчанию не используют тримминг, а сборки Release могут его включать — всегда тестируйте сборки Release/Publish перед отправкой в Store
  • В ближайшее время мы опубликуем статью с советами по тестированию перед отправкой в Store — следите за обновлениями

Эта статья входит в серию Подводные камни Store.