100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
.NET

.csproj and MSBuild Basics

How the .csproj file and the MSBuild engine work together to define, configure, and drive every .NET Core build.

Building BlocksBeginner9 min readJul 10, 2026
Analogies

Understanding the .csproj File

Every .NET Core project is described by a .csproj file, an XML document that tells the dotnet CLI and MSBuild what to compile, which framework to target, which packages to restore, and how to produce the final output. It is not just metadata sitting beside your code; it is the actual build script that drives every 'dotnet build', 'dotnet run', and 'dotnet publish' invocation.

🏏

Cricket analogy: A .csproj is like the team sheet handed to the umpire before a match — it declares who's playing (which files and references are in scope) and under what format (T20, ODI, Test) the game (build) will run, exactly the way TargetFramework sets the rules for compilation.

SDK-Style Project Format

Modern .NET Core projects use the SDK-style format, introduced with .NET Core and now standard across .NET 5 and later. The file starts with '<Project Sdk="Microsoft.NET.Sdk">', which implicitly includes all .cs files under the project folder, implicitly references the base class library, and enables features like implicit usings and nullable reference types through a handful of terse properties, replacing the verbose, explicitly-enumerated project files from classic .NET Framework.

🏏

Cricket analogy: This is like modern DRS technology replacing manual umpiring decisions — a few lines of configuration (SDK attribute) now do automatically what used to require the umpire (developer) to manually check every ball (manually list every file), the way old-style csproj required explicit <Compile Include> entries.

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <RootNamespace>Contoso.Orders</RootNamespace>
    <OutputType>Exe</OutputType>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
  </ItemGroup>

  <ItemGroup>
    <Compile Remove="Scratch\**\*.cs" />
  </ItemGroup>

</Project>

MSBuild Properties, Items, and Conditions

Inside the .csproj, PropertyGroup elements define single-valued settings like TargetFramework, Version, or LangVersion, while ItemGroup elements define collections such as PackageReference, ProjectReference, or explicit Compile entries. Both can carry a Condition attribute, evaluated by MSBuild at build time, so you can scope a property or item to a specific configuration, such as '"$(Configuration)" == "Release"', or a specific target framework in a multi-targeted project.

🏏

Cricket analogy: This is like a bowling change that only applies 'Condition' the pitch is a green top at a venue like Headingley — the captain (MSBuild) evaluates the pitch report before deciding, exactly as MSBuild evaluates a Condition attribute before including a property or item.

MSBuild Targets and the Build Pipeline

Underneath the properties and items, MSBuild executes a pipeline of Targets — named, ordered sets of Tasks such as Csc (the C# compiler task), Copy, or ResolveAssemblyReferences. The SDK imports a large chain of built-in targets like Restore, Build, and Publish, and you can hook into this pipeline by defining your own <Target Name="..." BeforeTargets="Build"> or AfterTargets="Build" elements to run custom logic, such as stamping a build number or copying extra files, without editing the SDK's own target files.

🏏

Cricket analogy: This is like a fixed sequence of innings in a Test match — toss, first innings, follow-on decision — where you can insert a rain delay 'BeforeTargets' the next session, but the overall Target pipeline (match structure) proceeds in a defined order just like MSBuild's Restore-then-Build-then-Publish chain.

You rarely need to edit MSBuild targets directly for day-to-day app development — the dotnet CLI commands (build, run, test, publish) cover almost everything. Reach for custom Targets or Directory.Build.props only when you need cross-cutting behavior, like stamping a build number into every project in a solution or enforcing a shared analyzer ruleset.

Never hand-edit the obj/ or bin/ folders, or files like project.assets.json — they are fully regenerated by 'dotnet restore' and 'dotnet build' and any manual changes will be silently overwritten, or worse, cause confusing restore failures that look like real project errors.

  • The .csproj is an XML file that MSBuild reads to know what to compile, reference, and produce.
  • SDK-style projects start with <Project Sdk="Microsoft.NET.Sdk"> and implicitly include all .cs files under the project folder.
  • PropertyGroup holds single-valued settings like TargetFramework; ItemGroup holds collections like PackageReference and ProjectReference.
  • Condition attributes let you scope a property or item to a specific Configuration or TargetFramework.
  • MSBuild executes a pipeline of named Targets (Restore, Build, Publish) built from ordered Tasks.
  • Custom Targets can hook in with BeforeTargets or AfterTargets without modifying the SDK's own target files.
  • Never manually edit generated files under obj/ or bin/ — they are regenerated on every restore and build.

Practice what you learned

Was this page helpful?

Topics covered

#NET#NETCoreStudyNotes#MicrosoftTechnologies#CsprojAndMSBuildBasics#Csproj#MSBuild#File#SDK#StudyNotes#SkillVeris