.NET Framework를 .NET으로 이행하기 전에 확인해야 할 것 - 착수 전에 승부가 결정되는 실천 체크리스트

· · .NET Framework, C#, 모더나이제이션, Windows 개발, 이행

한국어 시트 포함 Excel 체크리스트 다운로드

.csprojTargetFrameworknet10.0으로 바꾸고, NuGet을 몇 개 업데이트해서 빌드가 통과하면 끝.

……그런 이행이라면 상당히 평화롭습니다. 실제로는 그렇게 되지 않는 경우가 많습니다.

.NET Framework의 현장에는 System.Web, WCF, Web Forms, 오래된 packages.config, web.config.install.xdt, 네이티브 DLL, COM / ActiveX, 디자인 시에만 동작하는 서드파티제 컨트롤, 암묵적인 x86 전제, 디자이너 의존의 ResX, 오래된 시리얼라이저 등, 평소에는 의식하지 않은 전제가 꽤 잠들어 있습니다.

그래서 .NET Framework에서 .NET으로의 이행에서 정말 중요한 것은 구현에 들어가기 전의 재고 조사입니다. 착수 전에 논점을 분해할 수 있다면, 이행은 「큰 도박」이 아니라 「순서대로 없애가는 작업」이 됩니다.

이 글에서는 기존의 .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는 무거워지기 쉽습니다.
  • WinForms / WPF는 .NET으로 이행해도 Windows 전용 그대로입니다. 여기를 잘못 알면 이행했는데 Linux 컨테이너에 안 실리는 정석의 지뢰를 밟습니다.
  • ASP.NET Framework → ASP.NET Core는 실질 아키텍처 이행입니다. 소규모 앱이라면 한꺼번에 갈 수 있는 경우도 있지만, 큰 본번계는 단계 이행 전제로 하는 편이 안전합니다.
  • WCF와 EF6은 runtime의 이행과 분리할 수 있는 경우가 있습니다. WCF 클라이언트는 .NET용으로 서포트된 패키지가 있고, EF6은 modern .NET으로 옮긴 후에 EF Core로 분리 이행할 수 있습니다.
  • 한편, AppDomain 작성, .NET Remoting, CAS, COM+, Workflow Foundation, BinaryFormatter 의존은 빨간불입니다. 먼저 발견하지 않으면 뒤에서 공수가 폭발합니다.
  • packages.config / install.ps1 / XDT / content 자산 / 네이티브 DLL / COM / ActiveX / x86 전제는 빌드는 통과해도 실행 시나 디자인 시에 떨어지기 쉬우므로, 착수 전에 씻어냅니다.
  • 2026-03 시점에서는 .NET 10이 LTS입니다. 신규 이행의 착지점은 기본적으로 현행 LTS를 기준으로 생각하는 것이 자연스럽습니다.
  • 테스트, 계측, 롤백의 준비가 없는 이행은 위험합니다. 이행은 구현 작업이라기보다 전제 조건을 하나씩 가시화하는 작업입니다.

2. 애초에 「지금 당장 이행해야 할지」를 먼저 결정한다

처음에 해야 할 것은 「이행하는 방법」을 생각하는 것이 아닙니다. 정말로 지금 이 앱을 이행해야 할지를 결정하는 것입니다.

여기를 애매하게 하면, 기술적으로는 옳지만 사업적으로는 너무 무거운 이행, 또는 반대로 명백히 이행하는 편이 좋은데 너무 뒤로 미루는 판단이 되기 쉽습니다.

2.1 .NET Framework에 남는 판단도 평범하게 있을 수 있다

.NET Framework 4.8.1은 서포트되는 Windows에 실려 있는 한 계속 서포트됩니다. 즉, 지금 당장 전부 modern .NET으로 하지 않으면 즉시 위험하다는 단순한 이야기가 아닙니다.

다만 잔류에는 명확한 제약이 있습니다.

  • Windows 전용에서 나올 수 없다
  • ASP.NET Web Forms나 오래된 서버 스택을 계속 안고 간다
  • 새로운 .NET의 성능 개선, 언어 기능, 에코시스템의 은혜를 받기 어렵다
  • 클라우드, 컨테이너, CI/CD의 요즘 전제와 어긋나기 쉽다

반대로, 다음에 강하게 의존하고 있다면, 당면은 .NET Framework 4.8.1에 기울여 안정 운용하면서 별도 라인으로 교체 계획을 세우는 것은 합리적입니다.

  • Web Forms의 화면 자산이 대량으로 있다
  • WCF 서버 호환을 엄밀히 유지할 필요가 있다
  • Workflow Foundation이나 COM+ 의존이 깊다
  • 서드파티의 디자인 시 부품이 modern .NET 비대응
  • 비즈니스적으로 큰 사양 변경이 허용되지 않는다

2.2 선택지별로 무엇이 바뀌는가

