aspnet-core
Build, debug, modernize, or review ASP.NET Core applications with correct hosting, middleware, security, configuration, logging, and deployment patterns on…
Detect and fix MSBuild anti-patterns in project and build files. USE WHEN asked to review, audit, lint, clean up, or code-review a .csproj/.vbproj/.fsproj/.props/.targets/.proj (or Directory.Build.props/.targets) file, when asked 'is this project file correct?' or 'what's wrong
$ npx -y skills add managedcode/dotnet-skills --skill msbuild-antipatterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/msbuild-antipatternsContext preview
The summary Claude sees to decide when to auto-load this skill.
Detect and fix MSBuild anti-patterns in project and build files. USE WHEN asked to review, audit, lint, clean up, or code-review a .csproj/.vbproj/.fsproj/.props/.targets/.proj (or Directory.Build.props/.targets) file, when asked 'is this project file correct?' or 'what's wrong
name: msbuild-antipatterns description: "Detect and fix MSBuild anti-patterns in project and build files. USE WHEN asked to review, audit, lint, clean up, or code-review a .csproj/.vbproj/.fsproj/.props/.targets/.proj (or Directory.Build.props/.targets) file, when asked 'is this project file correct?' or 'what's wrong with my build file?', or when hunting subtle build bugs caused by how a project is authored. Each anti-pattern has a symptom and a concrete BAD→GOOD fix. DO NOT USE FOR: non-MSBuild build systems (npm, Maven, CMake), or migrating a project to SDK-style (use msbuild-modernization)." license: MIT
A numbered catalog of common MSBuild anti-patterns. Each entry follows the format:
Use this catalog when scanning project files for improvements.
---
**Smell**: `<Exec Command="mkdir ..." />`, `<Exec Command="copy ..." />`, `<Exec Command="del ..." />`
**Why it's bad**: Built-in tasks are cross-platform, support incremental build, emit structured logging, and handle errors consistently. `<Exec>` is opaque to MSBuild.
<!-- BAD --> <Target Name="PrepareOutput"> <Exec Command="mkdir $(OutputPath)logs" /> <Exec Command="copy config.json $(OutputPath)" /> <Exec Command="del $(IntermediateOutputPath)*.tmp" /> </Target> <!-- GOOD --> <Target Name="PrepareOutput"> <MakeDir Directories="$(OutputPath)logs" /> <Copy SourceFiles="config.json" DestinationFolder="$(OutputPath)" /> <Delete Files="@(TempFiles)" /> </Target>
**Built-in task alternatives:**
| Shell Command | MSBuild Task | |--------------|--------------| | `mkdir` | `<MakeDir>` | | `copy` / `cp` | `<Copy>` | | `del` / `rm` | `<Delete>` | | `move` / `mv` | `<Move>` | | `echo text > file` | `<WriteLinesToFile>` | | `touch` | `<Touch>` | | `xcopy /s` | `<Copy>` with item globs |
---
**Smell**: `Condition="$(Foo) == Bar"` — either side of a comparison is unquoted.
**Why it's bad**: If the property is empty or contains spaces/special characters, the condition evaluates incorrectly or throws a parse error. MSBuild requires single-quoted strings for reliable comparisons.
<!-- BAD --> <PropertyGroup Condition="$(Configuration) == Release"> <Optimize>true</Optimize> </PropertyGroup> <!-- GOOD --> <PropertyGroup Condition="'$(Configuration)' == 'Release'"> <Optimize>true</Optimize> </PropertyGroup>
**Rule**: Always quote **both** sides of `==` and `!=` comparisons with single quotes.
---
**Smell**: Paths like `C:\tools\`, `D:\packages\`, `/usr/local/bin/` in project files.
**Why it's bad**: Breaks on other machines, CI environments, and other operating systems. Not relocatable.
<!-- BAD --> <PropertyGroup> <ToolPath>C:\tools\mytool\mytool.exe</ToolPath> </PropertyGroup> <Import Project="C:\repos\shared\common.props" /> <!-- GOOD --> <PropertyGroup> <ToolPath>$(MSBuildThisFileDirectory)tools\mytool\mytool.exe</ToolPath> </PropertyGroup> <Import Project="$(RepoRoot)eng\common.props" />
**Preferred path properties:**
| Property | Meaning | |----------|---------| | `$(MSBuildThisFileDirectory)` | Directory of the current .props/.targets file | | `$(MSBuildProjectDirectory)` | Directory of the .csproj | | `$([MSBuild]::GetDirectoryNameOfFileAbove(...))` | Walk up to find a marker file | | `$([MSBuild]::NormalizePath(...))` | Combine and normalize path segments |
---
**Smell**: Properties set to values that the .NET SDK already provides by default.
**Why it's bad**: Adds noise, hides intentional overrides, and makes it harder to identify what's actually customized. When defaults change in newer SDKs, the redundant properties may silently pin old behavior.
<!-- BAD: All of these are already the default --> <PropertyGroup> <OutputType>Library</OutputType> <EnableDefaultItems>true</EnableDefaultItems> <EnableDefaultCompileItems>true</EnableDefaultCompileItems> <RootNamespace>MyLib</RootNamespace> <!-- matches project name --> <AssemblyName>MyLib</AssemblyName> <!-- matches project name --> <AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath> </PropertyGroup> <!-- GOOD: Only non-default values --> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> </PropertyGroup>
---
**Smell**: `<Compile Include="File1.cs" />`, `<Compile Include="File2.cs" />` in SDK-style projects.
**Why it's bad**: SDK-style projects automatically glob `**/*.cs` (and other file types). Explicit listing is redundant, creates merge conflicts, and new files may be accidentally missed if not added to the list.
<!-- BAD -->
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Services\MyService.cs" />
<Compile Include="Models\User.cs" />
</ItemGroup>
<!-- GOOD: Remove entirely — SDK includes all .cs files by default.
Only use Remove/Exclude when you need to opt out: -->
<ItemGroup>
<Compile Remove="LegacyCode\**" />
</ItemGroup>**Exception**: Non-SDK-style (legacy) projects require explicit file includes. If migrating, see `msbuild-modernization` skill.
**Exception (F# / `.fsproj`)**: F# compilation is order-dependent — the compiler processes `<Compile Include>` items sequentially and a file can only reference types/modules declared in files listed above it. `.fsproj` files must therefore list every source file explicitly, in dependency order (utility/leaf modules at the top, the entry point such as `Program.fs` at the bottom). If a `.fsi` signature file is used, it must appear **immediately before** its companion `.fs` implementation file.
---
Stop explaining .NET to your AI. Start building. We've all been there: asking Claude to use Entity Framework, only to get EF6 patterns in a .NET 8 project. Explaining to Copilot that Blazor Server and Blazor WebAssembly aren't the same thing.
Repo: managedcode/dotnet-skills
Build, debug, modernize, or review ASP.NET Core applications with correct hosting, middleware, security, configuration, logging, and deployment patterns on…
Build, upgrade, and operate Aspire 13.5.x C# or TypeScript application hosts with the current CLI, AppHost, ServiceDefaults, integrations, dashboard, testing,…
Build, review, or migrate Azure Functions in .NET with correct execution model, isolated worker setup, bindings, DI, and Durable Functions patterns. USE FOR:…
Build and review Blazor applications across server, WebAssembly, web app, and hybrid scenarios with correct component design, state flow, rendering, and…
Maintain or migrate EF6-based applications with realistic guidance on what to keep, what to modernize, and when EF Core is or is not the right next step. USE…
Design, tune, or review EF Core data access with proper modeling, migrations, query translation, performance, and lifetime management for modern .NET…