Csharp Project Setup
When to Use
Use this skill when:
- User asks how to initialize a new C# library, console app, worker service, or class library project with
dotnet newor manually - User asks how to structure a multi-project solution file (
.sln) with shared tooling - User asks about
Directory.Build.props,Directory.Build.targets, orDirectory.Packages.propsfor centralized configuration - User asks about NuGet configuration --
NuGet.Config,nuget.config, Central Package Management (CPM), or package source mapping - User asks how to configure SDK-style
.csprojfiles, target framework monikers, or multi-targeting - User asks about .NET 8 or .NET 9 project templates, implicit usings, nullable reference types, or
global.json - User asks about
global.jsonSDK pinning, build properties, or repository-level MSBuild conventions - User asks how to configure a project for NuGet package publishing, SourceLink, deterministic builds, or package metadata
Do NOT use this skill when:
- User asks about C# language features, pattern matching, records, or modern idioms -- use
csharp-modern-idioms - User asks about writing unit tests, test project setup, xUnit, NUnit, or Moq -- use
csharp-testing-patterns - User asks about ASP.NET Core Minimal API, controllers, middleware, or Blazor -- use
csharp-aspnet-patterns - User asks about Docker or Kubernetes deployment of a .NET app -- use a container deployment skill
- User asks about GitHub Actions, Azure Pipelines, or CI/CD pipeline configuration specifically -- use a CI/CD skill
- User asks about Entity Framework Core migrations or database modeling -- use a data-access skill
- User asks about .NET MAUI, Xamarin, or mobile project setup -- outside scope of this skill
Process
1. Assess Project Context and Choose the Right Template
Before generating any configuration, gather these critical dimensions:
- Project type: Library (
classlib), console app (console), worker service (worker), web API (webapi), gRPC service (grpc) -- each has different<OutputType>and SDK values - Deployment target: Is this a NuGet package, a self-contained executable, a framework-dependent app, or an AOT-compiled binary?
- Mono-repo vs. standalone: A monorepo with 3+ projects warrants
Directory.Build.props; a single project does not need it - Target framework: Default to
net8.0for LTS,net9.0for current. Usenet8.0;net9.0only when the library must support both. Never usenetstandard2.0for new libraries unless targeting legacy consumers requiring .NET Framework compatibility - Team size: Solo projects can use loose conventions; team projects must encode conventions in MSBuild properties and
.editorconfig - SDK version pinning: Always determine whether the repo needs a
global.json-- required when CI must reproduce builds exactly or when multiple SDK versions coexist on developer machines
Run dotnet --version to confirm the installed SDK. Use dotnet new list to see all available templates.
2. Initialize the Solution and Project Structure
Follow this canonical layout for any project beyond a single file:
/MyRepo
global.json ← SDK version pin
NuGet.Config ← package sources (optional)
Directory.Build.props ← shared MSBuild properties
Directory.Build.targets ← shared MSBuild targets (optional)
Directory.Packages.props ← Central Package Management versions (optional)
.editorconfig ← formatting and analysis rules
MyRepo.sln ← solution file
/src
/MyRepo.Core
MyRepo.Core.csproj
/MyRepo.Utilities
MyRepo.Utilities.csproj
/tests
/MyRepo.Core.Tests
MyRepo.Core.Tests.csproj
/samples
/MyRepo.Sample
MyRepo.Sample.csproj
- Create the solution file first:
dotnet new sln -n MyRepo - Create projects:
dotnet new classlib -n MyRepo.Core -o src/MyRepo.Core - Add projects to solution:
dotnet sln MyRepo.sln add src/MyRepo.Core/MyRepo.Core.csproj - Repeat for all projects. Never add test projects to the
src/directory -- keep them intests/ - The solution file is a human-readable grouping file; all real configuration lives in
.csprojandDirectory.Build.props - Use solution folders (
dotnet sln add --solution-folder) to visually groupsrc,tests, andsamplesin Visual Studio and Rider
3. Author the SDK-Style .csproj File
An SDK-style .csproj is declarative and minimal. Avoid the verbose legacy format:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest-recommended</AnalysisLevel>
</PropertyGroup>
<ItemGroup>
<!-- Project references use relative paths -->
<ProjectReference Include="..\..\src\MyRepo.Core\MyRepo.Core.csproj" />
</ItemGroup>
<ItemGroup>
<!-- Package versions come from Directory.Packages.props if using CPM -->
<PackageReference Include="Serilog" Version="3.1.1" />
</ItemGroup>
</Project>
Key property decisions:
<Nullable>enable</Nullable>-- mandatory for all new projects. Nullable reference types eliminate entire classes of null-dereference bugs<ImplicitUsings>enable</ImplicitUsings>-- adds a generatedGlobalUsings.g.cswithSystem,System.Collections.Generic,System.Linq, etc. Do not fight this feature; add additional global usings in aGlobalUsings.csfile<LangVersion>latest</LangVersion>-- tracks the latest stable C# version for the SDK. Usepreviewonly for experimental work<TreatWarningsAsErrors>true</TreatWarningsAsErrors>-- critical for team projects; forces nullable warnings and analyzer warnings to be resolved<AnalysisLevel>latest-recommended</AnalysisLevel>-- enables Roslyn analyzers without requiring a separate analyzer package. Options:none,default,latest,latest-recommended,latest-all<OutputType>Exe</OutputType>-- only required for console apps and workers. Libraries omit this; it defaults toLibrary<AllowUnsafeBlocks>true</AllowUnsafeBlocks>-- only in projects that explicitly require unsafe pointer operations
4. Configure Directory.Build.props for Centralized Properties
Directory.Build.props is evaluated by MSBuild before any .csproj file in the same directory or any subdirectory. Place it at the repository root to apply properties globally:
<Project>
<!-- Repository identity -->
<PropertyGroup>
<Product>MyRepo</Product>
<Company>Acme Corp</Company>
<Copyright>Copyright © Acme Corp $(Year)</Copyright>
<Authors>Acme Corp</Authors>
<RepositoryUrl>https://github.com/acme/myrepo</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Language and analysis defaults applied to ALL projects -->
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- Deterministic builds for reproducibility -->
<PropertyGroup Condition="'$(CI)' == 'true'">
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<!-- Artifact output: all output goes to a single /artifacts folder -->
<PropertyGroup>
<ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath>
</PropertyGroup>
</Project>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>-- causes.editorconfigstyle rules (IDE diagnostics) to fire as build errors, not just IDE squiggles<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>-- required for SourceLink to embed correct paths into PDB files; set conditionally on theCIenvironment variable- Use a
<PropertyGroup Condition="...">to vary behavior forsrc/vs.tests/projects by checking$(MSBuildProjectFullPath.Contains('tests')) Directory.Build.propsis not recursive-override -- a project-level.csprojcan override any property defined here. This is by designDirectory.Build.targetsis evaluated after.csproj, making it appropriate for targets that enforce post-build checks or file manipulation
5. Configure NuGet and Central Package Management
For repositories with 3 or more projects, Central Package Management (CPM) eliminates version drift across projects:
Directory.Packages.props (repository root):
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<!-- DisableCentralPackageVersions can be set per-project to opt out -->
</PropertyGroup>
<ItemGroup>
<!-- Versions declared once, referenced without version in .csproj -->
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="FluentValidation" Version="11.9.0" />
<PackageVersion Include="Polly" Version="8.3.0" />
</ItemGroup>
<!-- Test-only packages kept separate for clarity -->
<ItemGroup Label="Test">
<PackageVersion Include="xunit" Version="2.8.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
</ItemGroup>
</Project>
In each .csproj with CPM enabled, omit Version from <PackageReference>:
<PackageReference Include="Serilog" /> <!-- version resolved from Directory.Packages.props -->
NuGet.Config -- required when using a private feed or package source mapping:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<!-- Add private feed here if needed -->
<!-- <add key="MyFeed" value="https://pkgs.dev.azure.com/acme/_packaging/MyFeed/nuget/v3/index.json" /> -->
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
<clear />before<add>entries is a security best practice -- it prevents implicit pickup of machine-level or user-level feed configurations in CI- Package source mapping (available since NuGet 6.0) prevents dependency confusion attacks by binding packages to specific feeds
6. Pin the SDK with global.json
Always create global.json at the repository root for reproducible builds:
{
"sdk": {
"version": "8.0.401",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}
rollForward: "latestMinor"means the SDK will roll forward to the latest patch and minor within the major version. Use"patch"for maximum reproducibility,"latestMinor"for a balance of flexibility and stabilityallowPrerelease: falseprevents accidental use of preview SDKs in production builds- Run
dotnet --list-sdksto enumerate installed versions. Updateglobal.jsonintentionally when upgrading the SDK - In CI, explicitly install the exact SDK version matching
global.jsonusing thesetup-dotnetaction orUseDotNettask
7. Configure .editorconfig for Code Style and Roslyn Analyzers
.editorconfig is the single source of truth for both formatting and code-style diagnostic severity. Place it at the repository root:
root = true
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{cs,csx}]
indent_size = 4
# C# style rules (IDE diagnostics enforced in build via EnforceCodeStyleInBuild)
dotnet_style_qualification_for_field = false:error
dotnet_style_qualification_for_property = false:error
dotnet_style_prefer_auto_properties = true:error
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_prefer_braces = true:error
csharp_style_namespace_declarations = file_scoped:error
# Nullable diagnostics
dotnet_diagnostic.CS8600.severity = error # Converting null literal
dotnet_diagnostic.CS8601.severity = error # Possible null reference assignment
dotnet_diagnostic.CS8602.severity = error # Dereference of a possibly null reference
dotnet_diagnostic.CS8603.severity = error # Possible null reference return
dotnet_diagnostic.CS8618.severity = error # Non-nullable property uninitialized
# Suppressed rules with documented rationale
dotnet_diagnostic.CA1014.severity = none # Mark assemblies with CLSCompliant -- not needed for modern apps
dotnet_diagnostic.CA1062.severity = none # Validate parameters -- redundant with nullable enabled
[*.{csproj,props,targets}]
indent_size = 2
[*.json]
indent_size = 2
[*.{yml,yaml}]
indent_size = 2
csharp_style_namespace_declarations = file_scoped:errorenforces the C# 10+ file-scoped namespace syntax, eliminating one level of indentation- Severity levels:
none,suggestion,warning,error. Useerrorfor rules you would reject in code review - The
root = truesetting prevents.editorconfiginheritance from parent directories -- critical in monorepos
8. Verify the Setup and Configure .gitignore
Run a full build and restore to confirm all tooling is wired correctly:
# Restore all packages
dotnet restore MyRepo.sln
# Build in Release configuration
dotnet build MyRepo.sln -c Release --no-restore
# Run all tests
dotnet test MyRepo.sln --no-build -c Release
# Format check (no-modify, report only)
dotnet format MyRepo.sln --verify-no-changes --verbosity diagnostic
Minimum .gitignore entries for a .NET repository:
# Build outputs
**/bin/
**/obj/
**/artifacts/
# NuGet
**/*.nupkg
**/*.snupkg
**/packages/
**/project.assets.json
**/project.nuget.cache
**/.nuget/packages/
# IDE
.vs/
.idea/
*.user
*.suo
*.DotSettings.user
# OS
.DS_Store
Thumbs.db
Output Format
When providing a C# project setup recommendation, structure the response as follows:
## Project Setup Recommendation
### Context Summary
| Dimension | Assessment |
|------------------|-----------------------------------|
| Project type | [classlib / console / worker / …] |
| Target framework | [net8.0 / net9.0 / multi-target] |
| NuGet strategy | [CPM / per-project / none] |
| Mono-repo | [yes / no] |
| Publishing | [NuGet package / executable / …] |
### Directory Structure
[Annotated tree showing all relevant files and folders]
### File Contents
[Complete, copy-paste-ready content for each file, clearly labeled]
#### global.json
[complete file]
#### Directory.Build.props
[complete file]
#### Directory.Packages.props (if CPM applies)
[complete file]
#### NuGet.Config (if needed)
[complete file]
#### .editorconfig (key sections)
[complete file or relevant excerpt]
#### [ProjectName].csproj
[complete file]
### CLI Commands
[Ordered sequence of dotnet CLI commands to initialize the project]
### Decisions Log
| Decision | Choice | Rationale |
|-------------------------------|---------------------|------------------------------------------|
| Target framework | net8.0 | LTS; stable until Nov 2026 |
| Nullable reference types | enabled | Eliminates null-ref bugs at compile time |
| Central Package Management | enabled | 4+ projects; prevents version drift |
| TreatWarningsAsErrors | true | Enforces nullable compliance in CI |
Rules
Never use
<PackageReference>with floating versions (*,1.2.*,[1.0,2.0)). Pin exact versions inDirectory.Packages.propsor in the.csproj. Floating versions cause non-reproducible builds and can pull in breaking changes silently.Never target
netstandard2.0for new libraries unless the library must be consumed by .NET Framework 4.6.1+ projects. For libraries targeting only modern .NET, usenet8.0or multi-targetnet8.0;net9.0.netstandard2.0loses access to 5+ years of API surface and forces polyfills.Never place
<Version>inDirectory.Build.propsfor libraries intended for NuGet publication. Version must be set by CI usingdotnet pack -p:Version=$(GitVersion)or a MinVer/GitVersion integration. A hardcoded version in shared props means every package publishes the same version.Always set
<Nullable>enable</Nullable>from the first commit. Retrofitting nullable annotations onto an existing codebase with thousands of#nullable disablesuppressions is a multi-week project. Starting clean costs nothing.Always use
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>inDirectory.Build.props. Teams that leave warnings as warnings accumulate hundreds of unaddressed warnings within months, rendering the warning system useless. If a rule produces false positives, suppress it explicitly in.editorconfigwith a comment justifying the suppression.Never commit
bin/,obj/, or the NuGet cache to source control. These directories contain binary outputs and machine-specific paths. Theproject.assets.jsoninsideobj/contains absolute paths that will break other developers' builds.Always use file-scoped namespaces for all new C# 10+ code.
namespace MyRepo.Core;instead ofnamespace MyRepo.Core { ... }reduces indentation by one level for the entire file. Enforce this via.editorconfig:csharp_style_namespace_declarations = file_scoped:error.Never add
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>as a blanket workaround. This property disables auto-generation ofAssemblyInfo.csattributes. It is sometimes used to fix conflicts with a legacyAssemblyInfo.csfile -- but the correct fix is to delete the legacy file, not to disable generation.Always use
<ArtifactsPath>or an explicit output directory in CI rather than relying on per-projectbin/directories. This makes artifact collection in CI trivial and avoids hunting for outputs across subdirectories.Never mix SDK styles in the same solution. A solution should not combine legacy non-SDK
.csprojfiles (the verbose format from before .NET Core) with SDK-style projects. The two formats differ fundamentally in how they handle wildcards, transitive references, and implicit items. If a legacy project must remain, isolate it or migrate it.
Edge Cases
Multi-targeting for Library Compatibility
When a library must support both net8.0 and netstandard2.0 (for legacy consumers), use conditional compilation:
<TargetFrameworks>net8.0;net6.0;netstandard2.0</TargetFrameworks>
#if NET8_0_OR_GREATER
// Use System.Text.Json source generation
#elif NETSTANDARD2_0
// Use Newtonsoft.Json fallback
#endif
Be aware that multi-targeting multiplies build time. A project with 3 target frameworks builds 3 times. In a solution with 10 such projects, build time is 30x the single-framework cost. Only multi-target when the library is genuinely consumed by diverse consumers.
Projects That Must Opt Out of Global Properties
Some projects -- particularly generated code projects, scaffolded EF Core migration projects, or vendored third-party code -- cannot satisfy global constraints like TreatWarningsAsErrors. Override at the project level:
<PropertyGroup>
<!-- This is generated code; global warning-as-error policy does not apply -->
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);CS1591;CS8618</NoWarn>
</PropertyGroup>
Alternatively, use a second Directory.Build.props placed inside the subdirectory containing these projects. MSBuild walks up the directory tree and stops at the first Directory.Build.props it finds -- so a nested file in src/Generated/Directory.Build.props can override the root file for only those projects.
Native AOT and Trimming
When the target is a Native AOT executable (.NET 8+), the project must enable trimming-compatible code patterns:
<PropertyGroup>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization> <!-- reduces binary size -->
<TrimmerRootDescriptor>TrimmerRoots.xml</TrimmerRootDescriptor>
</PropertyGroup>
AOT imposes significant constraints: no runtime reflection, no dynamic, no Assembly.Load. Dependencies must be AOT-compatible. Run dotnet publish -r linux-x64 -c Release early and often to catch trimming warnings before they accumulate.
Enterprise Private NuGet Feed Without Internet Access
In air-gapped or enterprise environments where nuget.org is unreachable:
<!-- NuGet.Config -->
<packageSources>
<clear />
<add key="InternalFeed" value="https://artifacts.internal.corp/nuget/v3/index.json" />
</packageSources>
Also, set NUGET_PACKAGES environment variable in CI to a shared cache directory to avoid redundant downloads across agents. Use dotnet restore --locked-mode after an initial dotnet restore that generates packages.lock.json to guarantee package identity in CI.
Incremental Migration from Legacy .csproj Format
When migrating a Visual Studio 2015-era .csproj file to SDK style:
- Create a new SDK-style
.csprojalongside the old one in a branch - Remove all
<Compile Include="...">entries -- SDK style includes all*.csfiles by default - Remove all
<Reference>entries to BCL assemblies (System, System.Core, etc.) -- SDK style handles these via the target framework - Retain
<Reference>entries for COM interop and non-NuGet assemblies - Migrate
packages.configto<PackageReference>usingdotnet migrateor the Visual Studio migration wizard - Delete
packages.configand thepackages/directory from the repository - Verify that no
AssemblyInfo.csattributes conflict with SDK-generated ones ([assembly: AssemblyVersion],[assembly: AssemblyTitle], etc.) -- either delete the file or set<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
NuGet Package Publishing Configuration
When a library project will be published to NuGet.org or a private feed, the .csproj must include package metadata:
<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>Acme.MyRepo.Core</PackageId>
<!-- Version set by CI, not here: dotnet pack -p:Version=1.2.3 -->
<Description>Core abstractions for the MyRepo library.</Description>
<PackageTags>acme;myrepo;core</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/acme/myrepo</PackageProjectUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<!-- SourceLink: embed source in symbols for debugger step-through -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup>
<None Include="../../README.md" Pack="true" PackagePath="/" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
Set <IsPackable>false</IsPackable> explicitly on all test projects and sample projects to prevent accidental packaging.
Example
Input: "I'm starting a new .NET 8 open-source library called Conduit that will be published to NuGet.org. It has a core abstractions project, an implementation project, and tests. I want strict nullable checking, centralized package management, and a clean project structure."
Output:
Project Setup Recommendation
Context Summary
| Dimension | Assessment |
|---|---|
| Project type | classlib (2 projects) + test project |
| Target framework | net8.0 (LTS, suitable for OSS library) |
| NuGet strategy | Central Package Management (3 projects) |
| Mono-repo | No -- standalone library repository |
| Publishing | NuGet.org package + snupkg symbols |
Directory Structure
/Conduit
global.json
NuGet.Config
Directory.Build.props
Directory.Packages.props
.editorconfig
.gitignore
Conduit.sln
README.md
/src
/Conduit.Abstractions
Conduit.Abstractions.csproj
GlobalUsings.cs
/Pipelines
IPipeline.cs
/Conduit.Core
Conduit.Core.csproj
GlobalUsings.cs
/Pipelines
Pipeline.cs
/tests
/Conduit.Core.Tests
Conduit.Core.Tests.csproj
GlobalUsings.cs
/Pipelines
PipelineTests.cs
CLI Initialization Commands
# 1. Create repository directory and initialize solution
mkdir Conduit && cd Conduit
dotnet new sln -n Conduit
# 2. Create source projects
dotnet new classlib -n Conduit.Abstractions -o src/Conduit.Abstractions --framework net8.0
dotnet new classlib -n Conduit.Core -o src/Conduit.Core --framework net8.0
# 3. Create test project
dotnet new xunit -n Conduit.Core.Tests -o tests/Conduit.Core.Tests --framework net8.0
# 4. Add projects to solution with folder grouping
dotnet sln Conduit.sln add src/Conduit.Abstractions/Conduit.Abstractions.csproj --solution-folder src
dotnet sln Conduit.sln add src/Conduit.Core/Conduit.Core.csproj --solution-folder src
dotnet sln Conduit.sln add tests/Conduit.Core.Tests/Conduit.Core.Tests.csproj --solution-folder tests
# 5. Add project references
dotnet add src/Conduit.Core/Conduit.Core.csproj reference src/Conduit.Abstractions/Conduit.Abstractions.csproj
dotnet add tests/Conduit.Core.Tests/Conduit.Core.Tests.csproj reference src/Conduit.Core/Conduit.Core.csproj
# 6. Verify initial build
dotnet restore Conduit.sln
dotnet build Conduit.sln -c Release --no-restore
dotnet test Conduit.sln --no-build -c Release
File Contents
global.json
{
"sdk": {
"version": "8.0.401",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}
Directory.Build.props
<Project>
<!-- Shared identity for all packages in this repository -->
<PropertyGroup>
<Product>Conduit</Product>
<Authors>Conduit Contributors</Authors>
<Company>Conduit OSS</Company>
<Copyright>Copyright © Conduit Contributors</Copyright>
<RepositoryUrl>https://github.com/acme/conduit</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Language settings applied to every project -->
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- Deterministic builds in CI -->
<PropertyGroup Condition="'$(CI)' == 'true'">
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<!-- Consolidate all build output under /artifacts -->
<PropertyGroup>
<ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath>
</PropertyGroup>
<!-- Test projects do not produce NuGet packages -->
<PropertyGroup Condition="$(MSBuildProjectFullPath.Contains('tests'))">
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
</Project>
Directory.Packages.props
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<!-- SourceLink for symbol packages -->
<ItemGroup>
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
</ItemGroup>
<!-- Test dependencies -->
<ItemGroup Label="Test">
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="xunit" Version="2.8.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.1" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
</ItemGroup>
</Project>
NuGet.Config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
.editorconfig
root = true
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{cs,csx}]
indent_size = 4
# Namespace style: file-scoped
csharp_style_namespace_declarations = file_scoped:error
# Braces always required
csharp_prefer_braces = true:error
# var usage
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = false:suggestion
# Expression bodies
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = true:suggestion
# this. qualification
dotnet_style_qualification_for_field = false:error
dotnet_style_qualification_for_property = false:error
dotnet_style_qualification_for_method = false:error
# Nullable diagnostics as errors
dotnet_diagnostic.CS8600.severity = error
dotnet_diagnostic.CS8601.severity = error
dotnet_diagnostic.CS8602.severity = error
dotnet_diagnostic.CS8603.severity = error
dotnet_diagnostic.CS8618.severity = error
dotnet_diagnostic.CS8625.severity = error
# Suppressed: XML doc warnings on internal types -- OSS library only documents public API
dotnet_diagnostic.CS1591.severity = none
[*.{csproj,props,targets}]
indent_size = 2
[*.json]
indent_size = 2
[*.{yml,yaml}]
indent_size = 2
src/Conduit.Abstractions/Conduit.Abstractions.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageId>Conduit.Abstractions</PackageId>
<!-- Version is set by CI: dotnet pack -p:Version=$(VERSION) -->
<Description>Core interfaces and abstractions for the Conduit pipeline library.</Description>
<PackageTags>conduit;pipeline;abstractions;mediator</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/acme/conduit</PackageProjectUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="/" />
<!-- PrivateAssets=All: SourceLink is a build-time tool, not a runtime dep -->
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>
</Project>
src/Conduit.Core/Conduit.Core.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageId>Conduit.Core</PackageId>
<Description>Default pipeline implementation for the Conduit library.</Description>
<PackageTags>conduit;pipeline;mediator</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/acme/conduit</PackageProjectUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Conduit.Abstractions\Conduit.Abstractions.csproj" />
<None Include="..\..\README.md" Pack="true" PackagePath="/" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>
</Project>
tests/Conduit.Core.Tests/Conduit.Core.Tests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<!-- IsPackable=false inherited from Directory.Build.props for test projects -->
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Conduit.Core\Conduit.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
src/Conduit.Abstractions/GlobalUsings.cs
// Global usings for Conduit.Abstractions
// System.* usings are provided by ImplicitUsings; add domain-specific usings here.
global using System.Threading;
global using System.Threading.Tasks;
Decisions Log
| Decision | Choice | Rationale |
|---|---|---|
| Target framework | net8.0 only | LTS release; no .NET Framework consumers identified; avoids multi-target |
| Nullable reference types | enabled (error severity) | OSS library must have airtight null safety before consumers depend on it |
| Central Package Management | enabled | 3 projects sharing test dependencies; prevents version drift |
| TreatWarningsAsErrors | true | Enforces nullable and analyzer compliance; required for quality OSS |
| SourceLink | GitHub / snupkg | Enables debugger step-through for library consumers without source access |
| File-scoped namespaces | enforced via .editorconfig | C# 10+ standard; reduces indentation; consistent with modern OSS projects |
| Version location | CI-injected (-p:Version=...) |
Prevents accidental publish of hardcoded version; enables GitVersion/MinVer |
NuGet.Config with <clear /> |
yes | Prevents implicit machine-level feed injection in CI environments |
| EnforceCodeStyleInBuild | true | .editorconfig violations become build errors, not silent IDE suggestions |
| GenerateDocumentationFile | true | Produces XML docs for IntelliSense in downstream consumers |