선택 무엇이 좋아지는가 무엇이 남는가 / 잃는가 어울리는 케이스
.NET Framework 4.8.1에 머문다 기존 자산을 무너뜨리지 않고 안정 운용하기 쉽다 Windows 전용, 오래된 앱 모델, 모던화의 한계 강한 레거시 의존이 있고, 지금은 사업 우선으로 안정 운용하고 싶다
modern .NET으로 이행하지만 Windows에 머문다 런타임이나 툴체인을 modern화할 수 있다. 성능・개발 체험・SDK 스타일의 은혜가 크다 Windows API 의존은 남는다. 크로스 플랫폼이 되지 않는다 WinForms / WPF, Windows Service, Windows API를 사용하는 업무 앱
modern .NET으로 이행하고, 장래적으로 Linux / 컨테이너 / 클라우드도 시야에 넣는다 배치처의 자유도가 올라간다. 운용 모델도 새롭게 하기 쉽다 Windows 전용 API나 app model을 먼저 떼어낼 필요가 있다 서버 사이드를 클라우드 쪽으로 기울이고 싶다, 인프라 쇄신도 하고 싶다

중요한 것은 이행하고 싶은가가 아니라 이행 후에 어디에 착지하고 싶은가를 먼저 결정하는 것입니다.

3. 먼저 결정해 둬야 할 4가지 방침

3.1 착지점의 .NET 버전

집필 시점에서는 Microsoft의 서포트 정책상 .NET 10이 LTS입니다. 한편 .NET 8 LTS.NET 9 STS는 어느 쪽도 2026-11-10에 서포트 종료 예정입니다.

그래서 이제부터 신규로 .NET Framework에서 이행한다면, 어지간한 사정이 없으면 현행 LTS를 착지점으로 하는 것이 자연스럽습니다.

여기서의 실무적인 사고방식은 심플합니다.

  • 짧게 끝내고 싶은 작은 이행이라면 현행 LTS로 직접 착지
  • 장기 운용 전제의 기반 시스템이라도 역시 현행 LTS를 기본으로 생각한다
  • 「기존 라이브러리 사정으로 1개 전의 LTS로 하고 싶다」는 사정으로서 있을 수 있지만, 언제까지 보수되는지를 날짜로 보고 판단한다

3.2 Windows 전용인 채로 갈지, 장래 크로스 플랫폼을 노릴지

이 판단으로, 봐야 할 논점이 꽤 바뀝니다.

  • Windows 전용인 채로라면, WPF / WinForms나 Windows Compatibility Pack을 사용해 우선은 runtime을 modern화하는 현실 노선이 취할 수 있습니다.
  • 장래 Linux / 컨테이너화도 노린다System.Drawing.Common, 레지스트리, WMI, EventLog, Windows Service, COM, Office Interop 등의 Windows 전제 API를 이른 단계에서 재고 조사할 필요가 있습니다.

여기를 결정하지 않고 이행을 시작하면 도중에 「Windows 고정으로 좋았나」 「아니, 컨테이너에 실었던 거였지」와 이야기가 비틀어집니다.

3.3 한꺼번에 할지, 단계 이행으로 할지

이행의 형은 크게 3가지입니다.

  • in-place에 가까운 일괄 이행
  • side-by-side로 신구를 나열하는 이행
  • route / library 단위로 조금씩 기울이는 단계 이행

특히 ASP.NET Framework 앱에서는 Microsoft의 가이드에서도 incremental migration이 명확히 안내되고 있습니다. 본번을 멈추고 싶지 않다, 기능 수가 많다, 주변 의존이 많다는 조건이라면 처음부터 단계 이행 전제로 설계하는 편이 무리가 없습니다.

3.4 무엇을 「이번 이행 대상에서 뺄지」

이행이 실패하기 쉬운 것은 할 일을 너무 담기 때문입니다.

예를 들어, 다음을 동시에 하는 것은 무거워지기 쉽습니다.

  • .NET Framework → .NET
  • ASP.NET Framework → ASP.NET Core
  • EF6 → EF Core
  • Windows 서버 → Linux 컨테이너
  • 인증 기반의 변경
  • 로그 / 감시 기반의 변경
  • 데이터베이스의 이행

물론 전부 언젠가는 필요할지도 모릅니다. 다만 동시에 할 필요가 있는가는 별개입니다.

현실에는 다음과 같이 분리하는 편이 잘 됩니다.

  1. 먼저 runtime과 프로젝트 구조를 modern화한다
  2. 그 위에서 app model을 옮긴다
  3. 마지막으로 ORM, 인증, 클라우드, 감시를 업데이트한다

4. 착수 전에 갖춰 둘 토대

Microsoft의 이식 전 가이드는 꽤 실무적입니다. 요컨대, 이행 전에 지금의 .NET Framework 프로젝트를 modern한 입구로 기울여 둔다는 이야기입니다.

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 공유 라이브러리의 취급이 안정되기 쉬워진다
  • 오래된 런타임 유래의 노이즈를 먼저 줄일 수 있다
  • 호환성 문제를 「Framework의 오래됨」과 「modern .NET화」 중 어느 것이 원인인지 구분하기 쉽다

