.Net 10 project files explained

When setting up a project for the first time it is easy to just let your project template do all the work. Visual Studio’s IDE hides the details of the project file as you are building your project with little need to actually examine its contents. That is, until you find yourself having to configure your project outside of the ‘happy path’. To address this, I have created this post to fully describe the elements of a project file in all its glory. Enjoy!

Below is the deep-dive, “explain-every-item” guide to a modern .NET 10 (.csproj) project file.
.NET project files have evolved into a concise, MSBuild-based structure, but there are many elements—both common and optional—that matter in real-world solutions.

Full Explanation of All Items in a .NET 10 Project File

A typical .NET 10 project file looks like this:

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

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <OutputType>Exe</OutputType>
    <RootNamespace>MyApp</RootNamespace>
    <AssemblyName>MyApp</AssemblyName>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
    <GenerateAssemblyInfo>true</GenerateAssemblyInfo>
    <Version>1.0.0</Version>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
    <ProjectReference Include="..\Domain\Domain.csproj" />
    <Reference Include="System.Xml" />
  </ItemGroup>

  <ItemGroup>
    <None Include="appsettings.json" CopyToOutputDirectory="Always" />
  </ItemGroup>

</Project>

Let’s break down every category of element you will ever see in a .NET 10 .csproj.

1. <Project> Element

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

SDK options

Sdk ValueMeaningMicrosoft.NET.SdkStandard C# project (console, class library, etc.)Microsoft.NET.Sdk.WebASP.NET Core project (adds Kestrel, Razor, static files, etc.)Microsoft.NET.Sdk.WorkerBackground service template (WorkerService)Microsoft.NET.Sdk.RazorRazor / Blazor componentsMicrosoft.NET.Sdk.BlazorWebAssemblyBlazor WebAssembly client

The Sdk modifies:

  • default references

  • build steps

  • generated code

  • implicit imports

2. <PropertyGroup>

All compile-time configuration.
You can have multiple <PropertyGroup> blocks (conditional or global).

The key items:

Target Framework

<TargetFramework>net10.0</TargetFramework>
  • Tells MSBuild which .NET runtime the project builds against.

  • Multi-targeting uses <TargetFrameworks> (plural).

Nullable

<Nullable>enable</Nullable>

Controls nullable reference types:

  • enable

  • disable

  • annotations

  • warnings

Implicit Usings

<ImplicitUsings>enable</ImplicitUsings>

Automatically inserts common using directives (System, System.IO, etc.).

OutputType

<OutputType>Exe</OutputType>

Options:

  • Exe

  • WinExe

  • Library (default for class libraries)

RootNamespace

<RootNamespace>MyApp</RootNamespace>

Namespace used for generated code.

AssemblyName

<AssemblyName>MyApp</AssemblyName>

Output DLL/EXE filename.

GenerateDocumentationFile

<GenerateDocumentationFile>true</GenerateDocumentationFile>

Creates MyApp.xml for IntelliSense documentation.

TreatWarningsAsErrors

<TreatWarningsAsErrors>false</TreatWarningsAsErrors>

You can also target specific warnings:

<WarningsAsErrors>CS8618;CS1591</WarningsAsErrors>

GenerateAssemblyInfo

<GenerateAssemblyInfo>true</GenerateAssemblyInfo>

When true, compiler auto-generates AssemblyInfo.cs.

Version / FileVersion / AssemblyVersion

<Version>1.2.3</Version>
<AssemblyVersion>1.2.0.0</AssemblyVersion>
<FileVersion>1.2.3.4567</FileVersion>

Configurations

<Configurations>Debug;Release</Configurations>

Platform

<Platform>x64</Platform>

PublishSingleFile

<PublishSingleFile>true</PublishSingleFile>

Used for single-file publish (self-contained)

Self-contained publish

<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>

EnablePreviewFeatures

<EnablePreviewFeatures>true</EnablePreviewFeatures>

Required for preview C#/.NET features.

EnableNativeAOT

<PublishAot>true</PublishAot>

Native AOT compilation.

LangVersion

<LangVersion>13.0</LangVersion>

For .NET 10 / C# 13.

3. <ItemGroup>

Contains references, files, analyzers, embedded resources, etc.

PackageReference

<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />

You can also use PrivateAssets:

<PackageReference Include="coverlet.collector" Version="3.2.0">
   <PrivateAssets>all</PrivateAssets>
