Чек-лист перед миграцией с .NET Framework на .NET

· Обновлено: · · .NET, .NET Framework, C#, Модернизация, Разработка Windows, Миграция

История изменений (3 обновлений, последнее 31 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст исправлен, чтобы соответствовать японскому оригиналу.
Восстановлены разделы 9–15 полного перевода. Утверждения статьи не менялись.
Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619691)

Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.

Го Комура (2026). Чек-лист перед миграцией с .NET Framework на .NET. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619691 https://comcomponent.com/ru/blog/2026/03/15/003-dotnet-framework-to-dotnet-premigration-checklist/

DOI (последняя версия)
10.5281/zenodo.21619691
DOI (эта версия)
10.5281/zenodo.21619692

Скачать Excel-чек-лист с листами на японском и английском

Файл состоит из двух листов, Checklist-ja и Checklist-en. Чек-лист до старта из 13 глав сведён в 7 категорий и 34 пункта: политика / подготовка текущей стороны .NET Framework / тип приложения и выбор технологий / неподдерживаемые технологии и API, которые стоит проверить / зависимости, специфичные для Windows / общие библиотеки и доступ к данным / эксплуатация и build. Столбцы Status и Notes пустые, поэтому таблицу можно сразу использовать как инвентаризацию по проекту. Если просмотреть её до чтения статьи, ход до 13-й главы будет понятнее.

Сменить TargetFramework в .csproj на net10.0, обновить несколько пакетов NuGet — и если сборка прошла, всё готово.

…если бы миграция сводилась к этому, жить было бы спокойнее. На практике чаще получается иначе.

В реальных проектах на .NET Framework обычно дремлет немало допущений, о которых в обычной работе не думают: System.Web, WCF, Web Forms, старый packages.config, web.config.install.xdt, нативные DLL, COM / ActiveX, сторонние контролы, которые живут только в дизайнере, неявная ставка на x86, ResX, завязанный на дизайнер, старые сериализаторы.

Поэтому в миграции с .NET Framework на .NET по-настоящему важно не начать писать код, а сначала провести инвентаризацию. Если до старта разложить вопросы, миграция перестаёт быть «большой ставкой» и становится «последовательной работой по пунктам».

Инвентаризация меняет характер миграцииЕсли до реализации провести инвентаризацию и разложить вопросы, миграция становится не большой ставкой, а последовательной работой по пунктам.без инвентаризацииСкрытые неявные допущенияИнвентаризация до старта раскладывает вопросыПоследовательная работа по пунктамМиграция становится большой ставкой

Рис. 1: В зависимости от того, разложены ли вопросы до старта, миграция оказывается ставкой или обычной работой.

В этой статье собрано, что проверить перед миграцией существующего бизнес-приложения на .NET Framework 4.x на текущий .NET. Основные объекты примерно такие:

  • библиотеки классов
  • консольные приложения
  • службы Windows
  • WinForms / WPF
  • ASP.NET Framework (MVC / Web API / Web Forms)
  • приложения на WCF
  • приложения на EF6

Текст написан по состоянию на 2026-03-15. Сроки поддержки и рекомендации официальных инструментов меняются, поэтому если вы читаете статью заметно позже, сверьтесь и с официальными источниками.

1. Сначала вывод