4.2 PackageReference로 기울인다

이식 전 가이드에서는 참조를 PackageReference 형식으로 기울이는 것이 추천됩니다. 이것을 먼저 해 두면 의존 관계 관리가 꽤 전망이 좋아집니다.

PackageReference로 하면 무엇이 바뀌는가

  • 패키지 참조가 csproj에 집약된다
  • 추이적 의존 관계가 보기 쉬워진다
  • restore의 전제가 modern .NET 측과 맞춰진다
  • CLI / CI와의 상성이 좋아진다

다만, 여기에는 지뢰가 있습니다.

전형적인 지뢰

NuGet의 공식 문서에는 packages.config에서 PackageReference로의 이행에서 다음 제약이 명기되어 있습니다.

  • Visual Studio의 built-in 이행은 ASP.NET 프로젝트에서는 사용할 수 없다
  • install.ps1 / uninstall.ps1에 의존하는 패키지는 기대대로 동작하지 않는 경우가 있다
  • content 폴더의 자산은 무시되는 경우가 있다
  • web.config.install.xdt 등의 XDT 변환은 적용되지 않는다
  • lib 바로 아래의 어셈블리 구성이 오래된 패키지는 잘 해결되지 않는 경우가 있다

즉, 패키지 형식을 바꾸는 것만이라고 생각하지 않는 편이 좋습니다. 특히 classic ASP.NET은 NuGet 패키지 인스톨 시에 web.config를 고쳐 쓰는 문화가 꽤 있었으므로, 이행 시에 암묵적인 전제가 노출되기 쉽습니다.

4.3 SDK 스타일로 기울인다

이식 전 가이드에서는 SDK 스타일의 프로젝트 형식으로의 변환도 추천됩니다.

이것은 꽤 효과적입니다.

SDK 스타일로 하면 무엇이 바뀌는가

  • csproj가 대폭 간결해진다
  • PackageReference와 상성이 좋다
  • multi-targeting하기 쉽다
  • dotnet build / dotnet test / dotnet publish를 중심으로 한 CI/CD에 기울이기 쉽다
  • modern .NET 측의 구성에 가까워지므로 후반의 차분이 줄어든다

반대로 말하면 오래된 csproj와 오래된 NuGet 관리 그대로 갑자기 modern .NET으로 날아가면 차분이 너무 크다는 것입니다.

4.4 의존 관계를 먼저 업데이트한다

이것도 공식 가이드대로지만 의존 관계는 이용 가능한 최신 버전으로 기울이고, 가능하면 .NET Standard 대응판으로 기울입니다.

이것을 먼저 하는 의미

  • 「이 패키지는 modern .NET에서 사용할 수 있는지」가 빨리 알 수 있다
  • 오래된 의존 관계가 노이즈가 되는 것을 막을 수 있다
  • shared library를 netstandard2.0화하기 쉬워진다
  • 후반 이행 작업을 「코드의 이식」에 집중시키기 쉽다

4.5 공식 도구의 전제도 확인해 둔다

2026-03 시점에서는 Microsoft의 안내 중심은 GitHub Copilot 모더나이제이션 측으로 옮겨지고 있습니다. 즉, 종래의 이행 지원 도구만을 전제로 하기보다 평가, 계획, 코드 수정, 검증까지 포함한 일련의 지원 플로우로서 보는 편이 실무에 맞습니다.

다만, 현행 문서에서는 Visual Studio 2026 또는 서포트 중의 Visual Studio 2022계, GitHub Copilot, 그리고 C# 코드가 전제입니다.

이 확인이 필요한 이유

  • 공식 도구에 무엇을 기대할 수 있는지가 바뀐다
  • 팀의 IDE / build agent / 확장 기능의 전제를 맞출 수 있다
  • VB.NET 솔루션에서는 자동화에 너무 기대하지 않는 판단을 할 수 있다

VB.NET이 섞인 현장은 드물지 않습니다. 그래서 「최신의 공식 도구가 어디까지 도와주는가」를 처음에 확인해 둘 가치가 있습니다.

5. 프로젝트 종별별 난이도를 견적한다

이행은 「.NET Framework에서 .NET으로」라고 한데 묶어 이야기되기 쉽지만, 현실에는 프로젝트 종별별로 별도 게임입니다.

5.1 대략적인 난이도감

종별 난이도감 주된 논점
클래스 라이브러리 저~중 API 호환성, 의존 관계, 타겟 분할
콘솔 / 배치 / 일부 Windows Service 저~중 배포 방식, 네이티브 의존, 설정
WinForms / WPF Windows 전용 그대로, 디자이너, 서드파티 UI, BinaryFormatter 주변
ASP.NET MVC / Web API 중~고 ASP.NET Core로의 app model 이행, 인증, 세션, 설정, 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에 포함하고 있다
  • 레지스트리, WMI, EventLog 등 Windows API에 너무 기울어 있다
  • AppDomain이나 Remoting에 의존하고 있다