</PackageReference>

ProjectReference

<ProjectReference Include="..\Domain\Domain.csproj" />

Adds a dependency on another local project.

Framework Reference

Used in ASP.NET:

<FrameworkReference Include="Microsoft.AspNetCore.App" />

Reference (legacy)

<Reference Include="System.Xml" />

Used mostly for older .NET Framework libraries.

Analyzer

<Analyzer Include="packages\mypackage\analyzers\cs\MyAnalyzer.dll" />

Adds compiler analyzers (Roslyn).

Compile

<Compile Include="Services\*.cs" />

Allows customizing which C# files compile.

None / Content / EmbeddedResource

Common for config files:

<None Include="appsettings.json" CopyToOutputDirectory="Always" />

Options for CopyToOutputDirectory:

  • Never

  • Always

  • PreserveNewest

Resource files

<EmbeddedResource Include="Localization\*.resx" />

Razor Component / Blazor items

<RazorComponent Include="Pages\MyComp.razor" />

4. Conditional Properties

<PropertyGroup Condition="'$(Configuration)'=='Debug'">
    <DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>

Allows:

  • OS-based conditions

  • Configuration

  • TargetFramework variations

5. <Import> Elements

Rare in SDK-style projects, but sometimes seen:

<Import Project="custom.targets" />

Adds extra MSBuild logic.

6. Custom Build Targets

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
  <Exec Command="echo Post build running" />
</Target>

Targets run at various build phases:

  • BeforeBuild

  • AfterBuild

  • BeforePublish

  • AfterPublish

7. Additional MSBuild Concepts

Properties ($(PropertyName))

Variables used in conditions and targets.

Items (@(ItemName))

Collections of file entries (Compile, None, Content, etc.)

8. Structure of a Professional Clean Architecture .csproj Setup

Domain

  • No external packages except value-object utilities

  • Pure C#

Application

  • MediatR

  • Validation

  • Mapping

Infrastructure

  • EF Core

  • Repositories

  • External services

API / Server

  • ASP.NET Core

  • DI composition root

  • OpenAPI/Swagger

In More Detail…

I’ll walk it in layers:

  1. What a project file really is (MSBuild concepts).

  2. Line-by-line annotated example.

  3. Common <PropertyGroup> properties (what they do, why you’d care).

  4. Common <ItemGroup> items (PackageReference, ProjectReference, etc.).

  5. Metadata on items (CopyToOutputDirectory, PrivateAssets, etc.).

  6. Conditions and custom targets.

  7. How this all fits into Clean Architecture / real solutions.

1. What a .NET project file actually is

A .csproj is just an MSBuild project in XML.

MSBuild has a few core concepts:

  • Properties (scalar values)

    • Think: string variables.

    • Accessed as $(PropertyName)

    • Declared in <PropertyGroup>...</PropertyGroup>.

  • Items (collections of things)

    • Think: a list of files or references.

    • Accessed as @(ItemName)

    • Declared in <ItemGroup>...</ItemGroup>.

  • Targets

    • Named sets of steps (tasks) executed during the build.

    • Example: Build, Publish, Clean, or your custom one.

  • Tasks

    • Actual executable units (e.g. <Csc /> to compile C#, <Copy /> to copy files).

  • SDK imports

    • Sdk="Microsoft.NET.Sdk" silently imports .props & .targets that define defaults for everything.

So your .csproj is essentially:

<Project Sdk="Microsoft.NET.Sdk">
  <!-- Properties -->
  <PropertyGroup>…</PropertyGroup>

  <!-- Items -->
  <ItemGroup>…</ItemGroup>

  <!-- Optional custom targets -->
  <Target Name="...">…</Target>
</Project>

2. A realistic .NET 10 project file, annotated

Let’s take a slightly richer example and then break it down:

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

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <OutputType>Exe</OutputType>

    <RootNamespace>MyCompany.MyProduct.Api</RootNamespace>
    <AssemblyName>MyCompany.MyProduct.Api</AssemblyName>

    <LangVersion>preview</LangVersion>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>

    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <WarningsAsErrors>CS1591;CS8618</WarningsAsErrors>
    <NoWarn>CS1998</NoWarn>

    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <SelfContained>false</SelfContained>
    <PublishSingleFile>false</PublishSingleFile>
    <PublishTrimmed>false</PublishTrimmed>

    <Version>1.0.0</Version>
    <AssemblyVersion>1.0.0.0</AssemblyVersion>
    <FileVersion>1.0.0.0</FileVersion>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)'=='Release'">
    <Optimize>true</Optimize>
    <DebugType>portable</DebugType>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Application\Application.csproj" />
    <ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
  </ItemGroup>

  <ItemGroup>
    <None Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
    <None Include="appsettings.Development.json" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>

  <Target Name="PostBuild" AfterTargets="Build">
    <Message Text="PostBuild target running for $(AssemblyName)" Importance="High" />
  </Target>

