Instruction file imported from f2calv/.github (
.github/instructions/dotnet.instructions.md). Copyright stays with the author.
.NET
Central Build Configuration
- Keep shared MSBuild properties in the root
Directory.Build.props— root namespace, language version,ImplicitUsings,Nullable,GenerateDocumentationFile, warning policy andContinuousIntegrationBuildfor deterministic CI builds. - Declare one
UserSecretsIdfor the whole repository in the rootDirectory.Build.props, as a GUID. Never declare a second one in an individual project file: each distinct identifier creates a separate secrets store, so per-project identifiers scatter the same credentials across several places and make them easy to set in one project and miss in another. - Changing an existing
UserSecretsIdorphans whatever is already stored under the old one. Check for an existing store before changing it, and migrate the contents rather than silently stranding them. - Keep individual project files minimal — only project-specific properties and references belong there.
- Keep repository-wide generated assembly attributes and
AssemblyMetadataitems in the rootDirectory.Build.props. Containerized applications receiveVersionandSourceRevisionIdfrom the build invocation; expose additional source-revision metadata there rather than repeating it in application project files. - Centralise warning suppressions (
NoWarn) inDirectory.Build.props, with a comment naming each suppressed diagnostic and why it is suppressed. Never suppress a diagnostic inline in a project file without a comment. - Use conditional property groups for cross-cutting project categories rather than repeating settings per project — for example disabling
GenerateDocumentationFilefor test projects, and gatingIsPackableso packaging is opt-in.
Language and Compiler Settings
- Enable nullable reference types and implicit usings repository-wide; do not disable either per project without a recorded reason.
- Every .NET repository root
Directory.Build.propsmust declare<TreatWarningsAsErrors>true</TreatWarningsAsErrors>in an unconditional property group so the policy applies by default to every project and build configuration. Do not repeat or override the property in individual project files. - Where a specific diagnostic must remain non-fatal, append it to a centrally documented
WarningsNotAsErrorsentry so it still surfaces. UseNoWarnonly when the diagnostic is genuinely inapplicable repository-wide and document why it is suppressed. - A security advisory raised by a transitive dependency that has no direct reference to remove and no fixed upstream release belongs in
WarningsNotAsErrors, never inNoWarn— suppressing it hides a real vulnerability. Record the advisory identifier and why it cannot yet be resolved, and re-check on every dependency bump.
Analyzers
- Configure analyzer severities in
.editorconfig, which is the single source of truth for style and analyzer rules; enableEnforceCodeStyleInBuildso style rules are enforced by the compiler and not only by the IDE. - Use the canonical DotNet EditorConfig tranche for warning severities, including
IDE0051,IDE0052,IDE0055, explicit interface-member accessibility, safe syntax modernizations, logging argument cost and the shared naming rules. WithTreatWarningsAsErrors, promoted violations fail builds rather than remaining editor suggestions. - Treat unused-member and unread-field diagnostics as actionable dead code and remove the flagged members. Do not add them to
WarningsNotAsErrors. - Promote other style preferences to warnings only through the
editorconfig-managementskill's measured tranches. - Write new code clean at
suggestionseverity too. SonarQube imports Roslyn suggestions such as CA1859, CA1873, IDE0037 and IDE0053 as issues, and the Quality Gate fails on any new issue even when the build passes. - Analyzer packages are declared once centrally and flow to every project; do not add them ad hoc per project.
Solution Format
- Use the modern XML
.slnxsolution format. Convert legacy.slnfiles rather than maintaining the old format alongside. - Add every new test project to the repository's default
.slnxor.slnfile in the same change. Do not create a separate test-only solution instead of keeping the default solution complete. - Where Debug and Release variants exist, name them
<Name>.Debug.slnxand<Name>.Release.slnx, matching the repository name. - The Debug solution wires local
ProjectReferenceitems to sibling libraries; the Release solution uses publishedPackageReferenceitems. - Prefer the Debug solution for local builds; CI builds the Release solution.
Target Frameworks
- Libraries intended for wide consumption multi-target the supported .NET releases; applications target a single framework.
- A change that compiles on one target framework must be verified on every target framework before it is considered done.
SDK Selection
- Stable .NET releases do not require an SDK version in
global.json; let the installed compatible stable SDK and the CI setup step select it. - Pin the SDK
versionandrollForwardpolicy when using a preview SDK, isolating an SDK regression, or when a workflow requires bit-for-bit reproducibility. - Keep
global.jsonwhen it configures repository-wide .NET CLI behaviour without pinning an SDK. Every .NET repository's rootglobal.jsonselects the test runner with{ "test": { "runner": "Microsoft.Testing.Platform" } }, even before it has tests; use thedotnet-test-platformskill for test project setup and migration.
Verification
- After any refactoring, build the entire solution rather than only the affected project, so compilation errors in dependent projects surface immediately.
- Where Debug and Release solutions both exist, build the Debug solution locally; it is the one wired to local project references.
- Ask before running a build.