비즈니스 로직만을 잘라낼 수 있다면 가볍고, 앱 모델까지 안고 있으면 무겁다고 보면 알기 쉽습니다.

5.3 WinForms / WPF는 이행하기 쉽지만 Windows 전용 그대로

WinForms와 WPF는 .NET으로 이행 가능합니다. 다만, 어느 쪽도 Windows 전용 프레임워크 그대로입니다.

여기서 기대값을 틀리면 위험합니다.

  • 좋아지는 것
    • modern .NET의 런타임, 언어, SDK 스타일에 탈 수 있다
    • CI/CD나 package 관리를 요즘에 기울이기 쉽다
    • 일부의 성능・보수성 개선을 얻기 쉽다
  • 바뀌지 않는 것
    • Windows 전용인 것
    • UI 컨트롤이나 디자인 시 부품의 상성 문제가 남는 것
    • ActiveX / COM / 네이티브 DLL 문제가 사라지지 않는 것

또한, WinForms / WPF는 BinaryFormatter의 영향 확인이 필요한 케이스가 있습니다. 특히 clipboard, drag & drop, ResX, 디자인 시의 시리얼라이즈에 custom type이 얽히면, target을 .NET 9 이후로 올렸을 때 표면화되기 쉽습니다.

5.4 ASP.NET Framework는 「runtime 이행」이 아니라 「app model 이행」

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 의존을 shared library에서 떼어낼 수 있는가
  • 인증 / 세션 / 예외 처리 / 로그를 어떻게 맞출 것인가
  • 본번을 멈추지 않고 단계 이행할 것인가

특히 큰 앱은 처음부터 incremental migration 전제로 생각하는 편이 현실적입니다.

5.5 Web Forms는 「자산 이행」이 아니라 「책무 분해」부터 들어간다

Web Forms는 ASP.NET Core와 같은 app model이 아닙니다. 그래서 견적상은 화면 자산을 그대로 가지고 갈 수 있는 전제로 생각하지 않는 편이 안전합니다.

실무에서는 먼저 다음 분해부터 들어가는 경우가 많습니다.

  • 화면 로직과 비즈니스 로직을 나눈다
  • Page / UserControl / ViewState에 묻힌 책무를 분해한다
  • 업무 로직이나 데이터 액세스를 shared library로 도망친다
  • UI는 Razor Pages / MVC / Blazor 등 별도 모델로 재구성한다

즉 Web Forms 안건은 runtime의 이행보다 먼저 책무의 분해 계획이 있는지가 중요합니다.

5.6 WCF 클라이언트와 WCF 서버는 나누어 생각한다

여기는 한데 묶지 않는 편이 좋습니다.

WCF 클라이언트

WCF Client에는 modern .NET용의 서포트된 NuGet 패키지가 있습니다. 그래서 WCF를 부르는 측만이라면 외견만큼 무겁지 않은 케이스가 있습니다.

WCF 서버

한편으로 WCF 서비스를 호스트하는 측은 별개입니다. Microsoft의 가이드에서는 modern화의 경로로서 크게 다음 2가지가 안내되고 있습니다.

  • CoreWCF를 사용해 기존 클라이언트 호환을 유지하는 방향
  • gRPC 등 modern한 RPC / HTTP 베이스로 기울이는 방향

다만 CoreWCF는 WCF의 모든 것을 그대로 가져오는 것이 아니라 서브셋입니다. 즉, 기존 클라이언트와의 호환 유지에는 어울리지만 코드 변경과 테스트는 전제입니다.

6. .NET에서 사용할 수 없는 / 그대로는 막히기 쉬운 기술을 씻어낸다

이것은 착수 전에 반드시 해 둘 곳입니다. Microsoft에는 .NET Framework에서 사용할 수 있었지만 .NET 6+에서는 사용할 수 없는 기술의 일람이 있습니다.

6.1 빨간불이 되기 쉬운 기술

기술 .NET에서의 상태 어떻게 생각해야 하는가
AppDomain.CreateDomain 등 AppDomain의 작성 비대응 분리는 별도 프로세스 / 컨테이너 / AssemblyLoadContext로 생각한다
.NET Remoting 비대응 IPC, HTTP, gRPC, Socket, Pipe 등으로 재설계
CAS / Security Transparency 보안 경계로서 비대응 OS / 컨테이너 / 권한 분리로 생각한다
System.EnterpriseServices(COM+) 비대응 COM+ 전제의 설계를 분리・치환한다
Workflow Foundation 비대응 CoreWF 등 대체를 포함해 별도 견적으로 생각한다
WCF server built-in에서는 그대로는 아니다 CoreWCF인지 gRPC인지를 고른다
BinaryFormatter .NET 9 이후에서는 구현이 항상 예외 시리얼라이저 이행, ResX / clipboard / drag & drop 감사