Сначала только выводы, от которых трудно уйти.

  • До миграции сначала наведите порядок на стороне .NET Framework. Официальное руководство Microsoft тоже рекомендует перед портированием поднять цель до .NET Framework 4.7.2 или новее, заранее перейти на PackageReference, на SDK-стиль и обновить зависимости.
  • Сложность задаёт скорее модель приложения, чем объём кода. Библиотеки классов и консоль относительно лёгкие; ASP.NET Framework, Web Forms, WCF-сервер и WF (Workflow Foundation) обычно тяжелее.
  • WinForms / WPF можно перенести на .NET, но они остаются Windows-only. Если это перепутать, получится классическая мина: перенос сделан, а в Linux-контейнер приложение так и не встаёт.
  • Переход ASP.NET Framework → ASP.NET Core по сути миграция архитектуры. Небольшое приложение иногда можно перенести одним махом, но для крупного продакшена безопаснее сразу рассчитывать на поэтапную миграцию.
  • WCF и EF6 иногда можно отделить от миграции runtime. Для WCF-клиента есть поддерживаемые пакеты под .NET, а EF6 после перехода на современный .NET можно отдельно мигрировать на EF Core.
  • С другой стороны, создание AppDomain, .NET Remoting, CAS (Code Access Security), COM+, Workflow Foundation и зависимость от BinaryFormatter — красные флаги. Если не найти их заранее, трудозатраты взорвутся позже.
  • packages.config / install.ps1 / XDT (XML Document Transform, механизм преобразования web.config и подобных файлов) / содержимое content / нативные DLL / COM / ActiveX / ставка на x86` часто проходят сборку и падают уже в runtime или в дизайнере, поэтому их нужно выявить до старта.
  • На март 2026 LTS — .NET 10. Для новой миграции естественно ориентироваться на текущую LTS.
  • Миграция без тестов, замеров и отката опасна. Это не столько работа по реализации, сколько работа, в которой скрытые допущения выявляют по одному.

Карта знаний этой статьи

Миграция с .NET Framework на .NET требует инвентаризации до начала реализации; преобразование в PackageReference — преемник packages.config — и в проект в стиле SDK следует закончить до самой миграции. Создание нового AppDomain, .NET Remoting, COM+ и Workflow Foundation с .NET не совместимы; BinaryFormatter тоже может быть несовместим — в зависимости от ситуации. У WCF клиент и сервер различаются: на стороне сервера нужна перепроектировка на CoreWCF или gRPC. ASP.NET Core — преемник ASP.NET Framework, но Web Forms — это не та же модель приложения (app model); переход с EF6 на EF Core безопаснее отделить от миграции runtime и отложить. WinForms/WPF перенести можно, но они остаются Windows-only; зависимость от Windows-only API можно снизить пакетом Windows Compatibility Pack.

Карта знаний: контрольный список перед миграцией с .NET Framework на .NETСхема связей между форматом зависимостей, несовместимыми технологиями, обращением с WCF и EF6 и зависимостью от Windows-only API — тем, что нужно разобрать до миграции с .NET Framework на .NETтребуетпреемникиспользуетпреемникдолжен предшествоватьдолжен предшествоватьне рекомендуетсярекомендуется дляиспользуетнесовместимо снесовместимо снесовместимо снесовместимо снесовместимо стребуеттребуетреализуетпреемникдолжен предшествоватьпреемникнесовместимо снесовместимо сиспользуетиспользуетиспользуетиспользуетснижаетиспользуеттребуеттребуетмиграция с .NET Framework на .NET.NET Framework.NET (начиная с Core)Windows Compatibility PackPackageReferencepackages.configпроект SDK-style.NET Upgrade Assistantмодернизация с GitHub Copilot.NET Standard 2.0создание AppDomain.NET RemotingCOM+ (System.EnterpriseServices)Workflow FoundationBinaryFormatterсервер WCF (хост службы WCF)CoreWCFgRPCEF CoreEF6 (Entity Framework 6)ASP.NET CoreASP.NET Framework (MVC/Web API)ASP.NET Web FormsSystem.Web / HttpContext.CurrentWPFWindows Formsзависимость от Windows-only APIклиент WCF

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 30, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

2. Сначала решите, нужно ли мигрировать именно сейчас

Первое решение — не «как мигрировать», а действительно ли это приложение нужно мигрировать сейчас.

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

Вопрос, который решают первымПрежде чем выбирать способ миграции, нужно решить, действительно ли приложение мигрировать сейчас; иначе легко уйти в слишком тяжёлую миграцию или в чрезмерную отсрочку.решить сначалаесли оставить размытымДействительно ли мигрировать сейчасКак мигрироватьСлишком тяжёлая миграция или чрезмерная отсрочка

Рис. 2: Если не решить «мигрировать ли сейчас» до «как мигрировать», оценка качается в одну из сторон.

2.1 Остаться на .NET Framework — тоже обычное решение

.NET Framework 4.8.1 поддерживается, пока стоит на поддерживаемой Windows. То есть это не история вида «если прямо сейчас не перевести всё на современный .NET, сразу опасно».

Но у решения остаться есть жёсткие ограничения.

  • из Windows не выйти
  • придётся дальше держать ASP.NET Web Forms и старый серверный стек
  • выгоду от новой производительности, языковых возможностей и экосистемы .NET получить труднее
  • легче разойтись с нынешними допущениями облака, контейнеров и CI/CD

Наоборот, если зависимость такого рода сильная, разумно пока стабильно эксплуатировать .NET Framework 4.8.1 и параллельно строить план замены на отдельном треке.

Остаться — тоже рациональный выборПри сильной зависимости от Web Forms, совместимости WCF-сервера, COM+ и подобного разумный путь — пока стабильно работать на .NET Framework 4.8.1 и отдельно планировать замену.Сильная зависимость от legacyПока стабильно держать 4.8.1План замены на отдельном трекеОграничения вроде Windows-only остаются

Рис. 3: Если зависимость глубокая, реалистичны две линии сразу: стабильная эксплуатация на 4.8.1 и план замены.

  • очень много экранных наработок на Web Forms
  • нужно жёстко сохранить совместимость WCF-сервера
  • глубоко завязаны Workflow Foundation или COM+
  • design-time компоненты третьих сторон не работают на современном .NET
  • бизнес не допускает крупных изменений спецификации

2.2 Что меняется в зависимости от выбора

Выбор Что улучшается Что остаётся / теряется Когда уместно
Остаться на .NET Framework 4.8.1 Легче не ломать существующий код и стабильно эксплуатировать Windows-only, старая модель приложения, потолок модернизации Сильная зависимость от legacy, сейчас приоритет бизнеса — стабильная эксплуатация
Перейти на современный .NET, оставаясь на Windows Можно модернизировать runtime и инструментарий. Большая выгода от производительности, опыта разработки и SDK-стиля Зависимость от Windows API остаётся. Кроссплатформенности не будет Бизнес-приложения на WinForms / WPF, службах Windows, с Windows API
Перейти на современный .NET с прицелом на Linux / контейнеры / облако Больше свободы в месте развёртывания. Легче обновить и модель эксплуатации Windows-only API и app model нужно снять заранее Хотите сдвинуть серверную часть в облако и обновить инфраструктуру

Важно заранее решить не «хотим ли мигрировать», а куда нужно приземлиться после миграции.

Решают целевую точку посадкиВажно заранее решить не «хотим ли мигрировать», а куда нужно приземлиться после миграции.вопроса недостаточнорешить этоХотим ли мигрироватьЧто решить сначалаКуда приземлиться после миграцииПоявляются варианты и вопросы, на которые смотреть

Рис. 4: Если вопрос сменить с «хотим ли мигрировать» на «куда приземлиться», варианты начинают сходиться.

3. Четыре решения, которые стоит принять заранее

3.1 Целевая версия .NET

На момент написания, по политике поддержки Microsoft, LTS — .NET 10. При этом и .NET 8 LTS, и .NET 9 STS планируют снять с поддержки 2026-11-10. STS заканчивается в ту же дату, что LTS, потому что срок поддержки STS продлили с 18 до 24 месяцев. Если считать по старому правилу «STS — 18 месяцев», .NET 9 выглядит как окончание 2026-05-12, поэтому когда решение опирается на дату, сверяйтесь с официальной информацией о жизненном цикле.

Поэтому если вы только начинаете миграцию с .NET Framework, при отсутствии особых причин естественно целиться в текущую LTS.

Практическая логика здесь простая.

  • Небольшая миграция, которую хотят закрыть быстро — сразу на текущую LTS
  • Система, рассчитанная на долгую эксплуатацию, тоже по умолчанию на текущую LTS
  • «Из-за существующей библиотеки хотим предыдущую LTS» как причина бывает, но решение принимают, глядя на дату окончания поддержки
Как выбирать целевую версиюИ для небольшой миграции, и для основной системы базой служит текущая LTS; предыдущую LTS рассматривают из-за существующих библиотек и только после проверки даты окончания поддержки.по умолчаниюиз-за существующих библиотекВыбрать целевой .NETПриземлиться на текущую LTSРассмотреть предыдущую LTSРешить по дате окончания поддержки

Рис. 5: Целевая версия по умолчанию — текущая LTS; старую LTS выбирают только с датой поддержки в руках.

3.2 Остаться Windows-only или целиться в кроссплатформенность

От этого решения сильно меняется, на что смотреть.

  • Если остаётесь на Windows, реалистичный путь — модернизировать runtime через WPF / WinForms и Windows Compatibility Pack.
  • Если в будущем нужны Linux и контейнеры, на раннем этапе инвентаризируйте Windows-only API: System.Drawing.Common, реестр, WMI, EventLog, службы Windows, COM, Office Interop.

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

Развилка Windows-only или кроссплатформаЕсли остаётесь на Windows, реалистичный путь — сначала модернизировать runtime через compatibility pack; если целитесь в Linux и контейнеры, Windows-only API нужно инвентаризировать рано.остаёмся на Windowsв будущем и контейнерыначать, не решивКуда целимсяМодернизировать runtime через compatibility packРано инвентаризировать Windows-only APIПосередине разговор скручивается

Рис. 6: Если эту развилку не закрыть заранее, не ясно, на что смотреть, и разговор скручивается.

3.3 Одним махом или поэтапно

Форм миграции в целом три.

  • одномоментная миграция, близкая к in-place
  • side-by-side, когда старое и новое стоят рядом
  • поэтапная миграция по route / library

Для приложений на ASP.NET Framework руководство Microsoft явно указывает incremental migration. Если продакшен останавливать нельзя, функций много и вокруг много зависимостей, с самого начала закладывать поэтапную миграцию обычно честнее.

Три формы миграцииЕсть одномоментная миграция, близкая к in-place, side-by-side сосуществование и поэтапный перенос по route или library; если продакшен нельзя останавливать, разумная база — поэтапная миграция.Выбрать форму миграцииОдномоментная (ближе к in-place)side-by-side, старое и новое рядомПоэтапно по route / libraryЕсли продакшен нельзя останавливать, это база

Рис. 7: Форм три; для продакшена с большим числом функций, который нельзя останавливать, база — поэтапная миграция.

3.4 Что «вынести из текущего объёма миграции»

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

Например, одновременное выполнение всего перечисленного обычно тяжело.

  • .NET Framework → .NET
  • ASP.NET Framework → ASP.NET Core
  • EF6 → EF Core
  • Windows-серверы → Linux-контейнеры
  • смена платформы аутентификации
  • смена платформы логирования / мониторинга
  • миграция базы данных

Когда-нибудь всё это может понадобиться. Но нужно ли делать это одновременно — другой вопрос.

На практике лучше разделять так.

  1. Сначала модернизировать runtime и структуру проекта
  2. Затем перенести app model
  3. В конце обновить ORM, аутентификацию, облако и мониторинг
Порядок разделения без перегрузаСначала модернизируют runtime и структуру проекта, затем переносят app model, в конце обновляют ORM, аутентификацию, облако и мониторинг.если делать всё сразуМодернизировать runtime и структуруПеренести app modelОбновить ORM, аутентификацию, облако, мониторингМиграция тяжелеет и чаще проваливается

Рис. 8: Если не делать всё сразу, а разделить runtime, app model и окружение, объём не раздувается.

4. База, которую стоит подготовить до старта

Руководство Microsoft по подготовке к портированию довольно практичное. Коротко: перед миграцией текущий проект на .NET Framework нужно сдвинуть ближе к современному входу.

4.1 Поднять цель до .NET Framework 4.7.2 или новее

Официальное руководство рекомендует перед портированием таргетировать .NET Framework 4.7.2 или новее. Причина в том, что даже когда .NET Standard не содержит существующий API как есть, проще опереться на более новую замену API.

На практике, если можно, понятнее ориентироваться на 4.8.1.

  • проще с точки зрения поддержки
  • удобно считать финальной стабильной точкой на стороне .NET Framework
  • легче выстроить политику «сначала навести порядок на текущем Framework»

Что меняется, если сделать это заранее

  • проще и стабильнее работать с общими библиотеками на .NET Standard 2.0
  • заранее снижается шум от старого runtime
  • проще понять, проблема из-за возраста Framework или из-за перехода на современный .NET

4.2 Сдвинуться к PackageReference

Руководство по подготовке к портированию рекомендует перевести ссылки в формат PackageReference. Если сделать это заранее, управление зависимостями становится заметно прозрачнее.

Что меняется при PackageReference

  • ссылки на пакеты собираются в csproj
  • транзитивные зависимости становятся обозримее
  • допущения restore выравниваются со стороной современного .NET
  • лучше стыковка с CLI / CI

Но здесь есть мины.

Типичные мины

В официальной документации NuGet явно указаны такие ограничения перехода с packages.config на PackageReference.

  • встроенная миграция Visual Studio недоступна для проектов ASP.NET
  • пакеты, которые зависят от install.ps1 / uninstall.ps1, могут работать не так, как ожидается
  • содержимое папки content иногда игнорируется
  • XDT-преобразования вроде web.config.install.xdt не применяются
  • пакеты со старой раскладкой сборок прямо под lib иногда разрешаются некорректно

То есть это не «просто смена формата пакетов». В классическом ASP.NET при установке NuGet-пакета часто переписывали web.config, поэтому при миграции скрытые допущения легко всплывают.

Неявные допущения, которые всплывают при PackageReferenceПри переходе с packages.config на PackageReference install.ps1, XDT-преобразования и содержимое content могут не примениться, и скрытые допущения установки web.config всплывают.Преобразование в PackageReferenceinstall.ps1 иногда не выполняетсяXDT-преобразования не применяютсяСодержимое content иногда игнорируетсяВсплывают скрытые допущения

Рис. 9: Даже если казалось, что меняется только формат, допущения, завязанные на установку, всплывают разом.

Чем преобразовывать

Не только руками. Но у инструментов разная зона покрытия.

Инструмент Что умеет Допущения и оговорки
Преобразование в Visual Studio В обозревателе решений щёлкните правой кнопкой «Ссылки» или packages.config и выберите Migrate packages.config to PackageReference... Visual Studio 2017 15.7 или новее. Не работает для проектов ASP.NET и C++. Если пункта нет в меню, сначала восстановите NuGet или откройте диспетчер пакетов, затем щёлкните правой кнопкой снова
.NET Upgrade Assistant Анализирует проект и проводит обновление пакетом В официальной документации уже не рекомендуется; направляют на модернизацию через GitHub Copilot
Модернизация через GitHub Copilot Помогает с оценкой, планом, правкой кода и проверкой. Центр текущих официальных рекомендаций Как в 4.5, нужны Visual Studio, Copilot и код на C#
Ручной переход на SDK-стиль Создаёте новый csproj и переносите только нужные элементы В проекте с небольшим числом файлов это иногда самый быстрый путь

Перед запуском преобразование Visual Studio делает резервную копию проекта и в конце показывает отчёт (зависимости верхнего уровня, транзитивные зависимости, найденные проблемы совместимости). Если нужно откатиться, документация описывает такой путь: вернуть csproj и packages.config из папки резервной копии и в консоли диспетчера пакетов выполнить update-package -reinstall.

То есть попробовать можно так, чтобы было куда вернуться. Для оценки до старта быстрее всего преобразовать один проект и прочитать отчёт.

Пробовать преобразование так, чтобы можно было откатитьсяПреобразование Visual Studio сначала делает резервную копию и затем показывает отчёт, поэтому достаточно преобразовать один проект, прочитать отчёт и при необходимости вернуть файлы из копии.если есть проблемыесли проблем нетСоздаётся резервная копияПреобразовать один проектПрочитать отчёт преобразованияВернуть файлы и переустановить пакетыРасширить на другие проекты

Рис. 10: Преобразование можно пробовать с копией и отчётом, поэтому сначала берут один проект.

4.3 Сдвинуться к SDK-стилю

Руководство по подготовке к портированию также рекомендует переход на формат проекта в SDK-стиле.

Это даёт заметный эффект.

Что меняется при SDK-стиле

  • csproj становится намного короче
  • хорошо сочетается с PackageReference
  • проще multi-targeting
  • легче выстроить CI/CD вокруг dotnet build / dotnet test / dotnet publish
  • конфигурация ближе к стороне современного .NET, поэтому дальше разница меньше

Иначе говоря, если прыгнуть на современный .NET со старым csproj и старым управлением NuGet, разница окажется слишком большой.

SDK-стиль уменьшает разницуПрыжок на современный .NET со старым csproj и старым NuGet даёт слишком большую разницу, поэтому сначала сдвигаются к SDK-стилю и приближают конфигурацию к современной стороне.прыгнуть сразусначала SDK-стильСтарый csproj + старое управление NuGetРазница слишком большаяКонфигурация ближе к современной сторонеДальше разница меньше

Рис. 11: Если вставить SDK-стиль между шагами, прыжок на современный .NET становится меньше.

4.4 Сначала обновить зависимости

Это тоже по официальному руководству: зависимости сдвигают к последним доступным версиям, а если можно — к версиям с поддержкой .NET Standard.

Зачем делать это заранее

  • раньше становится ясно, «живёт ли этот пакет на современном .NET»
  • старые зависимости не превращаются в шум
  • проще перевести общие библиотеки на netstandard2.0
  • на следующем этапе проще сосредоточиться именно на портировании кода
Зачем обновлять зависимости заранееЕсли сначала сдвинуть зависимости ближе к актуальным, раньше видно, работают ли они на современном .NET, меньше шума от старых пакетов, и дальше можно сосредоточиться на портировании кода.Сначала обновить зависимостиРаньше видно поддержку современного .NETМеньше шума от старых зависимостейДальше можно сосредоточиться на портировании кода

Рис. 12: Чем раньше закрыть обновление зависимостей, тем больше сама миграция сводится к портированию.

4.5 Проверить и допущения официальных инструментов

На март 2026 центр рекомендаций Microsoft сместился к модернизации через GitHub Copilot. На практике удобнее смотреть на это не как на отдельные старые инструменты миграции, а как на единый поток помощи: оценка, план, правка кода, проверка.

Но текущая документация исходит из Visual Studio 2026 или поддерживаемой линейки Visual Studio 2022, GitHub Copilot и кода на C#.

Зачем это проверять

  • меняется то, чего можно ждать от официальных инструментов
  • можно выровнять допущения команды об IDE / build agent / расширениях
  • можно сразу решить не ждать слишком много от автоматизации в решениях на VB.NET

Проекты с примесью VB.NET не редкость. Поэтому стоит заранее понять, насколько далеко помогут текущие официальные инструменты.

Где сейчас официальные инструменты и какие у них допущения.NET Upgrade Assistant уже не рекомендуется, центр рекомендаций сместился к модернизации через GitHub Copilot, но нужны Visual Studio, Copilot и C#, поэтому в решениях на VB.NET автоматизацию не стоит переоценивать.не рекомендуется, направляют наесли есть VB.NET.NET Upgrade AssistantМодернизация через GitHub CopilotДопущения: Visual Studio + Copilot + C#Не ждать слишком много от автоматизации

Рис. 13: Центр инструментов сместился к Copilot-модернизации; если допущения не совпадают с площадкой, нужна отдельная подготовка.

5. Оценить сложность по типу проекта

О миграции часто говорят одной фразой «с .NET Framework на .NET», но на практике каждый тип проекта — отдельная игра.

5.1 Приблизительное ощущение сложности

Тип Ощущение сложности Основные вопросы
Библиотека классов Низкая—средняя Совместимость API, зависимости, разделение целевых платформ
Консоль / batch / часть служб Windows Низкая—средняя Способ распространения, нативные зависимости, конфигурация
WinForms / WPF Средняя Остаётся Windows-only, дизайнер, сторонний UI, всё вокруг BinaryFormatter
ASP.NET MVC / Web API Средняя—высокая Миграция app model на ASP.NET Core, аутентификация, сессии, конфигурация, DI
ASP.NET Web Forms Высокая Большая разница в модели экрана, предполагается замена слоя UI
Клиент WCF Средняя Замена пакетов, контракты, конфигурация
Сервер WCF Высокая CoreWCF или редизайн на gRPC / HTTP API
Одновременно EF6 → EF Core Высокая ORM — другая вещь, отличия в поведении, история миграций

5.2 Для библиотек классов ключ — как провести «границу общего»

Библиотеки классов переносить относительно легко. Но только когда библиотека действительно отделена так, как полагается библиотеке.

Сложность растёт при таких зависимостях:

  • обращение к System.Web
  • прямой взгляд в HttpContext.Current
  • типы WPF / WinForms в публичном API
  • слишком сильная опора на Windows API вроде реестра, WMI, EventLog
  • зависимость от AppDomain или Remoting

Проще смотреть так: если удаётся вынести только бизнес-логику — легко; если библиотека ещё и держит app model — тяжело.

Как смотреть на тяжесть библиотеки классовБиблиотека классов, из которой можно вынести только бизнес-логику, лёгкая; если она держит System.Web, UI-типы, Windows API, AppDomain и прочий app model, миграция тяжелеет.логику можно вынести отдельновнутри ещё и app modelСмотрим библиотеку классовМиграция лёгкаяМиграция тяжёлаяSystem.Web, UI-типы, Windows API и т. п.

Рис. 14: Сложность библиотеки задаёт не число строк, а то, сколько app model она в себе держит.

5.3 WinForms / WPF мигрируют относительно легко, но остаются Windows-only

WinForms и WPF можно перенести на .NET. Но оба остаются Windows-only фреймворками.

Если здесь ошибиться в ожиданиях, будет плохо.

  • Что улучшится
    • можно сесть на runtime, язык и SDK-стиль современного .NET
    • легче приблизить CI/CD и управление пакетами к текущей практике
    • проще получить отдельные улучшения производительности и сопровождения
  • Что не изменится
    • приложение останется Windows-only
    • останутся проблемы совместимости UI-контролов и design-time компонентов
    • проблемы ActiveX / COM / нативных DLL никуда не денутся

Кроме того, в WinForms / WPF иногда нужно проверить влияние BinaryFormatter. Особенно если custom-типы задействованы в clipboard, drag & drop, ResX или сериализации на этапе дизайна, проблема легко всплывает при подъёме цели на .NET 9 или новее.

Ожидания от миграции WinForms / WPFWinForms и WPF можно перенести на .NET и сесть на runtime, язык и SDK-стиль современного .NET, но Windows-only природа не меняется, и иногда нужно проверить влияние BinaryFormatter.Переносим WinForms / WPFМожно сесть на выгоду современного .NETОстаются Windows-onlyНужно проверить BinaryFormatter

Рис. 15: Десктопный UI переносится, но свойство «только Windows» уезжает вместе с ним.

5.4 ASP.NET Framework — это миграция app model, а не runtime

Переход с ASP.NET Framework на ASP.NET Core в руководстве Microsoft прямо назван non-trivial. Дело не просто в смене имён API, а в том, что меняется лежащая в основе архитектура.

Различия чаще всего проявляются здесь:

  • Hosting model
  • Middleware pipeline
  • Request processing model
  • Session / Cache
  • Authentication / Authorization
  • Configuration
  • Dependency Injection
  • Logging / Monitoring

Для приложения на ASP.NET Framework сначала стоит проверить следующее.

  • с каких route / endpoint можно начать перенос
  • можно ли снять зависимость от System.Web с общих библиотек
  • как выровнять аутентификацию / сессии / обработку исключений / логирование
  • будет ли поэтапная миграция без остановки продакшена

Для крупных приложений реалистичнее с самого начала закладываться на incremental migration.

Миграция ASP.NET — это миграция app modelПереход с ASP.NET Framework на ASP.NET Core — не замена имён API, а смена архитектуры hosting, middleware, аутентификации, конфигурации и DI; для крупных приложений реалистичная база — поэтапная миграция.крупное приложениеASP.NET Framework → CoreМеняется лежащая в основе архитектураhosting, middleware, аутентификация, конфигурация, DIПланировать как incremental migration

Рис. 16: Миграция ASP.NET — это не runtime, а app model; чем больше масштаб, тем сильнее нужна поэтапность.

5.5 С Web Forms начинают не с «переноса экранов», а с разделения ответственности

Web Forms — это не тот же app model, что у ASP.NET Core. Поэтому при оценке безопаснее не исходить из того, что экранные наработки уедут как есть.

На практике чаще начинают с такого разделения.

  • отделить логику экрана от бизнес-логики
  • разложить ответственность, зашитую в Page / UserControl / ViewState
  • вынести бизнес-логику и доступ к данным в общую библиотеку
  • UI собрать заново на другой модели: Razor Pages / MVC / Blazor

То есть в проекте на Web Forms важнее не план миграции runtime, а есть ли план разделения ответственности.

Web Forms начинают с разделения ответственностиWeb Forms — не тот же app model, что ASP.NET Core, поэтому начинают с отделения логики экрана от бизнес-логики, выноса логики в общую библиотеку и сборки UI на другой модели.если считать, что уедет как естьЭкранные наработки Web FormsОтделить логику экрана от бизнес-логикиВынести логику в общую библиотекуСобрать UI на другой моделиОценка разваливается

Рис. 17: Web Forms смотрят не как перенос активов, а как разделение ответственности и сборку на другой модели.

5.6 Клиента WCF и сервер WCF оценивают отдельно

Их не стоит смешивать.

Клиент WCF

Для WCF Client есть поддерживаемые NuGet-пакеты под современный .NET. Поэтому если вы только вызываете WCF, задача иногда оказывается не такой тяжёлой, как кажется.

Сервер WCF

Сторона, которая хостит службу WCF, — другое дело. В руководстве Microsoft в основном два пути модернизации.

  • CoreWCF, чтобы сохранить совместимость с существующими клиентами
  • сдвиг к современному RPC / HTTP вроде gRPC

Но CoreWCF не приносит весь WCF как есть, это subset. Для совместимости с существующими клиентами он подходит, но изменения кода и тесты обязательны.

WCF оценивают отдельно на клиенте и на сервереУ WCF-клиента есть поддерживаемые пакеты под современный .NET, и задача иногда легче, чем кажется; на сервере нужно выбрать CoreWCF для совместимости или редизайн на gRPC.клиентсерверсохранить совместимостьредизайнНа какой стороне используется WCFПеренос через поддерживаемый пакетВыбрать путьCoreWCF (subset)gRPC / HTTP API

Рис. 18: У вызывающей стороны и у хоста разные и сложность, и варианты, поэтому оценивать их вместе нельзя.

6. Выявить технологии, которых в .NET нет или которые стопорят «как есть»

Это нужно сделать до старта обязательно. У Microsoft есть перечень технологий, которые работали в .NET Framework, но недоступны в .NET 6+.

6.1 Технологии, которые часто становятся красным флагом

Технология Состояние в .NET Как к этому относиться
Создание AppDomain, например AppDomain.CreateDomain Не поддерживается Изоляцию думать через отдельный процесс / контейнер / AssemblyLoadContext
.NET Remoting Не поддерживается Перепроектировать на IPC, HTTP, gRPC, Socket, Pipe и т. п.
CAS / Security Transparency Не поддерживается как граница безопасности Думать через ОС / контейнер / разделение прав
System.EnterpriseServices (COM+) Не поддерживается Отделить и заменить дизайн, завязанный на COM+
Workflow Foundation Не поддерживается Считать отдельной оценкой, включая альтернативы вроде CoreWF
WCF server Из коробки не работает как есть Выбирать CoreWCF или gRPC
BinaryFormatter С .NET 9 реализация всегда бросает исключение Миграция сериализатора, аудит ResX / clipboard / drag & drop

6.2 AppDomain: «часть API осталась, но создание — отдельный вопрос»

Тема AppDomain немного запутанная. В .NET часть поверхности API сохранена, но использование в духе создать новый AppDomain и изолировать код не поддерживается.

Поэтому если AppDomain использовали для следующего, нужен редизайн.

  • изоляция плагинов
  • выгрузка динамически загруженного кода
  • изоляция кода с частичным доверием
  • временное отделение среды выполнения

Перед миграцией смотреть нужно не только на то, встречается ли слово AppDomain, но и на то, для чего его использовали.

Решение зависит от того, как использовали AppDomainДаже если часть API AppDomain осталась, создание нового AppDomain для изоляции не поддерживается, поэтому отличают простое использование типа от изоляции и при изоляции перепроектируют через отдельный процесс или AssemblyLoadContext.только тип или сведениясоздавали ради изоляцииВстретился AppDomainЧасто можно идти дальше как естьНужен редизайнОтдельный процесс / контейнер / AssemblyLoadContext

Рис. 19: Нужен ли редизайн, решает не наличие слова, а то, для чего механизм использовали.

6.3 Remoting глубже, чем кажется

Помимо самого Remoting, в зону влияния иногда попадают и вызовы BeginInvoke() / EndInvoke() асинхронных делегатов. Это не сам Remoting, но в современном .NET это не поддерживается, поэтому перед миграцией это тоже нужно выявить.

При поиске безопаснее сразу смотреть и это:

  • System.Runtime.Remoting
  • MarshalByRefObject
  • RealProxy
  • BeginInvoke( / EndInvoke(

6.4 BinaryFormatter внезапно выходит на первый план в зависимости от target version

Чем старше кодовая база, тем выше шанс, что BinaryFormatter используется «без осознания».

  • сохранённые данные
  • кэш
  • хранение сессий
  • состояние плагинов
  • clipboard / drag & drop
  • ResX
  • всё вокруг дизайнера WinForms / WPF

Начиная с .NET 9 реализации BinaryFormatter в runtime нет, и API всегда бросает PlatformNotSupportedException. То есть это не «подумаем позже», а вопрос, который нужно аудировать в момент выбора target version.

Как BinaryFormatter выходит на первый планBinaryFormatter часто используют неосознанно в сохранённых данных, ResX и clipboard, а с .NET 9 API всегда бросает исключение, поэтому аудит нужен уже в момент выбора target version.Неосознанное использование (ResX, clipboard и т. п.)Цель ставят на .NET 9 или новееAPI всегда бросает исключениеАудировать сразу в момент выбора

Рис. 20: BinaryFormatter начинает работать в момент выбора target, поэтому его не откладывают, а аудируют заранее.

6.5 Поисковые термины, которые стоит прогнать через grep в первую очередь

Ещё до старта достаточно поискать эти слова по всему решению — картина сильно меняется.

System.Web
HttpContext.Current
System.Runtime.Remoting
MarshalByRefObject
AppDomain
BinaryFormatter
ServiceHost
ChannelFactory
System.EnterpriseServices
Workflow
packages.config
web.config.install.xdt
install.ps1
DllImport
AxInterop
Microsoft.Office.Interop

Найти хотя бы одно из них не значит «сразу мимо». Это карта, которая показывает, что уедет по стандартному маршруту, а что окажется на отдельном треке.

Реальные команды поиска

Инструмент может быть rg (ripgrep) или PowerShell.

# PowerShell. Поиск по всему решению
Get-ChildItem -Recurse -Include *.cs,*.vb,*.config,*.csproj,*.vbproj |
    Select-String -Pattern 'BinaryFormatter|System\.Runtime\.Remoting|MarshalByRefObject|AppDomain\.CreateDomain' |
    Select-Object Path, LineNumber, Line
# ripgrep. Когда сначала нужны количества
rg -n --stats "BinaryFormatter|System\.Runtime\.Remoting|AppDomain\.CreateDomain" --glob "*.cs" --glob "*.vb"

Что делать, если нашли

Поиск — только вход, решение принимают после него. Для каждого термина следующее место проверки уже известно.

Найденный термин Что проверить дальше Куда это сходится
System.Web / HttpContext.Current Можно ли вынести из общей библиотеки. Можно ли на вызывающей стороне переложить в DTO Снимать по стратегии 8.5
System.Runtime.Remoting / MarshalByRefObject / BeginInvoke Для чего был удалённый вызов. Между процессами, между машинами или просто асинхронный вызов Редизайн на IPC, HTTP, gRPC, Task
AppDomain Только имя типа или изоляция через CreateDomain Если ради изоляции — отдельный процесс или AssemblyLoadContext (6.2)
BinaryFormatter Нужно ли потом читать записанные данные. Нет ли косвенного использования через ResX или clipboard Перейти на другой сериализатор и спланировать чтение старых данных (6.4)
ServiceHost / ChannelFactory Хост или клиент Оценивать отдельно, как в 5.6
packages.config / install.ps1 / web.config.install.xdt Что переписывалось при установке Мины из 4.2. Конфигурацию нужно явно забрать себе
DllImport / AxInterop / Microsoft.Office.Interop Допущения о разрядности и полный набор файлов, нужных в runtime Перейти к проверке bitness в 9.4

После этого получается не ответ «можно ли мигрировать», а список: что идёт стандартным маршрутом, а что — отдельной оценкой. Именно такой список и нужен до старта.

От поиска к спискуПоиск — только вход; если по каждому найденному термину пройти следующее место проверки, получается список того, что идёт стандартным маршрутом, а что требует отдельной оценки.Прогнать решение поисковыми терминамиПо каждому термину пройти следующее место проверкиПолучается список стандартного маршрута и отдельной оценкиНаходка сама по себе не значит «мимо»

Рис. 21: Поиск — вход; если дойти до решения, получается список, который нужен до старта.

7. Решить, насколько допустима привязка к Windows

Частое заблуждение при миграции — «перешли на .NET, значит стало кроссплатформенно». Такой магии нет. Если приложение глубоко связано с Windows, после миграции оно по-прежнему Windows-only.

7.1 Перенести, оставаясь Windows-only, — вполне реалистичный путь

У Microsoft есть Windows Compatibility Pack: он даёт доступ из современного .NET ко многим Windows API — реестр, WMI, EventLog, службы Windows, Directory Services и другие.

На практике это важно.

  • сначала хотим перейти на современный .NET
  • но пока из Windows не выходим
  • поэтому зависимость от Windows API пока готовы допустить

Для такой площадки это сильный вариант.

Первой целью миграции вовсе не обязательно должна быть кроссплатформенность.

7.2 Но Windows-only API — ещё и «долг, который всплывёт позже»

Наличие Windows Compatibility Pack не значит, что можно расслабиться во всём.

  • хотите поставить в Linux-контейнер
  • хотите работать с прицелом на Kubernetes
  • хотите, чтобы разработчики на macOS / Linux собирали тот же build
  • в будущем хотите сократить число Windows VM в облаке

Если такие цели есть, зависимость от Windows API лучше сделать видимой уже сейчас.

Где уместен compatibility pack и чем он становится долгомЕсли пока остаётесь на Windows, compatibility pack позволяет временно допустить Windows API и перейти на современный .NET; если потом целитесь в контейнеры и облако, эту зависимость нужно сделать видимой сейчас как долг, который всплывёт позже.пока остаёмся на Windowsесли потом контейнеры и облакоСначала хотим современный .NETВременно допустить зависимость через compatibility packЗависимость — долг, который всплывёт позжеСделать видимой уже сейчас

Рис. 22: Compatibility pack — реалистичный мост, но эта зависимость в зависимости от цели становится долгом, поэтому её делают видимой.

7.3 System.Drawing.Common особенно часто понимают неправильно

System.Drawing.Common начиная с .NET 6 — библиотека только для Windows. Если есть код обработки изображений или отрисовки текста, сначала нужно решить, каким путём идти.

  • продолжать эксплуатацию на Windows
  • или в будущем запускать также на Linux / macOS

В первом случае иногда можно пока оставить как есть. Во втором замену на SkiaSharp, ImageSharp и подобные нужно сразу включить в план миграции.

Развилка System.Drawing.CommonSystem.Drawing.Common с .NET 6 — библиотека только для Windows, поэтому при эксплуатации на Windows её иногда можно пока оставить, а если нужны Linux и macOS, замену на SkiaSharp или ImageSharp сразу включают в план.эксплуатация на Windowsнужны и Linux / macOSИспользуется System.Drawing.CommonИногда можно пока оставить как естьЗаменить на SkiaSharp / ImageSharpСразу включить в план миграции

Рис. 23: System.Drawing.Common — Windows-only, поэтому обращение с ним с самого начала зависит от точки посадки.

7.4 Типичные признаки жёсткой привязки к Windows

Если есть такие ссылки или API, безопаснее оценивать проект исходя из того, что «как минимум сначала он мигрирует, оставаясь Windows-only».

  • Microsoft.Win32.Registry
  • System.Management
  • System.Diagnostics.EventLog
  • System.ServiceProcess
  • System.DirectoryServices
  • System.Drawing
  • DllImport / P/Invoke
  • ссылки на COM
  • AxInterop.*
  • Microsoft.Office.Interop.*

8. Способ вынесения общих библиотек меняет сложность

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

8.1 Сначала классифицировать

Библиотеки проще разложить, разбив на три большие категории.

  1. чистая бизнес-логика / доменная логика
  2. промежуточный слой с небольшой зависимостью от app model
  3. слой, плотно связанный с UI / Web / Windows API

Из них первой стоит переносить категорию 1.

  • вычисления
  • проверка правил
  • DTO / контракты
  • доменные сервисы
  • простые преобразования данных

Если эту часть удаётся вынести чисто, сложность резко падает.

Три класса библиотек и порядок переносаБиблиотеки делят на чистую бизнес-логику, промежуточный слой с зависимостью от app model и слой, плотно связанный с UI или Windows API; первой переносят чистую бизнес-логику.переносить первойзатемв концеЧистая бизнес-логикаЕсли вынести чисто, сложность падаетПромежуточный слой ближе к app modelСлой, плотно связанный с UI и Windows API

Рис. 24: Библиотеки делят на три слоя и сначала выносят чистую логику.

8.2 netstandard2.0 по-прежнему рабочий мост

По рекомендациям Microsoft, для общей библиотеки, которой нужно сосуществовать и со стороной .NET Framework, базовый вариант — .NET Standard 2.0.

Здесь важны два момента.

  • .NET Framework не поддерживает .NET Standard 2.1
  • если общую библиотеку нужно ссылать и из старого, и из нового кода, 2.0 обычно оказывается практическим решением
Мост netstandard2.0.NET Framework не поддерживает .NET Standard 2.1, поэтому если общую библиотеку нужно ссылать и из старого, и из нового кода, практическим решением чаще оказывается .NET Standard 2.0.может ссылатьсяможет ссылатьсяFramework не поддерживаетСторона .NET FrameworkОбщая библиотека netstandard2.0Сторона современного .NETnetstandard2.1

Рис. 25: Как мост для ссылок с обеих сторон реалистичен 2.0, а не 2.1.

8.3 Что меняется при netstandard2.0

Подход Что меняется Когда уместно На что смотреть
Переход на netstandard2.0 Легко ссылаться и из старого, и из нового кода Чистая бизнес-логика, общие контракты, утилиты API, специфичные для app model, положить нельзя
Multi-target (например net48;net10.0) Общий код сохраняется, при этом можно держать различия по окружениям Библиотеки с небольшими различиями по окружениям Растёт объём условной компиляции и управления сборкой
Сразу сделать только net10.0 В перспективе самый чистый вариант Новые слои, которым не нужно сосуществование старого и нового Из .NET Framework ссылаться нельзя

8.4 Режим совместимости — не панацея

У .NET Standard 2.0 есть режим совместимости, который позволяет ссылаться на библиотеки .NET Framework. Но это не волшебство, при котором всё прозрачно работает само.

Например, библиотеки, завязанные на API конкретной app model вроде WPF, остаются сложными. То есть даже говоря «общая библиотека», важно сузить её до ответственности, которую действительно можно разделять.

8.5 Для библиотек ASP.NET всё решает возможность снять System.Web

При поэтапной миграции ASP.NET Framework жизнь сильно усложняется, если общая библиотека напрямую висит на HttpContext.Current или System.Web.

Базовая стратегия тогда — один из следующих вариантов.

  • вынести зависимость от System.Web за пределы интерфейса
  • изменить код так, чтобы сведения из HttpContext приходили как DTO
  • на переходный период использовать adapter
  • если и это не помогает — снимать зависимость поэтапно через multi-target

8.6 Библиотеки поднимают в порядке leaf-first

В руководстве по incremental migration для ASP.NET явно сказано поднимать supporting library в порядке postorder depth-first, то есть с листьев.

Это работает не только для Web, но и для обычных решений в целом.

  • зависимости уже подняты, поэтому верхние слои становятся обозримее
  • проблемы совместимости легче локализовать
  • легче тестировать каждую библиотеку отдельно
Поднимать leaf-firstЕсли supporting library поднимать с листьев зависимостей, зависимости уже готовы, верхние слои становятся обозримее, проблемы совместимости локализуются, и библиотеки проще тестировать по отдельности.Сначала поднять листовые библиотекиЗатем промежуточный слой, чьи зависимости уже готовыВ конце верхний слой приложенияПроблемы можно локализовать и тестировать

Рис. 26: Если поднимать с листьев, к моменту верхнего слоя основание уже собрано.

9. Инвентаризировать NuGet / внешние зависимости / сторонние компоненты

Если сделать это небрежно, самая большая боль придёт во второй половине миграции.

9.1 Зависимости проще разложить, разбив на 4 вида

  1. публичные пакеты NuGet
  2. внутренние private package / internal library
  3. ссылки на локальные DLL
  4. COM / ActiveX / нативные DLL / SDK

Смотреть только на пункт 1 недостаточно. По-настоящему опасны 3 и 4.

Четыре вида зависимостей и где рискЗависимости делят на публичный NuGet, внутренние пакеты, ссылки на локальные DLL и COM / ActiveX / нативные DLL; одного публичного NuGet мало, по-настоящему опасны локальные DLL и COM / нативная сторона.Инвентаризация зависимостейПубличный NuGet и внутренние пакетыСсылки на локальные DLLCOM / ActiveX / нативные DLLПо-настоящему опасны эти

Рис. 27: Мало посчитать только публичный NuGet: сначала разбирают локальные DLL и COM / нативную сторону.

9.2 Что проверить по каждой зависимости

По каждой зависимости минимум нужно подтвердить следующее.

  • таргетирует ли она современный .NET
  • поддерживает ли PackageReference
  • нормально ли живёт в SDK-стиле
  • нет ли ограничений x86 / x64 / ARM64
  • не зависит ли от design-time инструментов или расширений Visual Studio
  • не предполагает ли install script / config transform
  • продолжается ли поддержка

9.3 Сторонний UI / отчёты / design-time компоненты оценивают отдельно

В миграции WinForms / WPF / ASP.NET это ощущается особенно сильно.

  • сетки
  • движки отчётов
  • компоненты вывода PDF
  • компоненты графиков
  • UI-библиотеки, встроенные в дизайнер
  • обёртки ActiveX

Здесь задействован не только runtime, но и поддержка в design-time. Если в оценке миграции смотреть только на «компилируется ли», обычно промахиваются.

9.4 Нативные DLL и bitness смотрят обязательно

Даже если в эпоху .NET Framework казалось, что приложение работает как AnyCPU, на деле оно может зависеть от такого.

  • COM, жёстко привязанный к x86
  • 32-bit ActiveX
  • конкретная версия VC++ Runtime
  • подписанные нативные DLL

Это не проблемы, которые внезапно родились из перехода на современный .NET, — это лишь всплывающие на поверхность изначально существовавшие ограничения. Именно поэтому их стоит сделать видимыми до миграции.

Как всплывают ограничения bitnessДаже если казалось, что приложение работает как AnyCPU, оно может зависеть от x86-only COM, 32-bit ActiveX или конкретной версии VC++ Runtime, и это лишь старые ограничения, которые становятся видны при переходе на современный .NET.Казалось, что работает как AnyCPUЗависимость от x86 COM, 32-bit ActiveXПри миграции всплывают старые ограниченияСделать видимыми до миграции

Рис. 28: Проблема bitness не порождается миграцией: просто становятся видны ограничения, которые были с самого начала.

10. EF6, сериализаторы и данные рассматривать как отдельную проблему

Миграцию runtime и перепроектирование доступа к данным / сериализации лучше по возможности держать как разные задачи — так обычно получается удачнее.

10.1 EF6 → EF Core — это не прямое обновление

В руководстве Microsoft по EF тоже сказано, что EF Core — это total rewrite EF6, и прямого пути обновления нет.

Поэтому для приложений на EF6 реалистичен такой порядок.

  1. сначала перейти на современный .NET
  2. при необходимости оставить EF6 и продолжать на нём работать
  3. затем перевести на EF Core уже отдельным проектом

Не совмещать миграцию runtime и миграцию ORM. Уже одно это заметно снижает сложность.

Разнести миграцию EF6 и runtimeEF Core — полная переработка EF6 без прямого пути обновления, поэтому реалистичный порядок — сначала перейти на современный .NET, при необходимости оставить EF6, и лишь потом перевести на EF Core отдельным проектом.если делать одновременноСначала перейти на современный .NETПри необходимости оставить EF6Потом EF Core отдельным проектомПутаются различия ORM и становится тяжелее

Рис. 29: Runtime и ORM не двигают одновременно: реалистичнее сначала перенести runtime, оставив EF6.

10.2 Что меняется, если оставить EF6

  • Плюсы
    • различия слоя доступа к данным можно отложить
    • можно сосредоточиться на миграции business logic и app model
    • «различия поведения EF Core» не примешиваются
  • На что смотреть
    • с точки зрения новой разработки основной вариант — EF Core
    • у форм использования EF6 Designer / EDMX есть отдельные ограничения

10.3 Для EF6 на базе EDMX смотрят и design-time

В документации EF6 написано, что EF Designer напрямую не поддерживается в проектах .NET / .NET Standard и в проектах .NET Framework на SDK-стиле.

Для приложений на EDMX эти три пункта нужно рассматривать по отдельности.

  • работает ли это во время выполнения
  • можно ли пользоваться Designer
  • как поступить со сгенерированным кодом

Если EDMX используется активно, безопаснее сразу заложить это в первоначальную оценку.

10.4 BinaryFormatter и своя сериализация легко становятся «скрытой зависимостью»

Сериализаторы легко упустить, если полагаться только на поиск по коду.

  • формат сохранения
  • обмен сообщениями
  • кэш
  • старые контракты WCF / SOAP
  • ResX
  • clipboard / drag & drop

Здесь замешана ещё и совместимость данных. То есть проверять нужно не просто «собралось ли», а читаются ли старые данные.

11. В объём миграции включать конфигурацию, распространение, эксплуатацию и CI/CD

Объект миграции — не только исходный код.

11.1 Файлы конфигурации

На стороне .NET Framework в app.config / web.config нередко лежит очень многое.

  • connection string
  • custom config section
  • настройки endpoint WCF
  • binding redirect
  • diagnostics
  • различные настройки ASP.NET
  • результаты transform при установке пакетов

На стороне современного .NET способ хранения и путь чтения конфигурации в ряде случаев меняются. Поэтому «файлы конфигурации оставим на потом» — опасно.

Первый шаг — инвентаризация конфигурации.

  • что лежит в файлах конфигурации
  • что из этого обязательно при запуске приложения
  • что относится к различиям по окружениям
  • что автоматически подставляли NuGet или установщик

11.2 Способ распространения

Форму распространения тоже смотрят до старта.

  • под IIS ли это
  • служба Windows ли это
  • Scheduled Task ли это
  • ClickOnce / MSI / собственный installer
  • предполагается ли on-premises сервер
  • что лучше подходит: self-contained или framework-dependent

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

11.3 Логи, мониторинг, регламенты эксплуатации

Эксплуатационную сторону тоже легко упустить.

  • предполагается ли Windows Event Log
  • смотрят ли Performance Counter
  • есть ли мониторинг на базе WMI
  • зафиксированы ли служебная учётная запись и права
  • предполагается ли, что логи пишутся в локальный файл

Совершенно обычная ситуация: код после миграции работает, а эксплуатация не выстраивается.

11.4 CI/CD и build agent

Перед миграцией проверяют и следующее.

  • ставится ли нужный .NET SDK на build agent
  • что делать с pipeline, завязанным на nuget.exe / msbuild.exe
  • сдвигаться ли к dotnet CLI
  • как обновить задания тестов, coverage, publish
  • не завязаны ли внутренние шаблоны или reusable pipeline на старый формат

«Локально работает, а CI падает» — классика миграции.

Объект миграции — не только кодДаже если исполняемый модуль перенесён, если файлы конфигурации, способ распространения, логи и мониторинг, CI/CD и build agent остаются на старых допущениях, в конце всё упирается в тупик, поэтому их тоже включают в объём миграции.Миграция исходного кодаЭтим не заканчиваетсяКонфигурация и способ распространенияЛоги, мониторинг, эксплуатацияCI/CD и build agentЛокально работает, а CI падает

Рис. 30: Пока не посчитаны конфигурация, распространение, эксплуатация и CI/CD, объём миграции ещё не посчитан.

12. Реалистичный порядок миграции

С учётом всего вышесказанного реалистичный ход обычно сводится примерно к следующему.

12.1 Сначала навести порядок на стороне текущего Framework

  1. Поднять до .NET Framework 4.7.2 или новее, по возможности до 4.8.1
  2. Обновить зависимости
  3. Пересмотреть packages.config
  4. По возможности сдвинуться к PackageReference и SDK-стилю
  5. Убедиться, что текущее приложение в этом состоянии действительно работает

Уже одно это заметно сокращает разницу на следующем этапе.

12.2 Сначала спасти shared library

Далее бизнес-логику и общие контракты сдвигают к netstandard2.0 или multi-target. Порядок подъёма — leaf-first.

12.3 Для самого приложения стратегию меняют по app model

  • библиотеки классов / консоль / часть служб идут сравнительно прямо
  • WinForms / WPF модернизируют, оставаясь Windows-only
  • ASP.NET MVC / Web API небольшие — одним махом, тяжёлые — поэтапно
  • Web Forms исходят из замены экранного слоя и сначала выносят shared logic
  • сервер WCF сначала решают: оставлять CoreWCF или делать редизайн на gRPC

12.4 Соблюдать правило «не делать всё сразу»

Особенно стоит избегать таких сочетаний.

  • миграция runtime + полная замена ORM
  • миграция runtime + смена платформы аутентификации
  • миграция runtime + полный переход в облако
  • миграция runtime + смена платформы мониторинга
  • миграция runtime + обновление UI-фреймворка

Даже если всё это в итоге нужно, обычно лучше не складывать в один спринт.

12.5 Двигаться только после тестов и baseline

Как минимум перед стартом хочется иметь следующее.

  • модульные тесты
  • интеграционные тесты основных бизнес-потоков
  • snapshot-проверку типичных экранов / API
  • baseline по производительности
  • способ проверки основных логов
  • процедуру отката

Идти вперёд в состоянии, когда после миграции нельзя понять, «что именно сломалось», довольно опасно.

Что подготовить до того, как двигатьсяЕсли до миграции подготовить тесты, baseline по производительности, способ проверки логов и процедуру отката, после миграции можно понять, что сломалось; идти без этой подготовки опасно.Сначала тесты и baselineЗатем миграцияМожно понять, что сломалосьИдти без подготовкиСломается, и не найти где

Рис. 31: Миграцию определяет подготовка до старта: без baseline сломанное место не локализовать.

13. Чек-лист до старта

Ниже форма, которую можно сразу вставить в управление проектом.

13.1 Политика

  • Можете одним предложением объяснить, зачем нужна миграция
  • Определено, куда приземляемся: Windows-only современный .NET или кроссплатформенность в будущем
  • Определена целевая версия .NET
  • Определено, что не входит в текущий объём (переход на EF Core, обновление аутентификации, полный переход в облако и т. п.)

13.2 Подготовка текущей стороны .NET Framework

  • Подняли до .NET Framework 4.7.2 или новее, по возможности до 4.8.1
  • Обновили зависимости ближе к актуальным
  • Выявили наличие packages.config
  • Проверили, можно ли перейти на PackageReference
  • Проверили, можно ли перейти на SDK-стиль
  • Текущее приложение в этом состоянии собирается, запускается и проходит тесты

13.3 Тип приложения и выбор технологий

  • Разбили сложность по типам: библиотека классов / десктоп / Web / WCF и т. д.
  • Понимаем, что WinForms / WPF остаются Windows-only
  • Понимаем, что ASP.NET Framework — это миграция app model на ASP.NET Core
  • Включили в оценку замену UI-слоя Web Forms
  • Оценили клиента и сервер WCF по отдельности

13.4 Неподдерживаемые технологии и API, которые стоит проверить

  • Выявили зависимость от AppDomain
  • Выявили Remoting / MarshalByRefObject / BeginInvoke / EndInvoke
  • Выявили CAS / Security Transparency / COM+ / WF
  • Выявили зависимость от BinaryFormatter
  • Выявили зависимость от System.Web

13.5 Зависимости, специфичные для Windows

  • Выявили использование реестра, WMI, EventLog, служб Windows, Directory Services
  • Выявили использование System.Drawing.Common
  • Выявили COM / ActiveX / Office Interop / P/Invoke / нативные DLL
  • Проверили ограничения x86 / x64 / ARM64

13.6 shared library и доступ к данным

  • Классифицировали shared library на бизнес-логику / слой, тесно связанный с app model
  • Выявили то, что можно перевести на netstandard2.0
  • Выявили библиотеки, которым нужен multi-target
  • Определили, можно ли сначала перенести только runtime, сохранив EF6
  • Проверили зависимость от EDMX / Designer

13.7 Эксплуатация и build

  • Провели инвентаризацию файлов конфигурации
  • Проверили способ распространения (IIS / Service / MSI / ClickOnce и т. д.)
  • Проверили допущения о логах / мониторинге / правах / учётной записи выполнения
  • Проверили, нужно ли обновлять CI/CD и build agent
  • Подготовили процедуру отката

14. Итог

В миграции с .NET Framework на .NET важно не столько «какой командой переносить», сколько до старта увидеть, что идёт как есть, а что становится отдельной проблемой.

Если сузить до главного, это шесть пунктов.

  • до миграции навести порядок на стороне .NET Framework
  • разделить сложность по app model
  • заранее выявить неподдерживаемые технологии
  • решить, насколько допустима привязка к Windows
  • решить, как резать shared library
  • не складывать в один этап ORM, аутентификацию и переход в облако

Точность оценки миграции сильно зависит от первой недели. Иначе говоря, если за эту неделю удастся разложить вопросы, вторая половина будет заметно ближе к обычной разработке.

Как использовать первую неделюЕсли за первую неделю разложить, что идёт как есть, а что становится отдельной проблемой, точность оценки растёт, и вторая половина становится ближе к обычной разработке.Первая неделя: разложить вопросыТочность оценки растётДальше ближе к обычной разработкеЧто идёт как есть, что отдельно

Рис. 32: Удастся ли первую неделю потратить на инвентаризацию — от этого зависит точность миграции в целом.

«Для начала просто попробуем переключиться на net10.0» — как разведка это неплохо. Но для боевой миграции перед этим есть на что посмотреть. Именно это мы и разобрали в этой статье.

15. Справочные материалы

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

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

Что сделать в первую очередь перед миграцией с .NET Framework на .NET?
До реализации сначала наведите порядок на стороне .NET Framework. Официальное руководство Microsoft тоже рекомендует перед портированием поднять версию до .NET Framework 4.7.2 или новее (на практике, если можно, — до 4.8.1), перевести packages.config на PackageReference, приблизить проекты к SDK-стилю и обновить зависимости ближе к актуальным. Если это сделать заранее, разница на этапе перехода на современный .NET сильно сокращается, и проще понять, проблема из-за возраста Framework или из-за самого перехода на .NET. Параллельно до старта проведите инвентаризацию неподдерживаемых технологий — AppDomain, Remoting, BinaryFormatter и подобных.
Можно ли оставить приложение на .NET Framework?
Да, это обычное решение. .NET Framework 4.8.1 поддерживается, пока стоит на поддерживаемой Windows, так что речь не о том, что без немедленного перехода на современный .NET система сразу окажется в опасности. Если экранов на Web Forms очень много, нужно жёстко сохранить совместимость WCF-сервера, глубоко завязаны Workflow Foundation или COM+, либо design-time компоненты третьих сторон не работают на современном .NET, разумно пока стабильно эксплуатировать 4.8.1 и параллельно строить план замены на отдельном треке. Ограничения при этом остаются: из Windows не выйти, выгоду от новой производительности и языковых возможностей .NET получить труднее.
Какие технологии не переносятся на .NET или чаще всего стопорят работу?
Создание AppDomain, .NET Remoting, CAS (Code Access Security), COM+ (System.EnterpriseServices) и Workflow Foundation в современном .NET не поддерживаются — это красные флаги, которые требуют пересмотра дизайна. WCF-сервер из коробки как есть не работает: придётся выбирать CoreWCF или редизайн на gRPC / HTTP API. Начиная с .NET 9 реализации BinaryFormatter в runtime нет, API всегда бросает исключение, поэтому нужен аудит сохранённых данных, ResX, clipboard и drag & drop. Отдельно смотрите install.ps1 / XDT-преобразования packages.config, нативные DLL, COM / ActiveX и неявную ставку на x86: сборка может пройти, а падение всплывёт уже в runtime.
Если перенести WinForms или WPF на .NET, приложение станет кроссплатформенным?
Нет. WinForms / WPF можно перенести на .NET, но они остаются Windows-only фреймворками. Миграция даёт современный runtime .NET, языковые возможности, SDK-стиль и лучшую стыковку с CI/CD — но в Linux-контейнер приложение от этого не поедет. Если в будущем нужны Linux и контейнеры, заранее инвентаризируйте Windows-only API: System.Drawing.Common, реестр, WMI, EventLog, службы Windows, COM, Office Interop. Если остаётесь на Windows, реалистичный путь — сначала модернизировать runtime через Windows Compatibility Pack.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог