A senior plan for a reproducible multi-IDE, multi-OS .NET 10 environment: CLI and CI as the source of truth, global.json, .editorconfig, Directory.Build.props, shared IDE files, Dev Containers and secrets hygiene.
I standardise below the IDE rather than forcing one IDE. The CLI and CI are the source of truth. I pin the SDK with global.json, put style and analyser severities in .editorconfig, centralise build properties in Directory.Build.props and versions with Central Package Management, and commit shared IDE files such as .vsconfig, .vscode/extensions.json and launchSettings.json while ignoring personal state. I offer a Dev Container or a setup script for onboarding, keep secrets in user secrets and Key Vault, and CI runs the same dotnet format, build and test commands developers run locally.
"I standardise below the IDE rather than mandating one, because the CLI and CI are the source of truth and every IDE is just a client of the same SDK; forcing one IDE is the mistake I see, and it still doesn't guarantee identical builds. So I pin the SDK with
global.jsonand an explicit rollForward policy, put style and analyser severities in.editorconfigenforced in CI, centralise build properties inDirectory.Build.propsand versions with Central Package Management behind a trusted-sourcenuget.config, and commit shared IDE files such as.vsconfig,.vscode/extensions.jsonandlaunchSettings.jsonwhile ignoring personal state. Onboarding gets a Dev Container or a setup script, secrets go to user secrets locally and Key Vault with managed identity in Azure, and CI runs the same build script developers run.For example, on [our team of twelve], onboarding took [about two days] and builds regularly passed locally but failed in CI. I took ownership of making the environment reproducible without forcing anyone to change IDE. Comparing
dotnet --infoacross machines, I found [four] different SDKs including a preview, so I pinned the SDK, moved style into.editorconfig, centralised build settings and package versions, moved local secrets into user secrets, and added a Dev Container plus a singlebuild.shused by both developers and CI. The next new starter reached a green test run in [about an hour], and local-versus-CI failures dropped to [your number].I measure it with time to first green build and environment-related incidents, roll it out incrementally starting with
global.jsonand.editorconfig, and put SDK bumps on a schedule, because pinning means someone owns the upgrades."
Editors differ; the SDK does not. If every developer and CI agent runs the same SDK against the same project files with the same rules, the IDE becomes a personal preference. So I standardise the layers underneath.
global.json at the repo root pins 10.0.100 with rollForward: latestFeature, so everyone gets the newest installed .NET 10 SDK feature band and patch, with its security fixes. I also set allowPrerelease: false explicitly, because when it isn't set the command line allows preview SDKs, so a machine with a .NET 11 preview installed could otherwise pick it.Directory.Build.props sets Nullable, ImplicitUsings, TreatWarningsAsErrors, EnforceCodeStyleInBuild and AnalysisLevel once for every project.Directory.Packages.props with Central Package Management, plus nuget.config with <clear /> and only trusted sources, so restores are identical and protected from dependency confusion..editorconfig with root = true; CI runs dotnet format --verify-no-changes..vsconfig for Visual Studio workloads, .vscode/extensions.json, Properties/launchSettings.json profiles that every IDE and dotnet run understand. Ignore .vs/, .idea/ workspace files and *.user..devcontainer/devcontainer.json (VS Code and Rider can open it) and a short setup script for Visual Studio users that installs the SDK and applies .vsconfig.appsettings.Development.json; local values in dotnet user-secrets, Azure values in Key Vault accessed with managed identity.dotnet restore, dotnet format --verify-no-changes, dotnet build -c Release, dotnet test. Developers can run the same script locally.global.json (with allowPrerelease: true) or a separate Dev Container image builds and tests against the preview SDK, and the main branch moves only when the team decides to adopt the next release. Routine SDK patch bumps happen on a schedule.Track time to first successful build and test for a new starter, and the count of environment-related support requests. Both are easy to measure and show whether the investment worked.
On a team of twelve building services hosted in Azure Australia East, onboarding took about two days and builds sometimes passed locally but failed in CI. After the changes above, a new starter on macOS cloned the repo, opened the Dev Container and ran the tests within an hour, and local-versus-CI failures became rare enough that each one was treated as a bug.
The interviewer is assessing whether you can lead technical standards for a team without turning them into IDE wars, and whether you think about reproducibility, security and measurement.
Green flags
Red flags
repo/
+-- global.json <- which SDK (all IDEs + CLI + CI)
+-- nuget.config <- which package sources (trusted only)
+-- Directory.Build.props <- shared MSBuild properties
+-- Directory.Packages.props <- central package versions
+-- .editorconfig <- style + analyser severities
+-- .vsconfig <- Visual Studio workloads
+-- .vscode/extensions.json <- VS Code recommendations
+-- .devcontainer/devcontainer.json
+-- .gitignore <- excludes .vs/, .idea workspace, *.user, bin/, obj/
+-- build.sh <- the exact commands CI runs
+-- src/Orders.Api/Properties/launchSettings.json <- run profiles for every IDE
developer IDE --+
+--> same SDK + same props + same rules --> same output
CI pipeline --+
| Drift | Symptom | File that fixes it |
|---|---|---|
| Different SDKs | New warnings on one machine, "works on mine" | global.json |
| Different build settings | Nullable warnings only in some projects | Directory.Build.props |
| Different package versions | Runtime MissingMethodException, restore differences |
Directory.Packages.props (+ lock files) |
| Different package sources | Restore works locally, fails in CI; dependency confusion | nuget.config with <clear /> and source mapping |
| Different style | Formatting churn in reviews | .editorconfig + dotnet format in CI |
| Different IDE components | Missing templates or designers | .vsconfig, .vscode/extensions.json |
| Different OS toolchains | Linux-only bugs | Dev Container matching the CI image |
| Different commands | CI fails on things local builds skip | One build.sh used by both |
rollForward: latestFeature keeps everyone on .NET 10 while taking the newest feature band and patch installed. Stricter teams use latestPatch and bump the band deliberately.allowPrerelease: false explicitly. When it isn't set, the command line allows preview SDKs (Visual Studio follows its own preview setting), so an agent with a preview installed could pick it.actions/setup-dotnet with global-json-file, or UseDotNet@2 with useGlobalJson: true) and prints dotnet --info first, so the SDK in use is visible in every log.RestorePackagesWithLockFile and --locked-mode in CI if you need stronger guarantees), same compiler options.ContinuousIntegrationBuild=true so outputs are deterministic and don't embed local paths.--no-build, --no-restore).nuget.config with <clear /> removes machine-level sources nobody reviewed. Package source mapping pins each package id pattern to one source, so an attacker can't publish a same-named package on nuget.org to replace an internal one (dependency confusion):
<packageSourceMapping>
<packageSource key="nuget.org"><package pattern="*" /></packageSource>
<packageSource key="internal"><package pattern="Contoso.*" /></packageSource>
</packageSourceMapping>
| Benefit | Cost |
|---|---|
| Same Linux toolchain as CI and production containers | Windows desktop workloads (WPF, WinForms) can't run in them |
| Dependencies (SQL Server, Azurite) as sibling containers | Bind mounts can be slow on macOS and Windows for large repos |
| Onboarding in minutes | Visual Studio doesn't open them |
| Needs Docker Desktop (paid for larger companies) or an alternative, and images must be kept patched |
So Windows Visual Studio users still need the setup-script path.
global.json and .editorconfig first: cheap, high impact.Directory.Build.props and Central Package Management.nuget.config hardening.build.sh adopted by CI.Measure time to first green build and environment-related CI failures before and after.
dotnet --info and checks IDE versions.To try .NET 11 previews, use a branch with its own global.json (allowPrerelease: true) or a separate container image, never a developer's global install, so the main branch stays on .NET 10 LTS.
global.json, Directory.Build.props and .devcontainer/devcontainer.json from the code example to a small solution.dotnet test.allowPrerelease from global.json, and run dotnet --version to see the preview get picked; put the setting back.global.json:
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature",
"allowPrerelease": false
}
}
Directory.Build.props:
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-recommended</AnalysisLevel>
</PropertyGroup>
</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" />
</packageSources>
</configuration>
.devcontainer/devcontainer.json:
{
"name": "orders-service",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/dotnet:2": { "version": "10.0" }
},
"customizations": {
"vscode": { "extensions": ["ms-dotnettools.csdevkit"] }
},
"postCreateCommand": "dotnet restore"
}
The same commands locally and in CI (build.sh):
#!/usr/bin/env bash
set -euo pipefail
dotnet restore
dotnet format --verify-no-changes --no-restore
dotnet build -c Release --no-restore
dotnet test -c Release --no-build
Mistake: mandating a single IDE. It costs productivity for people fastest in another tool, and doesn't guarantee identical builds anyway. Instead: standardise the SDK, build settings, package versions and rules underneath, and let people choose their editor.
Mistake: no global.json.
Each machine and agent uses whatever SDK is newest, including a .NET 11 preview someone installed to experiment.
Instead: commit global.json with a version, a rollForward policy and allowPrerelease: false.
Mistake: leaving allowPrerelease unset.
Unset, the command line allows preview SDKs, so an agent with a preview installed picks it while Visual Studio may not.
Instead: set "allowPrerelease": false explicitly on main branches.
Mistake: choosing a roll-forward policy without thinking about it.
latestFeature lets machines use different feature bands (10.0.1xx and 10.0.2xx), which can bring analyser or MSBuild changes.
Instead: choose deliberately: latestFeature for most teams, latestPatch for stricter ones, and confirm CI and developers match by printing dotnet --version in the pipeline log.
Mistake: pinning the SDK and never updating it. The team misses security patches and falls behind on fixes. Instead: bump it on a schedule (for example monthly after Patch Tuesday) in one pull request that also updates CI images and the Dev Container.
Mistake: trying previews on the main branch.
Preview SDKs bring new warnings and behaviour changes to everyone at once.
Instead: evaluate previews on a spike branch with its own global.json, or in a separate container image.
Mistake: CI that doesn't fail on test failures.
continueOnError on the test step, or a script without set -e, lets broken builds go green.
Instead: let dotnet test's non-zero exit code fail the run: use set -euo pipefail in scripts and no continueOnError on the test step.
Mistake: committing personal IDE state (.vs/, .idea/workspace.xml, *.user).
It causes constant conflicts and leaks local paths.
Instead: ignore those files and commit only shared ones such as .vsconfig, .vscode/extensions.json and the team-shared .DotSettings.
Mistake: different commands in CI and locally.
For example, CI builds Release with warnings as errors while developers build Debug without, so problems appear only in CI.
Instead: put the exact commands in one build.sh that both CI and developers run.
Mistake: secrets in launchSettings.json or appsettings.Development.json "because it is only local".
Both are committed, so every clone and fork holds the secret.
Instead: use dotnet user-secrets locally and Azure Key Vault with managed identity in Azure.
Mistake: treating containers as a universal answer. Windows desktop workloads can't run in them, Visual Studio doesn't open them, and bind mounts can be slow. Instead: offer a Dev Container for macOS and Linux users and container-friendly projects, plus a setup script for Visual Studio users.
This is a developer-experience investment with measurable return. A senior engineer frames it in numbers (onboarding time, CI failures caused by environment drift, review time lost to style) and rolls it out incrementally: global.json and .editorconfig first because they are cheap and high impact, then shared props and Central Package Management, then Dev Containers. The trade-off to name is flexibility versus control: pinning the SDK means someone owns regular upgrades, so put SDK bumps on a schedule (for example monthly with Patch Tuesday) rather than letting the pin go stale.
At Staff level the same files become governance across many repos, published from a template repository or shared build package.
For .NET 10 you need Visual Studio 2026. The edition depends on licensing and features: Community is free for individuals, open source and small organisations; Professional is the normal paid edition; Enterprise adds tools like IntelliTrace and Live Unit Testing. You install workloads such as ASP.NET and web development. To keep the team consistent, export a .vsconfig from the Visual Studio Installer and commit it at the repo root: Visual Studio then prompts anyone missing a component to install it, and the installer can apply the same file on build agents.
The dotnet host looks for a global.json from the current folder upwards. If there isn't one, it uses the latest installed SDK, even a preview. If there is one, it reads sdk.version and applies the rollForward policy to choose from the installed SDKs. With a version and no policy, the default is latestPatch, meaning the same feature band with the newest patch. allowPrerelease controls whether previews count. If nothing matches, every SDK command fails. I pin the SDK in every repo so developers, IDEs and CI build with the same compiler, analysers and defaults. dotnet --info shows which SDK was picked and why.
Directory.Build.props and Directory.Build.targets are ordinary MSBuild files that the SDK imports automatically into every project below them. For each project, MSBuild searches upwards and uses the nearest one it finds. .props is imported early, before the project's own content, so it's for default properties and items: target framework, Nullable, analysers, package metadata. Projects can still override those values. .targets is imported late, after the project, so it's for targets and logic that need final values. Only the nearest file is picked up. To layer them, the inner file imports the outer one with GetPathOfFileAbove. I debug them with dotnet msbuild -preprocess or a binary log.
How to tell it (STAR)
**Situation:** On [our team of twelve], onboarding took about two days and we had regular builds that passed locally but failed in CI because everyone's environment was slightly different. **Task:** I took ownership of making the environment reproducible without forcing anyone to change IDE. **Action:** I pinned the SDK with `global.json`, moved style into `.editorconfig`, centralised build settings and package versions, cleaned up shared IDE files and secrets, and added a Dev Container plus a single build script used by both developers and CI. **Result:** The next new starter reached a green test run in [about an hour], and local-versus-CI failures dropped to [your number].
Last updated 1 Nov 2026.