</Project>

We’ll now decode each category of elements carefully.

3. <PropertyGroup> – the important properties explained

3.1 Framework & runtime settings

TargetFramework

<TargetFramework>net10.0</TargetFramework>
  • Tells MSBuild which runtime you compile against.

  • For multi-target:

    <TargetFrameworks>net8.0;net10.0</TargetFrameworks>
    

OutputType

<OutputType>Exe</OutputType>
  • How the output is packaged:

    • Exe → console/Web/worker app (has an entry point).

    • WinExe → Windows GUI app (no console window).

    • Library → class library (DLL).

3.2 C# language & compiler behavior

Nullable

<Nullable>enable</Nullable>

Controls nullable reference type analysis:

  • disable – no nullable analysis.

  • enable – treat reference types as non-nullable by default.

  • annotations – only annotate reference types, no warnings.

  • warnings – warn on nullability, but don’t require annotations.

You can also set this per-PropertyGroup (e.g. only for specific TFMs).

ImplicitUsings

<ImplicitUsings>enable</ImplicitUsings>
  • When enabled, the SDK injects a bunch of common using directives for you.

  • Typically includes namespaces like System, System.IO, System.Net.Http, etc.

  • disable if you want complete manual control.

LangVersion

<LangVersion>preview</LangVersion>
  • Controls which C# language version is used.

  • Options:

    • Explicit version (11, 12, 13.0, etc.)

    • latest

    • preview

  • You often pair this with:

EnablePreviewFeatures

<EnablePreviewFeatures>true</EnablePreviewFeatures>
  • Opts the project into preview language/runtime features, often required for cutting-edge .NET 10 / C# 13 stuff.

3.3 Assembly naming & namespacing

RootNamespace

<RootNamespace>MyCompany.MyProduct.Api</RootNamespace>
  • Base namespace used for generated code (e.g. Razor-generated classes).

  • If not set, defaults to the project name.

AssemblyName

<AssemblyName>MyCompany.MyProduct.Api</AssemblyName>
  • Name of the generated assembly file: MyCompany.MyProduct.Api.dll.

  • You may override this if folder name ≠ desired assembly name.

3.4 Warnings, errors, and documentation

GenerateDocumentationFile