6.2 AppDomain은 「일부 API가 남아 있어도 작성은 별도 문제」

AppDomain 주변은 약간 까다롭습니다. .NET에서도 일부 API surface는 남아 있지만, 새로운 AppDomain을 만들어 격리하는 사용법은 서포트되지 않고 있습니다.

그래서 이하의 용도로 AppDomain을 사용하고 있는 경우는 재설계가 필요합니다.

  • 플러그인 격리
  • 동적 로드의 파기
  • 부분 신뢰 코드의 격리
  • 일시적인 실행 환경의 분리

이행 전에 봐야 할 것은 AppDomain이라는 단어가 나오는지뿐만 아니라, 무엇을 위해 AppDomain을 사용하고 있었는지입니다.

6.3 Remoting은 「외견보다 깊다」

Remoting은 물론이지만, delegate의 BeginInvoke() / EndInvoke() 호출과 같은 Remoting 유래의 거동도 영향 범위에 들어가는 경우가 있습니다.

그래서 검색 시에는 다음도 봐 두면 안전합니다.

  • 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을 결정한 시점에서 먼저 감사할 논점입니다.

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

이것들이 1개라도 발견되면 즉 아웃이라는 의미는 아닙니다. 어디가 표준 루트로 이행할 수 있고, 어디가 별도 트랙이 되는지를 알기 위한 지도입니다.

7. Windows 전용 전제를 어디까지 허용할지 결정한다

이행에서 자주 일어나는 오해가 「.NET으로 옮기면 크로스 플랫폼이 된다」라는 것입니다. 그런 마법은 없습니다. 앱이 Windows에 깊게 결합되어 있으면 이행 후에도 평범하게 Windows 전용입니다.

7.1 Windows 전용인 채로 옮기는 것은 충분히 현실적

Microsoft에는 Windows Compatibility Pack이 있고, 레지스트리, WMI, EventLog, Windows Service, Directory Services 등 많은 Windows계 API를 modern .NET에서 사용할 수 있게 하는 수단이 있습니다.

이것은 꽤 중요합니다.

  • 먼저 modern .NET으로 옮기고 싶다
  • 하지만 당면은 Windows에서 나오지 않는다
  • 그래서 Windows API 의존은 일단 허용하고 싶다

는 현장에서는 꽤 유효합니다.

즉, 이행의 최초 목표는 반드시 크로스 플랫폼화가 아니어도 좋다는 것입니다.

7.2 다만 Windows 전용 API는 「나중에 효과적으로 오는 빚」이기도 하다

Windows Compatibility Pack이 있다고 해서 뭐든 안심은 아닙니다.

  • Linux 컨테이너에 싣고 싶다
  • Kubernetes 전제로 동작시키고 싶다
  • macOS / Linux 개발자도 같은 build를 돌리고 싶다
  • 장래 클라우드에서 Windows VM을 줄이고 싶다

라는 목표가 있다면 Windows API 의존은 지금 중에 가시화해 두는 편이 좋습니다.

7.3 System.Drawing.Common은 특히 오해되기 쉽다

System.Drawing.Common은 .NET 6 이후에서는 Windows 전용 라이브러리입니다. 즉, 이미지 처리나 문자 묘화로 사용하고 있는 코드가 있다면 다음을 먼저 결정할 필요가 있습니다.

  • Windows인 채로 운용할 것인가
  • 장래 Linux / macOS에서도 동작시키고 싶은가

전자라면 당면 그대로여도 좋은 케이스가 있습니다. 후자라면 SkiaSharp나 ImageSharp 등으로의 치환을 이행 계획에 처음부터 포함할 필요가 있습니다.

7.4 Windows 고정을 나타내는 대표적인 냄새

다음과 같은 참조나 API가 있을 때는 「적어도 처음은 Windows 전용인 채로 옮긴다」 전제로 견적하는 편이 안전합니다.

  • Microsoft.Win32.Registry
  • System.Management
  • System.Diagnostics.EventLog
  • System.ServiceProcess
  • System.DirectoryServices
  • System.Drawing
  • DllImport / P/Invoke
  • COM 참조
  • AxInterop.*
  • Microsoft.Office.Interop.*

8. 공유 라이브러리의 잘라내는 방식으로 난이도가 바뀐다

큰 솔루션에서는 이행의 성패는 shared library의 자르는 방식으로 결정된다고 말해도 과장이 아닙니다.

8.1 먼저 분류한다

라이브러리는 크게 3 종류로 나누면 정리하기 쉽습니다.

  1. 순수한 업무 로직 / 도메인 로직
  2. 앱 모델에 약간 의존하는 중간층
  3. UI / Web / Windows API에 밀착한 층

