--- name: msbuild-modernization description: "Guide for modernizing and migrating MSBuild project files to SDK-style format. USE FOR: converting legacy .csproj/.vbproj with verbose XML to SDK-style, migrating packages.config to PackageReference, removing Properties/AssemblyInfo.cs in favor of auto-generation, eliminating explicit lists via implicit globbing, consolidating shared settings into Directory.Build.props. Indicators of legacy projects: ToolsVersion attribute, , .csproj files > 50 lines for simple projects. DO NOT USE FOR: projects already in SDK-style format, non-.NET build systems (npm, Maven, CMake), .NET Framework projects that cannot move to SDK-style." license: MIT --- # MSBuild Modernization: Legacy to SDK-style Migration ## Identifying Legacy vs SDK-style Projects **Legacy indicators:** - `` - Explicit file lists (`` for every `.cs` file) - `ToolsVersion` attribute on `` element - `packages.config` file present - `Properties\AssemblyInfo.cs` with assembly-level attributes **SDK-style indicators:** - `` attribute on root element - Minimal content — a simple project may be 10–15 lines - No explicit file includes (implicit globbing) - `` items instead of `packages.config` **Quick check:** if a `.csproj` is more than 50 lines for a simple class library or console app, it is likely legacy format. ```xml Debug AnyCPU Library MyLibrary MyLibrary v4.7.2 512 true ``` ```xml net472 ``` ## Migration Checklist: Legacy → SDK-style ### Step 1: Replace Project Root Element **BEFORE:** ```xml ``` **AFTER:** ```xml ``` Remove the XML declaration, `ToolsVersion`, `xmlns`, and both `` lines. The `Sdk` attribute replaces all of them. ### Step 2: Set TargetFramework **BEFORE:** ```xml v4.7.2 ``` **AFTER:** ```xml net472 ``` **TFM mapping table:** | Legacy `TargetFrameworkVersion` | SDK-style `TargetFramework` | |---------------------------------|-----------------------------| | `v4.6.1` | `net461` | | `v4.7.2` | `net472` | | `v4.8` | `net48` | | (migrating to .NET 6) | `net6.0` | | (migrating to .NET 8) | `net8.0` | ### Step 3: Remove Explicit File Includes **BEFORE:** ```xml ``` **AFTER:** Delete all of these `` and `` item groups entirely. SDK-style projects include them automatically via implicit globbing. **Exception:** keep explicit entries only for files that need special metadata or reside outside the project directory: ```xml ``` ### Step 4: Remove AssemblyInfo.cs **BEFORE** (`Properties\AssemblyInfo.cs`): ```csharp using System.Reflection; using System.Runtime.InteropServices; [assembly: AssemblyTitle("MyLibrary")] [assembly: AssemblyDescription("A useful library")] [assembly: AssemblyCompany("Contoso")] [assembly: AssemblyProduct("MyLibrary")] [assembly: AssemblyCopyright("Copyright © Contoso 2024")] [assembly: ComVisible(false)] [assembly: Guid("...")] [assembly: AssemblyVersion("1.2.0.0")] [assembly: AssemblyFileVersion("1.2.0.0")] ``` **AFTER** (in `.csproj`): ```xml MyLibrary A useful library Contoso MyLibrary Copyright © Contoso 2024 1.2.0 ``` Delete `Properties\AssemblyInfo.cs` — the SDK auto-generates assembly attributes from these properties. **Alternative:** if you prefer to keep `AssemblyInfo.cs`, disable auto-generation: ```xml false ``` ### Step 5: Migrate packages.config → PackageReference **BEFORE** (`packages.config`): ```xml ``` **AFTER** (in `.csproj`): ```xml ``` Delete `packages.config` after migration. **Migration options:** - **Visual Studio:** right-click `packages.config` → *Migrate packages.config to PackageReference* - **CLI:** `dotnet migrate-packages-config` or manual conversion - **Binding redirects:** SDK-style projects auto-generate binding redirects — remove the `` section from `app.config` if present ### Step 6: Remove Unnecessary Boilerplate Delete all of the following — the SDK provides sensible defaults: ```xml Debug AnyCPU {...} Library Properties 512 true true true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 ``` **Keep** only properties that differ from SDK defaults (e.g., `Exe`, `` if it differs from the assembly name, custom ``). ### Step 7: Enable Modern Features After migration, consider enabling modern C# features: ```xml net8.0 enable enable ``` - `enable` — enables nullable reference type analysis - `enable` — auto-imports common namespaces (.NET 6+) - **Avoid `latest`** — the effective language version is determined by the SDK/compiler defaults, not just the TFM, so builds can silently vary across machines with different SDKs installed. Omit `` unless you need to pin a specific version. For reproducible builds, pin the SDK version repo-wide with `global.json` (which indirectly fixes the default language version), or set an explicit numeric `` (e.g. `12`) per project to directly control the language version. ## Complete Before/After Example **BEFORE** (legacy — 65 lines): ```xml Debug AnyCPU {12345678-1234-1234-1234-123456789ABC} Library Properties MyLibrary MyLibrary v4.7.2 512 true true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 ``` **AFTER** (SDK-style — 11 lines): ```xml net472 ``` ## Common Migration Issues **Embedded resources:** files not in a standard location may need explicit includes: ```xml ``` **Content files with CopyToOutputDirectory:** these still need explicit entries: ```xml ``` **Multi-targeting:** change the element name from singular to plural: ```xml net8.0 net472;net8.0 ``` **WPF/WinForms projects:** use the appropriate SDK or properties: ```xml true true ``` **Test projects:** use the standard SDK with test framework packages: ```xml net8.0 false ``` ## Central Package Management Migration Centralizes NuGet version management across a multi-project solution. See [https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management](https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management) for details. **Step 1:** Create `Directory.Packages.props` at the repository root with `true` and `` items for all packages. **Step 2:** Remove `Version` from each project's `PackageReference`: ```xml ``` ## Directory.Build Consolidation Identify properties repeated across multiple `.csproj` files and move them to shared files. **`Directory.Build.props`** (for properties — placed at repo or src root): ```xml net8.0 enable enable true Contoso Copyright © Contoso 2024 ``` **`Directory.Build.targets`** (for targets/tasks — placed at repo or src root): ```xml ``` **Keep in individual `.csproj` files** only what is project-specific: ```xml Exe MyApp ``` ## Tools and Automation | Tool | Usage | |------|-------| | `dotnet try-convert` | Automated legacy-to-SDK conversion. Install: `dotnet tool install -g try-convert` | | .NET Upgrade Assistant | Full migration including API changes. Install: `dotnet tool install -g upgrade-assistant` | | Visual Studio | Right-click `packages.config` → *Migrate packages.config to PackageReference* | | Manual migration | Often cleanest for simple projects — follow the checklist above | **Recommended approach:** 1. Run `try-convert` for a first pass 2. Review and clean up the output manually 3. Build and fix any issues 4. Enable modern features (nullable, implicit usings) 5. Consolidate shared settings into `Directory.Build.props`