[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M699EY39PZX8PDCS968MBFPJ":3,"skill-same:01M699EY39PZX8PDCS968MBFPJ":74},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":40,"linkOnly":41,"markdownContent":42,"markdownBody":43,"frontMatter":44,"refName":45,"commitSha":46,"skillDirectory":47,"forksCount":48,"repositoryHtmlUrl":49,"repositoryHomepageUrl":6,"ownerHtmlUrl":50,"ownerBlogUrl":51,"ownerDescription":52,"updatedAt":53,"install":54,"resources":67},"01M699EY39PZX8PDCS968MBFPJ","msbuild-modernization",null,"Guide for modernizing and migrating MSBuild project files to SDK-style format. USE FOR: converting legacy .csproj\u002F.vbproj with verbose XML to SDK-style, migrating packages.config to PackageReference, removing Properties\u002FAssemblyInfo.cs in favor of auto-generation, eliminating explicit Compile Include lists via implicit globbing, consolidating shared settings into Directory.Build.props. Indicators of legacy projects: ToolsVersion attribute, an Import of Microsoft.CSharp.targets via $(MSBuildToolsPath), .csproj files over 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.","microsoft\u002Ftestfx","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M699C71CJ91B5WZ3AXG3GG2M",1058,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fmsbuild-modernization\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,9,39],"msbuild modernization","msbuild","modernization","maven","csproj","move","import","project","shared","do","files","auto","guide","projects","directory","framework","csharp","packages","cmake","config","sdk","2026-11-10 10:12:30",false,"---\nname: msbuild-modernization\ndescription: \"Guide for modernizing and migrating MSBuild project files to SDK-style format. USE FOR: converting legacy .csproj\u002F.vbproj with verbose XML to SDK-style, migrating packages.config to PackageReference, removing Properties\u002FAssemblyInfo.cs in favor of auto-generation, eliminating explicit Compile Include lists via implicit globbing, consolidating shared settings into Directory.Build.props. Indicators of legacy projects: ToolsVersion attribute, an Import of Microsoft.CSharp.targets via $(MSBuildToolsPath), .csproj files over 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.\"\nlicense: MIT\n---\n\n# MSBuild Modernization: Legacy to SDK-style Migration\n\n## Identifying Legacy vs SDK-style Projects\n\n**Legacy indicators:**\n\n- `\u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>`\n- Explicit file lists (`\u003CCompile Include=\"...\" \u002F>` for every `.cs` file)\n- `ToolsVersion` attribute on `\u003CProject>` element\n- `packages.config` file present\n- `Properties\\AssemblyInfo.cs` with assembly-level attributes\n\n**SDK-style indicators:**\n\n- `\u003CProject Sdk=\"Microsoft.NET.Sdk\">` attribute on root element\n- Minimal content — a simple project may be 10–15 lines\n- No explicit file includes (implicit globbing)\n- `\u003CPackageReference>` items instead of `packages.config`\n\n**Quick check:** if a `.csproj` is more than 50 lines for a simple class library or console app, it is likely legacy format.\n\n```xml\n\u003C!-- Legacy: ~80+ lines for a simple library -->\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003CProject ToolsVersion=\"15.0\" xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002Fdeveloper\u002Fmsbuild\u002F2003\">\n  \u003CImport Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" \u002F>\n  \u003CPropertyGroup>\n    \u003CConfiguration Condition=\" '$(Configuration)' == '' \">Debug\u003C\u002FConfiguration>\n    \u003CPlatform Condition=\" '$(Platform)' == '' \">AnyCPU\u003C\u002FPlatform>\n    \u003COutputType>Library\u003C\u002FOutputType>\n    \u003CRootNamespace>MyLibrary\u003C\u002FRootNamespace>\n    \u003CAssemblyName>MyLibrary\u003C\u002FAssemblyName>\n    \u003CTargetFrameworkVersion>v4.7.2\u003C\u002FTargetFrameworkVersion>\n    \u003CFileAlignment>512\u003C\u002FFileAlignment>\n    \u003CDeterministic>true\u003C\u002FDeterministic>\n  \u003C\u002FPropertyGroup>\n  \u003C!-- ... 60+ more lines ... -->\n  \u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>\n\u003C\u002FProject>\n```\n\n```xml\n\u003C!-- SDK-style: ~8 lines for the same library -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003CTargetFramework>net472\u003C\u002FTargetFramework>\n  \u003C\u002FPropertyGroup>\n\u003C\u002FProject>\n```\n\n## Migration Checklist: Legacy → SDK-style\n\n### Step 1: Replace Project Root Element\n\n**BEFORE:**\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003CProject ToolsVersion=\"15.0\" xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002Fdeveloper\u002Fmsbuild\u002F2003\">\n  \u003CImport Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\"\n          Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" \u002F>\n  \u003C!-- ... project content ... -->\n  \u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>\n\u003C\u002FProject>\n```\n\n**AFTER:**\n\n```xml\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003C!-- ... project content ... -->\n\u003C\u002FProject>\n```\n\nRemove the XML declaration, `ToolsVersion`, `xmlns`, and both `\u003CImport>` lines. The `Sdk` attribute replaces all of them.\n\n### Step 2: Set TargetFramework\n\n**BEFORE:**\n\n```xml\n\u003CPropertyGroup>\n  \u003CTargetFrameworkVersion>v4.7.2\u003C\u002FTargetFrameworkVersion>\n\u003C\u002FPropertyGroup>\n```\n\n**AFTER:**\n\n```xml\n\u003CPropertyGroup>\n  \u003CTargetFramework>net472\u003C\u002FTargetFramework>\n\u003C\u002FPropertyGroup>\n```\n\n**TFM mapping table:**\n\n| Legacy `TargetFrameworkVersion` | SDK-style `TargetFramework` |\n|---------------------------------|-----------------------------|\n| `v4.6.1`                        | `net461`                    |\n| `v4.7.2`                        | `net472`                    |\n| `v4.8`                          | `net48`                     |\n| (migrating to .NET 6)           | `net6.0`                    |\n| (migrating to .NET 8)           | `net8.0`                    |\n\n### Step 3: Remove Explicit File Includes\n\n**BEFORE:**\n\n```xml\n\u003CItemGroup>\n  \u003CCompile Include=\"Controllers\\HomeController.cs\" \u002F>\n  \u003CCompile Include=\"Models\\User.cs\" \u002F>\n  \u003CCompile Include=\"Models\\Order.cs\" \u002F>\n  \u003CCompile Include=\"Services\\AuthService.cs\" \u002F>\n  \u003CCompile Include=\"Services\\OrderService.cs\" \u002F>\n  \u003CCompile Include=\"Properties\\AssemblyInfo.cs\" \u002F>\n  \u003C!-- ... 50+ more lines ... -->\n\u003C\u002FItemGroup>\n\u003CItemGroup>\n  \u003CContent Include=\"Views\\Home\\Index.cshtml\" \u002F>\n  \u003CContent Include=\"Views\\Shared\\_Layout.cshtml\" \u002F>\n  \u003C!-- ... more content files ... -->\n\u003C\u002FItemGroup>\n```\n\n**AFTER:**\n\nDelete all of these `\u003CCompile>` and `\u003CContent>` item groups entirely. SDK-style projects include them automatically via implicit globbing.\n\n**Exception:** keep explicit entries only for files that need special metadata or reside outside the project directory:\n\n```xml\n\u003CItemGroup>\n  \u003CContent Include=\"..\\shared\\config.json\" Link=\"config.json\" CopyToOutputDirectory=\"PreserveNewest\" \u002F>\n\u003C\u002FItemGroup>\n```\n\n### Step 4: Remove AssemblyInfo.cs\n\n**BEFORE** (`Properties\\AssemblyInfo.cs`):\n\n```csharp\nusing System.Reflection;\nusing System.Runtime.InteropServices;\n\n[assembly: AssemblyTitle(\"MyLibrary\")]\n[assembly: AssemblyDescription(\"A useful library\")]\n[assembly: AssemblyCompany(\"Contoso\")]\n[assembly: AssemblyProduct(\"MyLibrary\")]\n[assembly: AssemblyCopyright(\"Copyright © Contoso 2024\")]\n[assembly: ComVisible(false)]\n[assembly: Guid(\"...\")]\n[assembly: AssemblyVersion(\"1.2.0.0\")]\n[assembly: AssemblyFileVersion(\"1.2.0.0\")]\n```\n\n**AFTER** (in `.csproj`):\n\n```xml\n\u003CPropertyGroup>\n  \u003CAssemblyTitle>MyLibrary\u003C\u002FAssemblyTitle>\n  \u003CDescription>A useful library\u003C\u002FDescription>\n  \u003CCompany>Contoso\u003C\u002FCompany>\n  \u003CProduct>MyLibrary\u003C\u002FProduct>\n  \u003CCopyright>Copyright © Contoso 2024\u003C\u002FCopyright>\n  \u003CVersion>1.2.0\u003C\u002FVersion>\n\u003C\u002FPropertyGroup>\n```\n\nDelete `Properties\\AssemblyInfo.cs` — the SDK auto-generates assembly attributes from these properties.\n\n**Alternative:** if you prefer to keep `AssemblyInfo.cs`, disable auto-generation:\n\n```xml\n\u003CPropertyGroup>\n  \u003CGenerateAssemblyInfo>false\u003C\u002FGenerateAssemblyInfo>\n\u003C\u002FPropertyGroup>\n```\n\n### Step 5: Migrate packages.config → PackageReference\n\n**BEFORE** (`packages.config`):\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003Cpackages>\n  \u003Cpackage id=\"Newtonsoft.Json\" version=\"13.0.3\" targetFramework=\"net472\" \u002F>\n  \u003Cpackage id=\"Serilog\" version=\"3.1.1\" targetFramework=\"net472\" \u002F>\n  \u003Cpackage id=\"Microsoft.Extensions.DependencyInjection\" version=\"8.0.0\" targetFramework=\"net472\" \u002F>\n\u003C\u002Fpackages>\n```\n\n**AFTER** (in `.csproj`):\n\n```xml\n\u003CItemGroup>\n  \u003CPackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" \u002F>\n  \u003CPackageReference Include=\"Serilog\" Version=\"3.1.1\" \u002F>\n  \u003CPackageReference Include=\"Microsoft.Extensions.DependencyInjection\" Version=\"8.0.0\" \u002F>\n\u003C\u002FItemGroup>\n```\n\nDelete `packages.config` after migration.\n\n**Migration options:**\n\n- **Visual Studio:** right-click `packages.config` → *Migrate packages.config to PackageReference*\n- **CLI:** `dotnet migrate-packages-config` or manual conversion\n- **Binding redirects:** SDK-style projects auto-generate binding redirects — remove the `\u003Cruntime>` section from `app.config` if present\n\n### Step 6: Remove Unnecessary Boilerplate\n\nDelete all of the following — the SDK provides sensible defaults:\n\n```xml\n\u003C!-- DELETE: SDK imports (replaced by Sdk attribute) -->\n\u003CImport Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" ... \u002F>\n\u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>\n\n\u003C!-- DELETE: default Configuration\u002FPlatform (SDK provides these) -->\n\u003CPropertyGroup>\n  \u003CConfiguration Condition=\" '$(Configuration)' == '' \">Debug\u003C\u002FConfiguration>\n  \u003CPlatform Condition=\" '$(Platform)' == '' \">AnyCPU\u003C\u002FPlatform>\n  \u003CProjectGuid>{...}\u003C\u002FProjectGuid>\n  \u003COutputType>Library\u003C\u002FOutputType>  \u003C!-- keep only if not Library -->\n  \u003CAppDesignerFolder>Properties\u003C\u002FAppDesignerFolder>\n  \u003CFileAlignment>512\u003C\u002FFileAlignment>\n  \u003CAutoGenerateBindingRedirects>true\u003C\u002FAutoGenerateBindingRedirects>\n  \u003CDeterministic>true\u003C\u002FDeterministic>\n\u003C\u002FPropertyGroup>\n\n\u003C!-- DELETE: standard Debug\u002FRelease configurations (SDK defaults match) -->\n\u003CPropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n  \u003CDebugSymbols>true\u003C\u002FDebugSymbols>\n  \u003CDebugType>full\u003C\u002FDebugType>\n  \u003COptimize>false\u003C\u002FOptimize>\n  \u003COutputPath>bin\\Debug\\\u003C\u002FOutputPath>\n  \u003CDefineConstants>DEBUG;TRACE\u003C\u002FDefineConstants>\n  \u003CErrorReport>prompt\u003C\u002FErrorReport>\n  \u003CWarningLevel>4\u003C\u002FWarningLevel>\n\u003C\u002FPropertyGroup>\n\u003CPropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n  \u003CDebugType>pdbonly\u003C\u002FDebugType>\n  \u003COptimize>true\u003C\u002FOptimize>\n  \u003COutputPath>bin\\Release\\\u003C\u002FOutputPath>\n  \u003CDefineConstants>TRACE\u003C\u002FDefineConstants>\n  \u003CErrorReport>prompt\u003C\u002FErrorReport>\n  \u003CWarningLevel>4\u003C\u002FWarningLevel>\n\u003C\u002FPropertyGroup>\n\n\u003C!-- DELETE: framework assembly references (implicit in SDK) -->\n\u003CItemGroup>\n  \u003CReference Include=\"System\" \u002F>\n  \u003CReference Include=\"System.Core\" \u002F>\n  \u003CReference Include=\"System.Data\" \u002F>\n  \u003CReference Include=\"System.Xml\" \u002F>\n  \u003CReference Include=\"System.Xml.Linq\" \u002F>\n  \u003CReference Include=\"Microsoft.CSharp\" \u002F>\n\u003C\u002FItemGroup>\n\n\u003C!-- DELETE: packages.config reference -->\n\u003CNone Include=\"packages.config\" \u002F>\n\n\u003C!-- DELETE: designer service entries -->\n\u003CService Include=\"{508349B6-6B84-11D3-8410-00C04F8EF8E0}\" \u002F>\n```\n\n**Keep** only properties that differ from SDK defaults (e.g., `\u003COutputType>Exe\u003C\u002FOutputType>`, `\u003CRootNamespace>` if it differs from the assembly name, custom `\u003CDefineConstants>`).\n\n### Step 7: Enable Modern Features\n\nAfter migration, consider enabling modern C# features:\n\n```xml\n\u003CPropertyGroup>\n  \u003CTargetFramework>net8.0\u003C\u002FTargetFramework>\n  \u003CNullable>enable\u003C\u002FNullable>\n  \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n\u003C\u002FPropertyGroup>\n```\n\n- `\u003CNullable>enable\u003C\u002FNullable>` — enables nullable reference type analysis\n- `\u003CImplicitUsings>enable\u003C\u002FImplicitUsings>` — auto-imports common namespaces (.NET 6+)\n- **Avoid `\u003CLangVersion>latest`** — the effective language version is determined by the SDK\u002Fcompiler defaults, not just the TFM, so builds can silently vary across machines with different SDKs installed. Omit `\u003CLangVersion>` 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 `\u003CLangVersion>` (e.g. `\u003CLangVersion>12\u003C\u002FLangVersion>`) per project to directly control the language version.\n\n## Complete Before\u002FAfter Example\n\n**BEFORE** (legacy — 65 lines):\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003CProject ToolsVersion=\"15.0\" xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002Fdeveloper\u002Fmsbuild\u002F2003\">\n  \u003CImport Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\"\n          Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" \u002F>\n  \u003CPropertyGroup>\n    \u003CConfiguration Condition=\" '$(Configuration)' == '' \">Debug\u003C\u002FConfiguration>\n    \u003CPlatform Condition=\" '$(Platform)' == '' \">AnyCPU\u003C\u002FPlatform>\n    \u003CProjectGuid>{12345678-1234-1234-1234-123456789ABC}\u003C\u002FProjectGuid>\n    \u003COutputType>Library\u003C\u002FOutputType>\n    \u003CAppDesignerFolder>Properties\u003C\u002FAppDesignerFolder>\n    \u003CRootNamespace>MyLibrary\u003C\u002FRootNamespace>\n    \u003CAssemblyName>MyLibrary\u003C\u002FAssemblyName>\n    \u003CTargetFrameworkVersion>v4.7.2\u003C\u002FTargetFrameworkVersion>\n    \u003CFileAlignment>512\u003C\u002FFileAlignment>\n    \u003CDeterministic>true\u003C\u002FDeterministic>\n  \u003C\u002FPropertyGroup>\n  \u003CPropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    \u003CDebugSymbols>true\u003C\u002FDebugSymbols>\n    \u003CDebugType>full\u003C\u002FDebugType>\n    \u003COptimize>false\u003C\u002FOptimize>\n    \u003COutputPath>bin\\Debug\\\u003C\u002FOutputPath>\n    \u003CDefineConstants>DEBUG;TRACE\u003C\u002FDefineConstants>\n    \u003CErrorReport>prompt\u003C\u002FErrorReport>\n    \u003CWarningLevel>4\u003C\u002FWarningLevel>\n  \u003C\u002FPropertyGroup>\n  \u003CPropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    \u003CDebugType>pdbonly\u003C\u002FDebugType>\n    \u003COptimize>true\u003C\u002FOptimize>\n    \u003COutputPath>bin\\Release\\\u003C\u002FOutputPath>\n    \u003CDefineConstants>TRACE\u003C\u002FDefineConstants>\n    \u003CErrorReport>prompt\u003C\u002FErrorReport>\n    \u003CWarningLevel>4\u003C\u002FWarningLevel>\n  \u003C\u002FPropertyGroup>\n  \u003CItemGroup>\n    \u003CReference Include=\"System\" \u002F>\n    \u003CReference Include=\"System.Core\" \u002F>\n    \u003CReference Include=\"System.Xml.Linq\" \u002F>\n    \u003CReference Include=\"Microsoft.CSharp\" \u002F>\n  \u003C\u002FItemGroup>\n  \u003CItemGroup>\n    \u003CCompile Include=\"Models\\User.cs\" \u002F>\n    \u003CCompile Include=\"Models\\Order.cs\" \u002F>\n    \u003CCompile Include=\"Services\\UserService.cs\" \u002F>\n    \u003CCompile Include=\"Services\\OrderService.cs\" \u002F>\n    \u003CCompile Include=\"Helpers\\StringExtensions.cs\" \u002F>\n    \u003CCompile Include=\"Properties\\AssemblyInfo.cs\" \u002F>\n  \u003C\u002FItemGroup>\n  \u003CItemGroup>\n    \u003CNone Include=\"packages.config\" \u002F>\n  \u003C\u002FItemGroup>\n  \u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>\n\u003C\u002FProject>\n```\n\n**AFTER** (SDK-style — 11 lines):\n\n```xml\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003CTargetFramework>net472\u003C\u002FTargetFramework>\n  \u003C\u002FPropertyGroup>\n  \u003CItemGroup>\n    \u003CPackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" \u002F>\n    \u003CPackageReference Include=\"Serilog\" Version=\"3.1.1\" \u002F>\n  \u003C\u002FItemGroup>\n\u003C\u002FProject>\n```\n\n## Common Migration Issues\n\n**Embedded resources:** files not in a standard location may need explicit includes:\n\n```xml\n\u003CItemGroup>\n  \u003CEmbeddedResource Include=\"..\\shared\\Schemas\\*.xsd\" LinkBase=\"Schemas\" \u002F>\n\u003C\u002FItemGroup>\n```\n\n**Content files with CopyToOutputDirectory:** these still need explicit entries:\n\n```xml\n\u003CItemGroup>\n  \u003CContent Include=\"appsettings.json\" CopyToOutputDirectory=\"PreserveNewest\" \u002F>\n  \u003CNone Include=\"scripts\\*.sql\" CopyToOutputDirectory=\"PreserveNewest\" \u002F>\n\u003C\u002FItemGroup>\n```\n\n**Multi-targeting:** change the element name from singular to plural:\n\n```xml\n\u003C!-- Single target -->\n\u003CTargetFramework>net8.0\u003C\u002FTargetFramework>\n\n\u003C!-- Multiple targets -->\n\u003CTargetFrameworks>net472;net8.0\u003C\u002FTargetFrameworks>\n```\n\n**WPF\u002FWinForms projects:** use the appropriate SDK or properties:\n\n```xml\n\u003C!-- Option A: WindowsDesktop SDK -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk.WindowsDesktop\">\n\n\u003C!-- Option B: properties in standard SDK (preferred for .NET 5+) -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003CUseWPF>true\u003C\u002FUseWPF>\n    \u003C!-- or -->\n    \u003CUseWindowsForms>true\u003C\u002FUseWindowsForms>\n  \u003C\u002FPropertyGroup>\n\u003C\u002FProject>\n```\n\n**Test projects:** use the standard SDK with test framework packages:\n\n```xml\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003CTargetFramework>net8.0\u003C\u002FTargetFramework>\n    \u003CIsPackable>false\u003C\u002FIsPackable>\n  \u003C\u002FPropertyGroup>\n  \u003CItemGroup>\n    \u003CPackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.9.0\" \u002F>\n    \u003CPackageReference Include=\"xunit\" Version=\"2.7.0\" \u002F>\n    \u003CPackageReference Include=\"xunit.runner.visualstudio\" Version=\"2.5.7\" \u002F>\n  \u003C\u002FItemGroup>\n\u003C\u002FProject>\n```\n\n## Central Package Management Migration\n\nCentralizes NuGet version management across a multi-project solution. See [https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fnuget\u002Fconsume-packages\u002Fcentral-package-management](https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fnuget\u002Fconsume-packages\u002Fcentral-package-management) for details.\n\n**Step 1:** Create `Directory.Packages.props` at the repository root with `\u003CManagePackageVersionsCentrally>true\u003C\u002FManagePackageVersionsCentrally>` and `\u003CPackageVersion>` items for all packages.\n\n**Step 2:** Remove `Version` from each project's `PackageReference`:\n\n```xml\n\u003C!-- BEFORE -->\n\u003CPackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" \u002F>\n\n\u003C!-- AFTER -->\n\u003CPackageReference Include=\"Newtonsoft.Json\" \u002F>\n```\n\n## Directory.Build Consolidation\n\nIdentify properties repeated across multiple `.csproj` files and move them to shared files.\n\n**`Directory.Build.props`** (for properties — placed at repo or src root):\n\n```xml\n\u003CProject>\n  \u003CPropertyGroup>\n    \u003CTargetFramework>net8.0\u003C\u002FTargetFramework>\n    \u003CNullable>enable\u003C\u002FNullable>\n    \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n    \u003CTreatWarningsAsErrors>true\u003C\u002FTreatWarningsAsErrors>\n    \u003CCompany>Contoso\u003C\u002FCompany>\n    \u003CCopyright>Copyright © Contoso 2024\u003C\u002FCopyright>\n  \u003C\u002FPropertyGroup>\n\u003C\u002FProject>\n```\n\n**`Directory.Build.targets`** (for targets\u002Ftasks — placed at repo or src root):\n\n```xml\n\u003CProject>\n  \u003CTarget Name=\"PrintBuildInfo\" AfterTargets=\"Build\">\n    \u003CMessage Importance=\"High\" Text=\"Built $(AssemblyName) → $(TargetPath)\" \u002F>\n  \u003C\u002FTarget>\n\u003C\u002FProject>\n```\n\n**Keep in individual `.csproj` files** only what is project-specific:\n\n```xml\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003COutputType>Exe\u003C\u002FOutputType>\n    \u003CAssemblyName>MyApp\u003C\u002FAssemblyName>\n  \u003C\u002FPropertyGroup>\n  \u003CItemGroup>\n    \u003CPackageReference Include=\"Serilog\" \u002F>\n    \u003CProjectReference Include=\"..\\MyLibrary\\MyLibrary.csproj\" \u002F>\n  \u003C\u002FItemGroup>\n\u003C\u002FProject>\n```\n\n## Tools and Automation\n\n| Tool | Usage |\n|------|-------|\n| `dotnet try-convert` | Automated legacy-to-SDK conversion. Install: `dotnet tool install -g try-convert` |\n| .NET Upgrade Assistant | Full migration including API changes. Install: `dotnet tool install -g upgrade-assistant` |\n| Visual Studio | Right-click `packages.config` → *Migrate packages.config to PackageReference* |\n| Manual migration | Often cleanest for simple projects — follow the checklist above |\n\n**Recommended approach:**\n\n1. Run `try-convert` for a first pass\n2. Review and clean up the output manually\n3. Build and fix any issues\n4. Enable modern features (nullable, implicit usings)\n5. Consolidate shared settings into `Directory.Build.props`\n","# MSBuild Modernization: Legacy to SDK-style Migration\n\n## Identifying Legacy vs SDK-style Projects\n\n**Legacy indicators:**\n\n- `\u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>`\n- Explicit file lists (`\u003CCompile Include=\"...\" \u002F>` for every `.cs` file)\n- `ToolsVersion` attribute on `\u003CProject>` element\n- `packages.config` file present\n- `Properties\\AssemblyInfo.cs` with assembly-level attributes\n\n**SDK-style indicators:**\n\n- `\u003CProject Sdk=\"Microsoft.NET.Sdk\">` attribute on root element\n- Minimal content — a simple project may be 10–15 lines\n- No explicit file includes (implicit globbing)\n- `\u003CPackageReference>` items instead of `packages.config`\n\n**Quick check:** if a `.csproj` is more than 50 lines for a simple class library or console app, it is likely legacy format.\n\n```xml\n\u003C!-- Legacy: ~80+ lines for a simple library -->\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003CProject ToolsVersion=\"15.0\" xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002Fdeveloper\u002Fmsbuild\u002F2003\">\n  \u003CImport Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" \u002F>\n  \u003CPropertyGroup>\n    \u003CConfiguration Condition=\" '$(Configuration)' == '' \">Debug\u003C\u002FConfiguration>\n    \u003CPlatform Condition=\" '$(Platform)' == '' \">AnyCPU\u003C\u002FPlatform>\n    \u003COutputType>Library\u003C\u002FOutputType>\n    \u003CRootNamespace>MyLibrary\u003C\u002FRootNamespace>\n    \u003CAssemblyName>MyLibrary\u003C\u002FAssemblyName>\n    \u003CTargetFrameworkVersion>v4.7.2\u003C\u002FTargetFrameworkVersion>\n    \u003CFileAlignment>512\u003C\u002FFileAlignment>\n    \u003CDeterministic>true\u003C\u002FDeterministic>\n  \u003C\u002FPropertyGroup>\n  \u003C!-- ... 60+ more lines ... -->\n  \u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>\n\u003C\u002FProject>\n```\n\n```xml\n\u003C!-- SDK-style: ~8 lines for the same library -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003CTargetFramework>net472\u003C\u002FTargetFramework>\n  \u003C\u002FPropertyGroup>\n\u003C\u002FProject>\n```\n\n## Migration Checklist: Legacy → SDK-style\n\n### Step 1: Replace Project Root Element\n\n**BEFORE:**\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003CProject ToolsVersion=\"15.0\" xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002Fdeveloper\u002Fmsbuild\u002F2003\">\n  \u003CImport Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\"\n          Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" \u002F>\n  \u003C!-- ... project content ... -->\n  \u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>\n\u003C\u002FProject>\n```\n\n**AFTER:**\n\n```xml\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003C!-- ... project content ... -->\n\u003C\u002FProject>\n```\n\nRemove the XML declaration, `ToolsVersion`, `xmlns`, and both `\u003CImport>` lines. The `Sdk` attribute replaces all of them.\n\n### Step 2: Set TargetFramework\n\n**BEFORE:**\n\n```xml\n\u003CPropertyGroup>\n  \u003CTargetFrameworkVersion>v4.7.2\u003C\u002FTargetFrameworkVersion>\n\u003C\u002FPropertyGroup>\n```\n\n**AFTER:**\n\n```xml\n\u003CPropertyGroup>\n  \u003CTargetFramework>net472\u003C\u002FTargetFramework>\n\u003C\u002FPropertyGroup>\n```\n\n**TFM mapping table:**\n\n| Legacy `TargetFrameworkVersion` | SDK-style `TargetFramework` |\n|---------------------------------|-----------------------------|\n| `v4.6.1`                        | `net461`                    |\n| `v4.7.2`                        | `net472`                    |\n| `v4.8`                          | `net48`                     |\n| (migrating to .NET 6)           | `net6.0`                    |\n| (migrating to .NET 8)           | `net8.0`                    |\n\n### Step 3: Remove Explicit File Includes\n\n**BEFORE:**\n\n```xml\n\u003CItemGroup>\n  \u003CCompile Include=\"Controllers\\HomeController.cs\" \u002F>\n  \u003CCompile Include=\"Models\\User.cs\" \u002F>\n  \u003CCompile Include=\"Models\\Order.cs\" \u002F>\n  \u003CCompile Include=\"Services\\AuthService.cs\" \u002F>\n  \u003CCompile Include=\"Services\\OrderService.cs\" \u002F>\n  \u003CCompile Include=\"Properties\\AssemblyInfo.cs\" \u002F>\n  \u003C!-- ... 50+ more lines ... -->\n\u003C\u002FItemGroup>\n\u003CItemGroup>\n  \u003CContent Include=\"Views\\Home\\Index.cshtml\" \u002F>\n  \u003CContent Include=\"Views\\Shared\\_Layout.cshtml\" \u002F>\n  \u003C!-- ... more content files ... -->\n\u003C\u002FItemGroup>\n```\n\n**AFTER:**\n\nDelete all of these `\u003CCompile>` and `\u003CContent>` item groups entirely. SDK-style projects include them automatically via implicit globbing.\n\n**Exception:** keep explicit entries only for files that need special metadata or reside outside the project directory:\n\n```xml\n\u003CItemGroup>\n  \u003CContent Include=\"..\\shared\\config.json\" Link=\"config.json\" CopyToOutputDirectory=\"PreserveNewest\" \u002F>\n\u003C\u002FItemGroup>\n```\n\n### Step 4: Remove AssemblyInfo.cs\n\n**BEFORE** (`Properties\\AssemblyInfo.cs`):\n\n```csharp\nusing System.Reflection;\nusing System.Runtime.InteropServices;\n\n[assembly: AssemblyTitle(\"MyLibrary\")]\n[assembly: AssemblyDescription(\"A useful library\")]\n[assembly: AssemblyCompany(\"Contoso\")]\n[assembly: AssemblyProduct(\"MyLibrary\")]\n[assembly: AssemblyCopyright(\"Copyright © Contoso 2024\")]\n[assembly: ComVisible(false)]\n[assembly: Guid(\"...\")]\n[assembly: AssemblyVersion(\"1.2.0.0\")]\n[assembly: AssemblyFileVersion(\"1.2.0.0\")]\n```\n\n**AFTER** (in `.csproj`):\n\n```xml\n\u003CPropertyGroup>\n  \u003CAssemblyTitle>MyLibrary\u003C\u002FAssemblyTitle>\n  \u003CDescription>A useful library\u003C\u002FDescription>\n  \u003CCompany>Contoso\u003C\u002FCompany>\n  \u003CProduct>MyLibrary\u003C\u002FProduct>\n  \u003CCopyright>Copyright © Contoso 2024\u003C\u002FCopyright>\n  \u003CVersion>1.2.0\u003C\u002FVersion>\n\u003C\u002FPropertyGroup>\n```\n\nDelete `Properties\\AssemblyInfo.cs` — the SDK auto-generates assembly attributes from these properties.\n\n**Alternative:** if you prefer to keep `AssemblyInfo.cs`, disable auto-generation:\n\n```xml\n\u003CPropertyGroup>\n  \u003CGenerateAssemblyInfo>false\u003C\u002FGenerateAssemblyInfo>\n\u003C\u002FPropertyGroup>\n```\n\n### Step 5: Migrate packages.config → PackageReference\n\n**BEFORE** (`packages.config`):\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003Cpackages>\n  \u003Cpackage id=\"Newtonsoft.Json\" version=\"13.0.3\" targetFramework=\"net472\" \u002F>\n  \u003Cpackage id=\"Serilog\" version=\"3.1.1\" targetFramework=\"net472\" \u002F>\n  \u003Cpackage id=\"Microsoft.Extensions.DependencyInjection\" version=\"8.0.0\" targetFramework=\"net472\" \u002F>\n\u003C\u002Fpackages>\n```\n\n**AFTER** (in `.csproj`):\n\n```xml\n\u003CItemGroup>\n  \u003CPackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" \u002F>\n  \u003CPackageReference Include=\"Serilog\" Version=\"3.1.1\" \u002F>\n  \u003CPackageReference Include=\"Microsoft.Extensions.DependencyInjection\" Version=\"8.0.0\" \u002F>\n\u003C\u002FItemGroup>\n```\n\nDelete `packages.config` after migration.\n\n**Migration options:**\n\n- **Visual Studio:** right-click `packages.config` → *Migrate packages.config to PackageReference*\n- **CLI:** `dotnet migrate-packages-config` or manual conversion\n- **Binding redirects:** SDK-style projects auto-generate binding redirects — remove the `\u003Cruntime>` section from `app.config` if present\n\n### Step 6: Remove Unnecessary Boilerplate\n\nDelete all of the following — the SDK provides sensible defaults:\n\n```xml\n\u003C!-- DELETE: SDK imports (replaced by Sdk attribute) -->\n\u003CImport Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" ... \u002F>\n\u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>\n\n\u003C!-- DELETE: default Configuration\u002FPlatform (SDK provides these) -->\n\u003CPropertyGroup>\n  \u003CConfiguration Condition=\" '$(Configuration)' == '' \">Debug\u003C\u002FConfiguration>\n  \u003CPlatform Condition=\" '$(Platform)' == '' \">AnyCPU\u003C\u002FPlatform>\n  \u003CProjectGuid>{...}\u003C\u002FProjectGuid>\n  \u003COutputType>Library\u003C\u002FOutputType>  \u003C!-- keep only if not Library -->\n  \u003CAppDesignerFolder>Properties\u003C\u002FAppDesignerFolder>\n  \u003CFileAlignment>512\u003C\u002FFileAlignment>\n  \u003CAutoGenerateBindingRedirects>true\u003C\u002FAutoGenerateBindingRedirects>\n  \u003CDeterministic>true\u003C\u002FDeterministic>\n\u003C\u002FPropertyGroup>\n\n\u003C!-- DELETE: standard Debug\u002FRelease configurations (SDK defaults match) -->\n\u003CPropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n  \u003CDebugSymbols>true\u003C\u002FDebugSymbols>\n  \u003CDebugType>full\u003C\u002FDebugType>\n  \u003COptimize>false\u003C\u002FOptimize>\n  \u003COutputPath>bin\\Debug\\\u003C\u002FOutputPath>\n  \u003CDefineConstants>DEBUG;TRACE\u003C\u002FDefineConstants>\n  \u003CErrorReport>prompt\u003C\u002FErrorReport>\n  \u003CWarningLevel>4\u003C\u002FWarningLevel>\n\u003C\u002FPropertyGroup>\n\u003CPropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n  \u003CDebugType>pdbonly\u003C\u002FDebugType>\n  \u003COptimize>true\u003C\u002FOptimize>\n  \u003COutputPath>bin\\Release\\\u003C\u002FOutputPath>\n  \u003CDefineConstants>TRACE\u003C\u002FDefineConstants>\n  \u003CErrorReport>prompt\u003C\u002FErrorReport>\n  \u003CWarningLevel>4\u003C\u002FWarningLevel>\n\u003C\u002FPropertyGroup>\n\n\u003C!-- DELETE: framework assembly references (implicit in SDK) -->\n\u003CItemGroup>\n  \u003CReference Include=\"System\" \u002F>\n  \u003CReference Include=\"System.Core\" \u002F>\n  \u003CReference Include=\"System.Data\" \u002F>\n  \u003CReference Include=\"System.Xml\" \u002F>\n  \u003CReference Include=\"System.Xml.Linq\" \u002F>\n  \u003CReference Include=\"Microsoft.CSharp\" \u002F>\n\u003C\u002FItemGroup>\n\n\u003C!-- DELETE: packages.config reference -->\n\u003CNone Include=\"packages.config\" \u002F>\n\n\u003C!-- DELETE: designer service entries -->\n\u003CService Include=\"{508349B6-6B84-11D3-8410-00C04F8EF8E0}\" \u002F>\n```\n\n**Keep** only properties that differ from SDK defaults (e.g., `\u003COutputType>Exe\u003C\u002FOutputType>`, `\u003CRootNamespace>` if it differs from the assembly name, custom `\u003CDefineConstants>`).\n\n### Step 7: Enable Modern Features\n\nAfter migration, consider enabling modern C# features:\n\n```xml\n\u003CPropertyGroup>\n  \u003CTargetFramework>net8.0\u003C\u002FTargetFramework>\n  \u003CNullable>enable\u003C\u002FNullable>\n  \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n\u003C\u002FPropertyGroup>\n```\n\n- `\u003CNullable>enable\u003C\u002FNullable>` — enables nullable reference type analysis\n- `\u003CImplicitUsings>enable\u003C\u002FImplicitUsings>` — auto-imports common namespaces (.NET 6+)\n- **Avoid `\u003CLangVersion>latest`** — the effective language version is determined by the SDK\u002Fcompiler defaults, not just the TFM, so builds can silently vary across machines with different SDKs installed. Omit `\u003CLangVersion>` 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 `\u003CLangVersion>` (e.g. `\u003CLangVersion>12\u003C\u002FLangVersion>`) per project to directly control the language version.\n\n## Complete Before\u002FAfter Example\n\n**BEFORE** (legacy — 65 lines):\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003CProject ToolsVersion=\"15.0\" xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002Fdeveloper\u002Fmsbuild\u002F2003\">\n  \u003CImport Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\"\n          Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" \u002F>\n  \u003CPropertyGroup>\n    \u003CConfiguration Condition=\" '$(Configuration)' == '' \">Debug\u003C\u002FConfiguration>\n    \u003CPlatform Condition=\" '$(Platform)' == '' \">AnyCPU\u003C\u002FPlatform>\n    \u003CProjectGuid>{12345678-1234-1234-1234-123456789ABC}\u003C\u002FProjectGuid>\n    \u003COutputType>Library\u003C\u002FOutputType>\n    \u003CAppDesignerFolder>Properties\u003C\u002FAppDesignerFolder>\n    \u003CRootNamespace>MyLibrary\u003C\u002FRootNamespace>\n    \u003CAssemblyName>MyLibrary\u003C\u002FAssemblyName>\n    \u003CTargetFrameworkVersion>v4.7.2\u003C\u002FTargetFrameworkVersion>\n    \u003CFileAlignment>512\u003C\u002FFileAlignment>\n    \u003CDeterministic>true\u003C\u002FDeterministic>\n  \u003C\u002FPropertyGroup>\n  \u003CPropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    \u003CDebugSymbols>true\u003C\u002FDebugSymbols>\n    \u003CDebugType>full\u003C\u002FDebugType>\n    \u003COptimize>false\u003C\u002FOptimize>\n    \u003COutputPath>bin\\Debug\\\u003C\u002FOutputPath>\n    \u003CDefineConstants>DEBUG;TRACE\u003C\u002FDefineConstants>\n    \u003CErrorReport>prompt\u003C\u002FErrorReport>\n    \u003CWarningLevel>4\u003C\u002FWarningLevel>\n  \u003C\u002FPropertyGroup>\n  \u003CPropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    \u003CDebugType>pdbonly\u003C\u002FDebugType>\n    \u003COptimize>true\u003C\u002FOptimize>\n    \u003COutputPath>bin\\Release\\\u003C\u002FOutputPath>\n    \u003CDefineConstants>TRACE\u003C\u002FDefineConstants>\n    \u003CErrorReport>prompt\u003C\u002FErrorReport>\n    \u003CWarningLevel>4\u003C\u002FWarningLevel>\n  \u003C\u002FPropertyGroup>\n  \u003CItemGroup>\n    \u003CReference Include=\"System\" \u002F>\n    \u003CReference Include=\"System.Core\" \u002F>\n    \u003CReference Include=\"System.Xml.Linq\" \u002F>\n    \u003CReference Include=\"Microsoft.CSharp\" \u002F>\n  \u003C\u002FItemGroup>\n  \u003CItemGroup>\n    \u003CCompile Include=\"Models\\User.cs\" \u002F>\n    \u003CCompile Include=\"Models\\Order.cs\" \u002F>\n    \u003CCompile Include=\"Services\\UserService.cs\" \u002F>\n    \u003CCompile Include=\"Services\\OrderService.cs\" \u002F>\n    \u003CCompile Include=\"Helpers\\StringExtensions.cs\" \u002F>\n    \u003CCompile Include=\"Properties\\AssemblyInfo.cs\" \u002F>\n  \u003C\u002FItemGroup>\n  \u003CItemGroup>\n    \u003CNone Include=\"packages.config\" \u002F>\n  \u003C\u002FItemGroup>\n  \u003CImport Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" \u002F>\n\u003C\u002FProject>\n```\n\n**AFTER** (SDK-style — 11 lines):\n\n```xml\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003CTargetFramework>net472\u003C\u002FTargetFramework>\n  \u003C\u002FPropertyGroup>\n  \u003CItemGroup>\n    \u003CPackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" \u002F>\n    \u003CPackageReference Include=\"Serilog\" Version=\"3.1.1\" \u002F>\n  \u003C\u002FItemGroup>\n\u003C\u002FProject>\n```\n\n## Common Migration Issues\n\n**Embedded resources:** files not in a standard location may need explicit includes:\n\n```xml\n\u003CItemGroup>\n  \u003CEmbeddedResource Include=\"..\\shared\\Schemas\\*.xsd\" LinkBase=\"Schemas\" \u002F>\n\u003C\u002FItemGroup>\n```\n\n**Content files with CopyToOutputDirectory:** these still need explicit entries:\n\n```xml\n\u003CItemGroup>\n  \u003CContent Include=\"appsettings.json\" CopyToOutputDirectory=\"PreserveNewest\" \u002F>\n  \u003CNone Include=\"scripts\\*.sql\" CopyToOutputDirectory=\"PreserveNewest\" \u002F>\n\u003C\u002FItemGroup>\n```\n\n**Multi-targeting:** change the element name from singular to plural:\n\n```xml\n\u003C!-- Single target -->\n\u003CTargetFramework>net8.0\u003C\u002FTargetFramework>\n\n\u003C!-- Multiple targets -->\n\u003CTargetFrameworks>net472;net8.0\u003C\u002FTargetFrameworks>\n```\n\n**WPF\u002FWinForms projects:** use the appropriate SDK or properties:\n\n```xml\n\u003C!-- Option A: WindowsDesktop SDK -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk.WindowsDesktop\">\n\n\u003C!-- Option B: properties in standard SDK (preferred for .NET 5+) -->\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003CUseWPF>true\u003C\u002FUseWPF>\n    \u003C!-- or -->\n    \u003CUseWindowsForms>true\u003C\u002FUseWindowsForms>\n  \u003C\u002FPropertyGroup>\n\u003C\u002FProject>\n```\n\n**Test projects:** use the standard SDK with test framework packages:\n\n```xml\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003CTargetFramework>net8.0\u003C\u002FTargetFramework>\n    \u003CIsPackable>false\u003C\u002FIsPackable>\n  \u003C\u002FPropertyGroup>\n  \u003CItemGroup>\n    \u003CPackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.9.0\" \u002F>\n    \u003CPackageReference Include=\"xunit\" Version=\"2.7.0\" \u002F>\n    \u003CPackageReference Include=\"xunit.runner.visualstudio\" Version=\"2.5.7\" \u002F>\n  \u003C\u002FItemGroup>\n\u003C\u002FProject>\n```\n\n## Central Package Management Migration\n\nCentralizes NuGet version management across a multi-project solution. See [https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fnuget\u002Fconsume-packages\u002Fcentral-package-management](https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fnuget\u002Fconsume-packages\u002Fcentral-package-management) for details.\n\n**Step 1:** Create `Directory.Packages.props` at the repository root with `\u003CManagePackageVersionsCentrally>true\u003C\u002FManagePackageVersionsCentrally>` and `\u003CPackageVersion>` items for all packages.\n\n**Step 2:** Remove `Version` from each project's `PackageReference`:\n\n```xml\n\u003C!-- BEFORE -->\n\u003CPackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" \u002F>\n\n\u003C!-- AFTER -->\n\u003CPackageReference Include=\"Newtonsoft.Json\" \u002F>\n```\n\n## Directory.Build Consolidation\n\nIdentify properties repeated across multiple `.csproj` files and move them to shared files.\n\n**`Directory.Build.props`** (for properties — placed at repo or src root):\n\n```xml\n\u003CProject>\n  \u003CPropertyGroup>\n    \u003CTargetFramework>net8.0\u003C\u002FTargetFramework>\n    \u003CNullable>enable\u003C\u002FNullable>\n    \u003CImplicitUsings>enable\u003C\u002FImplicitUsings>\n    \u003CTreatWarningsAsErrors>true\u003C\u002FTreatWarningsAsErrors>\n    \u003CCompany>Contoso\u003C\u002FCompany>\n    \u003CCopyright>Copyright © Contoso 2024\u003C\u002FCopyright>\n  \u003C\u002FPropertyGroup>\n\u003C\u002FProject>\n```\n\n**`Directory.Build.targets`** (for targets\u002Ftasks — placed at repo or src root):\n\n```xml\n\u003CProject>\n  \u003CTarget Name=\"PrintBuildInfo\" AfterTargets=\"Build\">\n    \u003CMessage Importance=\"High\" Text=\"Built $(AssemblyName) → $(TargetPath)\" \u002F>\n  \u003C\u002FTarget>\n\u003C\u002FProject>\n```\n\n**Keep in individual `.csproj` files** only what is project-specific:\n\n```xml\n\u003CProject Sdk=\"Microsoft.NET.Sdk\">\n  \u003CPropertyGroup>\n    \u003COutputType>Exe\u003C\u002FOutputType>\n    \u003CAssemblyName>MyApp\u003C\u002FAssemblyName>\n  \u003C\u002FPropertyGroup>\n  \u003CItemGroup>\n    \u003CPackageReference Include=\"Serilog\" \u002F>\n    \u003CProjectReference Include=\"..\\MyLibrary\\MyLibrary.csproj\" \u002F>\n  \u003C\u002FItemGroup>\n\u003C\u002FProject>\n```\n\n## Tools and Automation\n\n| Tool | Usage |\n|------|-------|\n| `dotnet try-convert` | Automated legacy-to-SDK conversion. Install: `dotnet tool install -g try-convert` |\n| .NET Upgrade Assistant | Full migration including API changes. Install: `dotnet tool install -g upgrade-assistant` |\n| Visual Studio | Right-click `packages.config` → *Migrate packages.config to PackageReference* |\n| Manual migration | Often cleanest for simple projects — follow the checklist above |\n\n**Recommended approach:**\n\n1. Run `try-convert` for a first pass\n2. Review and clean up the output manually\n3. Build and fix any issues\n4. Enable modern features (nullable, implicit usings)\n5. Consolidate shared settings into `Directory.Build.props`\n",{"name":5,"license":15,"description":7},"main","f8f2bfd77819bb789d4736ce7db4a567a37fcf7e",".agents\u002Fskills\u002Fmsbuild-modernization",315,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-11 08:39:07",{"source":8,"skillName":5,"commands":55,"prompt":65,"zipAvailable":66},[56,59,62],{"tool":57,"command":58},"npx","npx skills add microsoft\u002Ftestfx --skill msbuild-modernization",{"tool":60,"command":61},"bunx","bunx skills add microsoft\u002Ftestfx --skill msbuild-modernization",{"tool":63,"command":64},"pnpm","pnpm dlx skills add microsoft\u002Ftestfx --skill msbuild-modernization","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fmsbuild-modernization\u002FSKILL.md",true,[68],{"path":69,"resourceType":70,"resourceTypeDesc":71,"sizeBytes":72,"sourceHtmlUrl":73},"agents\u002Fopenai.yaml","REFERENCE","参考文档",146,"https:\u002F\u002Fgithub.com\u002Fnichee\u002Fdotfiles\u002Fblob\u002Fmain\u002Fclaude\u002Fskills\u002Fubiquitous-language\u002Fagents\u002Fopenai.yaml",[75,101,132,152,172,196],{"skillNo":76,"name":77,"nameZh":6,"description":78,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":81,"publishedAt":40},"01M70GYY0AV0JGMTHE2CN6TNYK","dotnet-breaking-changes","Use before changing .NET\u002FC# code whose callers you do not control: a NuGet library, or a repo with PublicAPI.Shipped\u002FUnshipped.txt, ApiCompat, multiple TargetFrameworks, InternalsVisibleTo, or generated (.g.cs) partials. Green builds miss silent breaks from optional parameters, parameter names, sealing, const values, nullability, and #if. USE FOR: \"without breaking existing callers\"; adding a public method, overload, optional parameter, or interface member to a shipped library; renaming a public method or an internal helper a friend assembly uses; consolidating duplicated logic; changing a signature, nullability, default, or attribute; adding a target framework; RS0016\u002FRS0017 or ApiCompat errors; \"is this PR breaking?\". For a rename, extract, or merge, also invoke csharp-refactoring. DO NOT USE FOR: an executable whose callers all live in one project; renaming a local variable or private member, even in a shipped library; formatting; framework\u002FSDK\u002FNuGet upgrades (use dotnet-upgrade).","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fdotnet-breaking-changes\u002FSKILL.md",[],[82,83,84,85,26,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100],"dotnet","dotnet breaking changes","changes","green","default","local","errors","interface","generated","upgrade","invoke","you","code","refactoring","merge","rename","library","live","assembly",{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":112,"publishedAt":40},"01M699HHE9DTZWAMPB1ZR7ZKSW","testfx-test-value-evidence","Apply the vendored test-value-evidence skill with TestFx toolchain, assertion, parallel-safety, packed-consumer, and review conventions. Use when implementing or reviewing behavior-changing TestFx PRs or commits, including source-only changes.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-test-value-evidence\u002FSKILL.md",[107],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,84,129,130,131],"test","testfx test value evidence","test value evidence","evidence","value","review","parallel","toolchain","commits","implementing","prs","only","use","when","apply","with","behavior","Safety","conventions",{"skillNo":133,"name":134,"nameZh":6,"description":135,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":138,"publishedAt":40},"01M699HEYWH1B9XGNBFY8J2FZS","testfx-parallel-safety-preflight","Check concurrency and isolation before implementing or refactoring TestFx tests, lifecycle code, helpers, shared-state declarations, or production paths those tests exercise. Use before narrowing DoNotParallelize or ResourceLock, changing parallelization settings, or introducing asynchronous workers; repeat against the final diff.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-parallel-safety-preflight\u002FSKILL.md",[],[139,130,140,119,27,141,122,142,94,143,95,144,145,146,147,148,149,150,125,151],"testfx parallel safety preflight","preflight","asynchronous","exercise","concurrency","paths","isolation","helpers","diff","parallelization","lifecycle","tests","workers",{"skillNo":153,"name":154,"nameZh":6,"description":155,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":156,"categories":157,"keywords":158,"publishedAt":40},"01M699HC61ZV9GJ2C8JV03951G","testfx-localization-validation","Validate generated TestFx localization catalogs, resource IDs, placeholders and locked tokens. Use for RESX\u002FXLF changes, OneLoc PRs, localization build failures, duplicate targets or units, and orphaned translation entries.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-localization-validation\u002FSKILL.md",[],[159,160,161,162,163,164,165,166,167,123,90,125,168,169,84,170,171],"testfx localization validation","validation","localization","validate","translation","ids","for","tokens","entries","units","duplicate","targets","build",{"skillNo":173,"name":174,"nameZh":6,"description":175,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":176,"categories":177,"keywords":179,"publishedAt":40},"01M699H9J2NWQXE4BDT6R0FH7S","testfx-acceptance-validation","Validate TestFx shipping paths and capable CI execution using packed consumers, package\u002Fcache provenance, exact exits and artifacts, and selected-versus-executed test evidence. Use for acceptance regressions, package\u002FMSBuild\u002Flauncher\u002Fprotocol changes, or skipped-only CI results; not for unrelated unit-only or documentation changes.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-acceptance-validation\u002FSKILL.md",[178],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},[180,181,160,113,20,116,182,183,184,124,185,125,186,187,188,189,190,191,192,193,194,195,144,165],"testfx acceptance validation","acceptance","launcher","ci","execution","package","cache","artifacts","provenance","documentation","unrelated","shipping","results","protocol","unit","ci results",{"skillNo":197,"name":198,"nameZh":6,"description":199,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":200,"categories":201,"keywords":202,"publishedAt":40},"01M699H70BB999BT4F627T1PHE","session-delivery-hygiene","Gate local-session scope, persistent delivery, live PR state, integration provenance, and owned cleanup. Use before implementing guidance changes, changing or pushing PR work, diagnosing package\u002Foutput mismatches, or handing off implementation.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fsession-delivery-hygiene\u002FSKILL.md",[],[203,204,205,206,207,122,87,208,209,99,210,185,125,188,211,212,84,213,214,215],"session delivery hygiene","delivery","session","hygiene","scope","cleanup","work","pr","output","state","integration","guidance","gate"]