이 중 가장 먼저 옮겨야 할 것은 1입니다.

  • 계산
  • 룰 판정
  • DTO / 계약
  • 도메인 서비스
  • 단순한 데이터 변환

여기를 깔끔하게 낼 수 있으면 한꺼번에 난이도가 내려갑니다.

8.2 netstandard2.0은 지금도 유효한 다리

Microsoft의 가이던스에서는 .NET Framework 측과도 공존할 필요가 있는 shared library라면, 우선 .NET Standard 2.0을 생각하는 것이 기본입니다.

여기서 중요한 것은 2점입니다.

  • .NET Framework는 .NET Standard 2.1을 서포트하지 않는다
  • shared library를 old / new 양쪽에서 참조하고 싶다면 2.0이 현실해가 되기 쉽다

8.3 netstandard2.0으로 하면 무엇이 바뀌는가

방침 어떻게 바뀌는가 어울리는 케이스 주의점
netstandard2.0 old / new 양쪽에서 참조하기 쉽다 순수한 업무 로직, 공통 계약, 유틸리티 app model 고유 API는 실을 수 없다
multi-target(예: net48;net10.0) 공통 코드를 유지하면서 환경별 차분을 가질 수 있다 약간 환경 차가 있는 라이브러리 조건 분기나 build 관리가 늘어난다
갑자기 net10.0 전용화 장래는 가장 깔끔 old / new 공존이 불필요한 신규 층 .NET Framework에서는 참조할 수 없다

8.4 호환 모드는 만능이 아니다

.NET Standard 2.0에는 .NET Framework 라이브러리를 참조하는 호환 모드가 있습니다. 다만, 이것은 뭐든 투과적으로 동작하는 마법이 아닙니다.

예를 들어 WPF와 같은 app model 고유 API를 전제로 한 라이브러리는 평범하게 어렵습니다. 즉, shared library라고 해도 정말로 shared할 수 있는 책무만으로 좁히는 것이 중요합니다.

8.5 ASP.NET계 라이브러리는 System.Web을 떼어낼 수 있는지가 승부

ASP.NET Framework를 단계 이행하는 경우, shared library가 HttpContext.CurrentSystem.Web에 직접 매달려 있으면 꽤 힘듭니다.

이때의 기본 전략은 다음 중 어느 것입니다.

  • System.Web 의존을 인터페이스 밖으로 밀어낸다
  • HttpContext 유래의 정보를 DTO로서 받도록 바꾼다
  • 이행 과도기에 adapter를 사용한다
  • 그래도 무리라면 multi-target으로 단계적으로 떼어낸다

8.6 라이브러리는 leaf-first로 올린다

ASP.NET의 incremental migration 가이드에서는 supporting library를 postorder depth-first, 즉 잎에서 순서대로 올리는 것이 명시되어 있습니다.

이것은 Web에 한정되지 않고 일반 솔루션에서도 꽤 유효합니다.

  • 의존처가 먼저 올라가 있으므로 상위 층의 전망이 좋아진다
  • 호환성 문제를 국소화하기 쉽다
  • 라이브러리 단위로 테스트하기 쉽다

9. NuGet / 외부 의존 / 서드파티 부품을 재고 조사한다

여기를 거칠게 하면 이행의 후반에서 가장 아픈 꼴을 봅니다.

9.1 의존 관계는 4종류로 나누면 정리하기 쉽다

  1. 공개 NuGet 패키지
  2. 사내 private package / internal library
  3. 로컬 DLL 참조
  4. COM / ActiveX / 네이티브 DLL / SDK

이 중 1만 봐도 부족합니다. 정말로 위험한 것은 3과 4입니다.

9.2 의존별로 확인하고 싶은 것

각 의존에 대해 최저한 다음을 봅니다.

  • modern .NET을 타겟으로 하고 있는가
  • PackageReference에 대응하고 있는가
  • SDK 스타일로 문제 없는가
  • x86 / x64 / ARM64의 제약은 없는가
  • 디자인 시 도구나 Visual Studio 확장에 의존하고 있지 않은가
  • install script / config transform을 전제로 하고 있지 않은가
  • 서포트가 계속되고 있는가

9.3 서드파티 UI / 리포트 / 디자인 시 부품은 별도 틀로 견적

WinForms / WPF / ASP.NET의 이행에서는 여기가 꽤 효과적입니다.

  • 그리드
  • 리포트 엔진
  • PDF 출력 부품
  • 그래프 부품
  • 디자이너 통합형의 UI 라이브러리
  • ActiveX 래퍼

이들은 runtime뿐만 아니라 디자인 시 서포트가 얽힙니다. 이행의 견적에서 「컴파일할 수 있는가」만을 보면 평범하게 빗나갑니다.

9.4 네이티브 DLL과 bitness는 반드시 본다