<GenerateDocumentationFile>true</GenerateDocumentationFile>
  • Causes compiler to emit XML documentation file (based on /// comments).

  • Output is $(AssemblyName).xml next to the .dll.

  • Useful for NuGet packages and IntelliSense.

TreatWarningsAsErrors

<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  • All compiler warnings become errors → fail the build.

  • Great for engineering discipline, but sometimes too strict for legacy code.

WarningsAsErrors

<WarningsAsErrors>CS1591;CS8618</WarningsAsErrors>
  • Makes specific warnings errors.

  • Examples:

    • CS1591 – missing XML documentation.

    • CS8618 – non-nullable field must contain a non-null value.

This is typically used instead of global TreatWarningsAsErrors when you want to enforce only certain categories.

NoWarn

<NoWarn>CS1998</NoWarn>
  • Suppresses specific warnings.

  • e.g., CS1998 for async method lacking await (common in stub/test methods).

You can combine:

<NoWarn>CS1591;CS1998</NoWarn>

3.5 Versioning

Version, AssemblyVersion, FileVersion

<Version>1.0.0</Version>                <!-- NuGet / product version -->
<AssemblyVersion>1.0.0.0</AssemblyVersion>  <!-- CLR binding version -->
<FileVersion>1.0.0.0</FileVersion>          <!-- Win32 file version -->
  • Version:

    • Used for NuGet & AssemblyInformationalVersion.

  • AssemblyVersion:

    • Affects binding redirects; changing this can break dependent assemblies.

  • FileVersion:

    • Shown in file properties; safe to change freely.

In many projects you only set Version and let others default.

3.6 Publish and runtime behavior

RuntimeIdentifier / RuntimeIdentifiers

<RuntimeIdentifier>win-x64</RuntimeIdentifier>
  • Builds for a specific OS/architecture runtime.

  • For multiple:

    <RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
    

SelfContained

<SelfContained>false</SelfContained>
  • true → bundle the .NET runtime with your app (no separate install).

  • false → depend on a system-installed runtime (framework-dependent).

PublishSingleFile

<PublishSingleFile>false</PublishSingleFile>
  • true → publish as a single executable file (everything packed inside).

  • Works best with SelfContained=true, but can also be used with framework-dependent.

PublishTrimmed

<PublishTrimmed>false</PublishTrimmed>
  • true → IL linker trims unused code to reduce app size.

  • Requires careful testing; reflection-heavy code (e.g. some ORMs) can break.

PublishAot (Native AOT)

<PublishAot>true</PublishAot>
  • Ahead-of-time compile to a native binary.

  • Faster startup, no JIT, smaller single-file in some cases.

  • Not every library is compatible.

3.7 Debug / release specific properties

Often inside a conditional PropertyGroup:

<PropertyGroup Condition="'$(Configuration)'=='Release'">
  <Optimize>true</Optimize>
  <DebugType>portable</DebugType>
</PropertyGroup>

Optimize

  • true in Release, false in Debug.

  • Enables JIT/compiler optimizations.

DebugType

  • portable for portable PDBs (standard now).

  • Affects what debugging info is emitted.

You can also see:

<DebugSymbols>true</DebugSymbols>

4. <ItemGroup> – core items and what they mean

Items are collections with metadata. The important ones you see in SDK-style .csproj:

4.1 PackageReference

<ItemGroup>
  <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
</ItemGroup>
  • References a NuGet package.

  • Include = package ID.

  • Version = package version.

You can add extra metadata:

<PackageReference Include="coverlet.collector" Version="6.0.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
  • PrivateAssets:

    • all → this dependency does not flow transitively to projects that depend on you (e.g. test-only tools).

  • IncludeAssets:

    • Fine-grained control over what parts of the package you consume.

  • ExcludeAssets:

    • Opposite of IncludeAssets.

4.2 ProjectReference

<ItemGroup>
  <ProjectReference Include="..\Application\Application.csproj" />
</ItemGroup>
  • Adds a dependency on another project in the solution.

  • Compilation-wise it’s like adding a reference to its output assembly.

  • The build determines the correct build order.

You can also add metadata:

<ProjectReference Include="..\Infrastructure\Infrastructure.csproj">
  <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
</ProjectReference>

4.3 FrameworkReference

Primarily used with Web / Windows / MAUI workloads:

<ItemGroup>
  <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
  • Pulls in a shared framework that’s not delivered as a NuGet package (e.g. web host runtime).

Most modern ASP.NET Core projects get these from the SDK implicitly, so you often don’t see them.

4.4 Reference (assembly reference)

More “old school”:

<ItemGroup>
  <Reference Include="System.Xml" />
  <Reference Include="SomeLegacyLibrary">
    <HintPath>..\lib\SomeLegacyLibrary.dll</HintPath>
  </Reference>
</ItemGroup>
  • Direct reference to a DLL instead of a project or NuGet.

  • HintPath points to where the DLL lives.

In pure .NET 10 work you’ll see this less, but it’s common when interoperating with older libraries.

4.5 File classification: Compile, None, Content, EmbeddedResource

Even when you don’t explicitly declare them, the SDK implicitly defines items:

  • Compile → C# source files to compile (*.cs).

  • None → Files included in project but not compiled or treated as content by default.

  • Content → Files that should be published as part of the app.

  • EmbeddedResource → Files embedded directly into the assembly.

You override or customize them like:

<ItemGroup>
  <None Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
  <Content Include="wwwroot\**\*.*" />
  <EmbeddedResource Include="Resources\Strings.resx" />
</ItemGroup>

For None / Content, common metadata:

  • CopyToOutputDirectory = Never | Always | PreserveNewest

  • CopyToPublishDirectory = same options but for dotnet publish.

Example:

<None Include="appsettings.json"
      CopyToOutputDirectory="PreserveNewest"
      CopyToPublishDirectory="PreserveNewest" />

4.6 Analyzer

<ItemGroup>
  <Analyzer Include="packages\MyAnalyzers\analyzers\dotnet\cs\MyAnalyzer.dll" />
</ItemGroup>
  • Adds Roslyn analyzers (code style, warnings, etc.).

  • Many modern analyzer packages (e.g. Microsoft.CodeAnalysis.NetAnalyzers) automatically inject these via build/buildTransitive.

4.7 Other special item types (depending on workload)

Depending on SDK:

  • WPF (Microsoft.NET.Sdk.WindowsDesktop):

    • Page, ApplicationDefinition, Resource, Compile with XAML-specific metadata.

  • MAUI:

    • MauiXaml, MauiImage, MauiFont, etc.

  • Blazor / Razor:

    <RazorGenerate Include="Pages\Index.razor" />
    <RazorComponent Include="Components\*.razor" />
    

Most of the time, these are implicitly created by the SDK and you don’t see them unless you override default behavior.

5. Item metadata (the little attributes that matter a lot)

On any item (PackageReference, None, Content, etc.) you can have metadata—child elements inside:

Example: file copy semantics

<None Include="config\tenant-settings.json">
  <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
  • Include attribute: the actual file path or wildcard.

  • Child elements (metadata) define behavior.

Common metadata:

  • CopyToOutputDirectory

  • CopyToPublishDirectory

  • Private (for references – whether the DLL is copied locally)

  • Aliases (for references with aliasing)

  • SubType (old Visual Studio usage)

  • Link (display a file as if it lives in a different folder in VS)

Example using Link:

<None Include="..\Shared\GlobalSettings.json">
  <Link>Config\GlobalSettings.json</Link>
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

6. Conditions & custom targets

6.1 Conditions on properties or item groups

<PropertyGroup Condition="'$(Configuration)'=='Debug'">
  <DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)'=='net10.0'">
  <Nullable>enable</Nullable>
