Teaches agents to read and safely modify SDK-style .csproj files. Covers project structure, PropertyGroup conventions,
ItemGroup patterns, conditional expressions, Directory.Build.props/.targets, and central package management
(Directory.Packages.props). Each subsection provides annotated XML examples and common modification patterns.
Scope
SDK-style .csproj structure and SDK attribute conventions
PropertyGroup and ItemGroup reading and modification
Conditional expressions and TFM-based conditions
Directory.Build.props/.targets and Central Package Management
Out of scope
Project organization and SDK selection -- see [skill:dotnet-project-structure]
Build error interpretation -- see [skill:dotnet-build-analysis]
Common agent coding mistakes -- see [skill:dotnet-agent-gotchas]
Prerequisites
.NET 8.0+ SDK. SDK-style projects only (legacy .csproj format is not covered). MSBuild (included with .NET SDK).
Cross-references: [skill:dotnet-project-structure] for project organization and SDK selection,
[skill:dotnet-build-analysis] for interpreting build errors from project misconfiguration, [skill:dotnet-agent-gotchas]
for common project structure mistakes agents make.
Subsection 1: SDK-Style Project Structure
SDK-style projects use a <Project Sdk="..."> declaration that imports hundreds of default targets and props.
Understanding what the SDK provides implicitly is essential to avoid redundant or conflicting declarations.
Annotated XML Example
<!-- The Sdk attribute imports default props at the top and targets at the bottom --><!-- This single line replaces dozens of Import statements from legacy .csproj --><ProjectSdk="Microsoft.NET.Sdk"><!--
Common SDK values:
- Microsoft.NET.Sdk -> Console apps, libraries, class libraries
- Microsoft.NET.Sdk.Web -> ASP.NET Core (adds Kestrel, MVC, Razor, shared framework)
- Microsoft.NET.Sdk.Worker -> Background worker services
- Microsoft.NET.Sdk.Razor -> Razor class libraries
- Microsoft.NET.Sdk.BlazorWebAssembly -> Blazor WASM apps
--><!-- SDK-style projects auto-include all *.cs files via default globs --><!-- No need to list individual .cs files in <Compile Include="..."> --><!-- Default globs: **/*.cs for Compile, **/*.resx for EmbeddedResource -->
net9.0
```text
### Common Modification Patterns
**Changing SDK type** -- when an agent creates a web project with the wrong SDK:
```xml
```text
**Disabling default globs** -- rare, but needed when migrating from legacy format or when explicit file control is
required:
```xml
false
false
```text
**Verifying which SDK a project uses:**
```bash
# Check the first line of the .csproj for the Sdk attribute
head -1 src/MyApp/MyApp.csproj
# Output:
```bash
---
## Subsection 2: PropertyGroup Conventions
PropertyGroup elements contain scalar MSBuild properties. The most important properties control the target framework,
language features, and output type.
### Annotated XML Example
```xml
net9.0
enable
enable
Exe
MyApp.Api
MyApp.Api
preview
```text
### Common Modification Patterns
**Enabling nullable for an existing project:**
```xml
enable
```text
**Setting output type for a console app:**
```xml
Exe
```xml
**Adding TreatWarningsAsErrors (recommended for CI parity):**
```xml
true
```text
---
## Subsection 3: ItemGroup Patterns
ItemGroup elements contain collections: package references, project references, file inclusions, and other build items.
Understanding the three main item types prevents common agent mistakes.
### Annotated XML Example
```xml
```text
### Common Modification Patterns
**Adding a NuGet package:**
```bash
# Prefer CLI to avoid formatting issues
dotnet add src/MyApp/MyApp.csproj package Microsoft.EntityFrameworkCore --version 9.0.0
```bash
```xml
```xml
**Adding a project reference:**
```bash
# CLI ensures correct relative path
dotnet add src/MyApp.Api/MyApp.Api.csproj reference src/MyApp.Core/MyApp.Core.csproj
```bash
```xml
```csharp
**Including non-compiled files in output:**
```xml
```json
---
## Subsection 4: Condition Expressions and Multi-Targeting
MSBuild conditions enable TFM-specific properties, platform-specific package references, and build configuration logic.
Understanding condition syntax prevents broken multi-targeting builds.
### Annotated XML Example
```xml
net8.0;net9.0
true
true
```text
### Common Modification Patterns
**Adding a TFM:**
```xml
net8.0
net8.0;net9.0
```text
**Using version-agnostic TFM patterns for platform detection:**
```xml
```text
**Condition syntax reference:**
| Expression | Meaning |
| ------------------------------ | ------------------------------ |
| `'$(Prop)' == 'value'` | Exact match (case-insensitive) |
| `'$(Prop)' != 'value'` | Not equal |
| `$(Prop.StartsWith('prefix'))` | String starts with |
| `$(Prop.Contains('sub'))` | String contains |
| `'$(Prop)' == ''` | Property is empty/not set |
| `Exists('path')` | File or directory exists |
---
## Subsection 5: Directory.Build.props and Directory.Build.targets
These files centralize shared build configuration. MSBuild automatically imports `Directory.Build.props` (before the
project) and `Directory.Build.targets` (after the project) from the current directory and all parent directories up to
the filesystem root.
### Annotated XML: Directory.Build.props
```xml
net9.0
enable
enable
true
true
true
MyCompany
MyCompany
MIT
```text
### Annotated XML: Directory.Build.targets
```xml
```text
### Common Modification Patterns
**Hierarchy and override behavior:**
```text
repo-root/
Directory.Build.props <-- applies to ALL projects
src/
Directory.Build.props <-- applies to src/ projects only
MyApp.Api/
MyApp.Api.csproj <-- inherits from src/ props (NOT repo-root/)
```csharp
MSBuild imports the nearest `Directory.Build.props` found walking upward. If a nested `Directory.Build.props` exists, it
shadows the parent. To chain both, the nested file must explicitly import the parent:
```xml
MyApp.$(MSBuildProjectName)
```text
**When to use .props vs .targets:**
| Use .props for | Use .targets for |
| --------------------------------------- | -------------------------------------------------- |
| Property defaults (TFM, nullable, etc.) | Items that depend on project properties |
| Package metadata (authors, license) | Custom build targets (AfterTargets, BeforeTargets) |
| Properties projects can override | Analyzer packages added to all projects |
---
## Subsection 6: Directory.Packages.props (Central Package Management)
Central Package Management (CPM) centralizes NuGet package versions in a single `Directory.Packages.props` file.
Individual projects reference packages without specifying versions.
### Annotated XML Example
```xml
true
```text
**Project file with CPM enabled:**
```xml
net9.0
```text
### Common Modification Patterns
**Enabling CPM in an existing solution:**
1. Create `Directory.Packages.props` at the solution root with
`true`.
2. Move all `Version` attributes from `PackageReference` items into `PackageVersion` entries in the central file.
3. Remove `Version` from all `PackageReference` items in individual `.csproj` files.
```bash
# Find all PackageReference entries with Version attributes
grep -rn 'PackageReference Include=.*Version=' --include="*.csproj" src/
```bash
**Overriding a version in a specific project** (escape hatch):
```xml
```csharp
**Hierarchical resolution:** `Directory.Packages.props` resolves upward from the project directory, the same as
`Directory.Build.props`. In monorepos, place the central file at the repo root. Sub-directories can have their own
`Directory.Packages.props` -- the nearest one wins.
**Migrating from per-project versions:**
```bash
# List all unique packages and versions across the solution
dotnet list src/MyApp.sln package --format json
# Use this output to build the central PackageVersion list
```bash
---
## Slopwatch Anti-Patterns
These patterns in project files indicate an agent is hiding problems rather than fixing them. See
[skill:dotnet-slopwatch] for the automated quality gate that detects these patterns.
### NoWarn in .csproj
```xml
CS8600;CS8602;CS8604;IL2026;IL2046;IL3050
```text
`` in the project file suppresses warnings for the entire project, making issues invisible. This is worse than
`#pragma` because it has no scope boundary and cannot be audited per-file.
**Fix:** Remove `` entries and fix the underlying issues. For warnings that genuinely do not apply project-wide,
configure severity in `.editorconfig` instead:
```ini
# .editorconfig -- preferred over for controlled suppression
[*.cs]
dotnet_diagnostic.CA2007.severity = none # No SynchronizationContext in ASP.NET Core
```csharp
### Suppressed Analyzers in Directory.Build.props
```xml
$(NoWarn);CA1062;CA1822;CA2007
false
false
```text
Disabling analyzers in `Directory.Build.props` silences them across every project in the solution, including new
projects added later. Agents sometimes do this to achieve a clean build quickly.
**Fix:** Keep analyzers enabled globally. Address warnings per-project or per-file. If a specific rule category does not
apply (e.g., CA2007 in ASP.NET Core apps), suppress it in `.editorconfig` at the appropriate scope with a comment
explaining why.
---
## Cross-References
- [skill:dotnet-project-structure] -- SDK selection, project organization, solution layout
- [skill:dotnet-build-analysis] -- interpreting build errors caused by project misconfiguration
- [skill:dotnet-agent-gotchas] -- common project structure mistakes agents make (wrong SDK, broken refs)
## Code Navigation (Serena MCP)
**Primary approach:** Use Serena symbol operations for efficient code navigation:
1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` for file organization
3. **Track references**: `serena_find_referencing_symbols` for impact analysis
4. **Precise edits**: `serena_replace_symbol_body` for clean modifications
**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine
**Example workflow:**
```text
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"
```
## References
- [MSBuild Project SDK](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview)
- [MSBuild Reference](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild)
- [Central Package Management](https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management)
- [Directory.Build.props/targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory)
- [MSBuild Conditions](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions)
- [SDK-style Project Format](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview)
<PropertyGroup>
<TargetFramework>
</TargetFramework>
</PropertyGroup>
</Project>
<!-- WRONG: console SDK for a web project -->
<ProjectSdk="Microsoft.NET.Sdk">
<!-- CORRECT: Web SDK includes ASP.NET Core shared framework -->
<ProjectSdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<!-- Disable automatic inclusion of *.cs files -->
<EnableDefaultCompileItems>
</EnableDefaultCompileItems>
<!-- Disable all default items (Compile, EmbeddedResource, Content) -->
<EnableDefaultItems>
</EnableDefaultItems>
</PropertyGroup>
<ProjectSdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<!-- Target Framework Moniker (TFM) -- determines runtime and API surface -->
<!-- Use the latest LTS or STS release; prefer the repo's existing TFM. -->
<TargetFramework>
</TargetFramework>
<!-- For multi-targeting, use plural form (see Subsection 4) -->