.NET Framework 시대에 AnyCPU로 동작하고 있던 것처럼 보여도, 실제로는 다음에 의존하고 있는 경우가 있습니다.

  • x86 고정의 COM
  • 32bit 전용 ActiveX
  • 특정 버전의 VC++ Runtime
  • 서명된 네이티브 DLL

이 부근은 modern .NET화로 갑자기 나온 문제가 아니라, 원래부터 있던 제약이 표면화될 뿐입니다. 그래서 이행 전에 가시화해 두는 가치가 있습니다.

10. EF6, 시리얼라이저, 데이터 주변을 별도 문제로서 다룬다

runtime의 이행과 데이터 액세스나 시리얼라이즈의 재설계는 가능한 한 별도 문제로서 다루는 편이 잘 됩니다.

10.1 EF6 → EF Core는 직접 업그레이드가 아니다

Microsoft의 EF 가이드에서도 EF Core는 EF6의 total rewrite이며, direct upgrade path는 없다고 되어 있습니다.

그래서 EF6을 사용하고 있는 앱에서는 다음 순서가 현실적입니다.

  1. 먼저 modern .NET으로 옮긴다
  2. 필요하면 EF6을 유지한 채 앱을 동작시킨다
  3. 그 후 EF Core로 별도 프로젝트로서 이행한다

이것은 꽤 중요합니다. runtime migration과 ORM migration을 함께 하지 않는 것만으로 난이도는 꽤 내려갑니다.

10.2 EF6을 남기면 무엇이 바뀌는가

  • 좋은 점
    • 데이터 액세스 층의 차분을 뒤로 미룰 수 있다
    • business logic이나 app model의 이행에 집중할 수 있다
    • 「EF Core의 거동차」가 섞이지 않는다
  • 주의점
    • 신규 개발의 관점에서는 EF Core가 본명
    • EF6 Designer / EDMX 이용 형태에는 별도의 제약이 있다

10.3 EDMX 베이스의 EF6은 「디자인 시」까지 본다

EF6의 문서에는 EF Designer는 .NET / .NET Standard 프로젝트나 SDK 스타일의 .NET Framework 프로젝트에서 직접 서포트되지 않는다고 쓰여 있습니다.

즉, EDMX 베이스의 앱에서는 다음을 나누어 생각할 필요가 있습니다.

  • 실행 시에 동작하는가
  • Designer를 사용할 수 있는가
  • 생성 코드의 취급을 어떻게 할지

EDMX를 다용하고 있다면, 이것은 처음에 견적에 넣어 두는 편이 안전합니다.

10.4 BinaryFormatter나 독자 시리얼라이즈는 「숨은 의존」이 되기 쉽다

시리얼라이저는 코드 검색만으로는 놓치기 쉽습니다.

  • 영속화 포맷
  • 메시징
  • 캐시
  • 구 WCF / SOAP 계약
  • ResX
  • 클립보드 / drag & drop

이 부근은 데이터의 호환성도 얽힙니다. 즉, 단순히 「빌드할 수 있는가」가 아니라 오래된 데이터를 읽을 수 있는가까지 확인이 필요합니다.

11. 구성, 배포, 운용, CI/CD까지 이행 대상에 포함한다

이행의 대상은 소스 코드뿐만이 아닙니다.

11.1 설정 파일

.NET Framework 측에서는 app.config / web.config에 꽤 많은 것이 실려 있는 경우가 있습니다.

  • connection string
  • custom config section
  • WCF endpoint 설정
  • binding redirect
  • diagnostics
  • ASP.NET의 각종 설정
  • package install 시의 transform 결과

modern .NET 측에서는 구성의 가지는 방식이나 읽기 경로가 바뀌는 장면이 있습니다. 그래서 「설정 파일은 나중에」는 위험합니다.

처음에 할 것은 설정의 재고 조사입니다.

  • 무엇이 설정 파일에 있는가
  • 어느 것이 앱 기동 시에 필수인가
  • 어느 것이 환경 차분인가
  • 어느 것이 NuGet이나 installer에 의해 자동 주입되고 있었는가

11.2 배포 방식

다음도 확인해 두고 싶은 곳입니다.

  • IIS 산하인가
  • Windows Service인가
  • Scheduled Task인가
  • ClickOnce / MSI / 독자 installer인가
  • 온프레 서버 전제인가
  • self-contained / framework-dependent 중 어느 것이 어울리는가

실행 본체가 이행할 수 있어도 배포와 기동의 구조가 오래된 전제 그대로라면 마지막에 막힙니다.

11.3 로그, 감시, 운용 순서

운용 주변도 놓치기 쉽습니다.

  • Windows Event Log 전제인가
  • Performance Counter를 보고 있는가
  • WMI 베이스의 감시인가
  • 서비스 계정이나 권한이 고정인가
  • 로그의 출력처가 로컬 파일 전제인가

이행 후에 코드는 동작해도 운용이 돌지 않는다는 것은 평범하게 있습니다.