</PropertyGroup>
  • Condition is a little expression language ('$(PropertyName)' == 'value').

  • Can be applied to:

    • PropertyGroup

    • ItemGroup

    • Individual PackageReference, etc.

Example: only reference a package for certain TFMs:

<ItemGroup Condition="'$(TargetFramework)'=='net10.0'">
  <PackageReference Include="SomeNet10OnlyPackage" Version="1.0.0" />
</ItemGroup>

6.2 Custom targets

<Target Name="PostBuild" AfterTargets="Build">
  <Message Text="PostBuild target running for $(AssemblyName)" Importance="High" />
</Target>
  • Name = identifier of your target.

  • AfterTargets / BeforeTargets = hook points.

  • Inside, you call Tasks (e.g. <Copy>, <Exec>, <Message>).

Example: copy migration scripts after build:

<Target Name="CopyMigrations" AfterTargets="Build">
  <Copy SourceFiles="@(Content->'%(FullPath)')"
        DestinationFolder="$(OutputPath)\Migrations"
        SkipUnchangedFiles="true"
        Condition=" '%(Content.Extension)' == '.sql' " />
</Target>

7. How this plays with Clean Architecture / multi-project solutions

In a typical Clean Architecture solution:

  • Domain project (Domain.csproj)

    • Sdk="Microsoft.NET.Sdk"

    • TargetFramework>net10.0</TargetFramework>

    • Very minimal:

      • Few PackageReferences.

      • No references to Infrastructure or external frameworks.

  • Application project

    • ProjectReference → Domain.

    • PackageReference → MediatR, FluentValidation, etc.

  • Infrastructure project

    • ProjectReference → Application, Domain.

    • PackageReference → EF Core, Serilog sinks, external APIs.

  • API / UI / Worker project

    • Sdk="Microsoft.NET.Sdk.Web" (API/Blazor Server) or Worker.

    • ProjectReference → Application, Infrastructure.

    • PackageReference → Web-specific frameworks (Swashbuckle, HealthChecks, etc.).

    • Extra properties for Publish, RuntimeIdentifiers, etc.

The separation of responsibilities in the .csproj world is mostly:

  • Which project references which (ProjectReference)

  • Which frameworks are allowed (PackageReference)

  • How the thing is built and published (PropertyGroup)

#dotnet #csharp #dotnet10

Previous
Previous

Assembly Markers

Next
Next

Dependency Injection Using Scrutor