11.4 CI/CD와 build agent

이행 전에 다음도 확인합니다.

  • build agent에 필요한 .NET SDK가 들어가는가
  • nuget.exe / msbuild.exe 전제의 pipeline을 어떻게 할 것인가
  • dotnet CLI 베이스로 기울일 것인가
  • 테스트 실행, coverage, publish의 job을 어떻게 업데이트할 것인가
  • 사내 템플릿이나 reusable pipeline이 오래된 형식 전제가 아닌가

사람의 로컬 환경에서는 동작하는데 CI가 죽는다는 이행 흔한 일입니다.

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 전용인 채로 modern화한다
  • ASP.NET MVC / Web API 작으면 일괄, 무거우면 단계 이행
  • Web Forms 화면 자산의 치환을 전제로 shared logic을 먼저 도망친다
  • WCF 서버 CoreWCF 유지인지 gRPC 재설계인지 먼저 결정한다

12.4 「한 번에 전부 하지 않는다」를 지킨다

특히 피하고 싶은 조합은 다음입니다.

  • runtime 이행 + ORM 총 교체
  • runtime 이행 + 인증 기반 변경
  • runtime 이행 + 클라우드 전면 이행
  • runtime 이행 + 감시 기반 변경
  • runtime 이행 + UI 프레임워크 쇄신

전부 필요해도 같은 스프린트에 담지 않는 편이 대개 잘 됩니다.

12.5 테스트와 베이스라인을 취하고 나서 움직인다

최저한 다음은 원하는 곳입니다.

  • 단위 테스트
  • 주요 업무 플로우의 결합 테스트
  • 대표적인 화면 / API의 스냅샷적 확인
  • 성능 베이스라인
  • 주요 로그의 확인 방법
  • 롤백 순서

이행 후의 「무엇이 망가졌는가」를 특정할 수 없는 상태에서 진행하는 것은 꽤 위험합니다.

13. 착수 전 체크리스트

그대로 프로젝트 관리에 붙일 수 있는 형태로 둡니다.

13.1 방침

  • 왜 이행하는지를 1문으로 말할 수 있다
  • 이번 착지점이 Windows 전용의 modern .NET인지 장래의 크로스 플랫폼화인지 정해져 있다
  • target의 .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 전용인 채라고 이해하고 있다
  • ASP.NET Framework는 ASP.NET Core로의 app model 이행이라고 이해하고 있다
  • Web Forms의 UI 층 치환을 견적에 넣었다
  • WCF는 클라이언트와 서버를 나누어 평가했다

13.4 비대응 기술・요주의 API

  • AppDomain 의존을 씻어냈다
  • Remoting / MarshalByRefObject / BeginInvoke / EndInvoke를 씻어냈다
  • CAS / Security Transparency / COM+ / WF를 씻어냈다
  • BinaryFormatter 의존을 씻어냈다
  • System.Web 의존을 씻어냈다

13.5 Windows 전용 의존

  • 레지스트리, WMI, EventLog, Windows Service, 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이 필요한 라이브러리를 씻어냈다
  • EF6을 남기고 runtime만 먼저 옮길 수 있는지 판단했다
  • EDMX / Designer 의존을 확인했다

13.7 운용과 build

  • 설정 파일의 재고 조사를 했다
  • 배포 방식(IIS / Service / MSI / ClickOnce 등)을 확인했다
  • 로그 / 감시 / 권한 / 실행 계정 전제를 확인했다
  • CI/CD와 build agent의 업데이트가 필요한지 확인했다
  • 롤백 순서를 만들었다

14. 정리

.NET Framework에서 .NET으로의 이행에서 중요한 것은 「어느 커맨드로 옮길지」보다 무엇이 그대로 갈 수 있고, 무엇이 별도 문제인지를 착수 전에 꿰뚫어 보는 것입니다.

특히 짚어두고 싶은 것은 다음입니다.

  • 이행 전에 .NET Framework 측을 정리한다
  • 앱 모델별로 난이도를 나눈다
  • 사용할 수 없는 기술을 먼저 씻어낸다
  • Windows 전용 전제를 어디까지 허용할지 정한다
  • shared library를 어떻게 자를지 정한다
  • ORM, 인증, 클라우드 이행을 동시에 너무 담지 않는다

이행은 최초 1주일에 견적의 정밀도가 꽤 바뀝니다. 반대로 말하면 그 1주일에 논점을 정리할 수 있다면 후반은 꽤 평범한 개발로 기울일 수 있습니다.

「우선 net10.0으로 바꿔 보자」는 탐색으로서는 나쁘지 않습니다. 다만 본번 이행으로서는 그 전에 봐야 할 곳이 있다. 이 글에서 정리한 것은 바로 거기입니다.

15. 참고 자료

관련 기사

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

관련 토픽

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 주제와 연결되는 서비스

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

블로그 목록으로 돌아가기