Background & Motivation
.NET developers building Azure applications with azd need hook support in C#. The .NET SDK is already commonly installed alongside azd for Aspire and other .NET workloads. The existing dotnet.Cli in azd provides restore/build/run capabilities that the executor can reuse.
Parent Epic: #7435 — Multi-Language Hook Support
Phase 1 PR: #7451 — delivered the extensible framework and Python support
Independent of: #7621 (JavaScript) and #7622 (TypeScript) — can be implemented in parallel
User Story
As an azd user, I want to write hook scripts in C# (.cs files or .csproj projects) so that I can use .NET for hook automation, especially in Aspire-based projects.
Existing Infrastructure (from Phase 1)
The following already exist and are shared by all new executors:
HookExecutor interface (pkg/tools/script.go) — Prepare/Execute/Cleanup lifecycle
HookKind constant dotnet (pkg/tools/language/executor.go)
InferKindFromPath() — maps .cs→dotnet
- Project discovery (
pkg/tools/language/project_discovery.go) — walk-up for *.*proj
- IoC-based executor resolution in
hooks_runner.go
dotnet.Cli singleton in cmd/container.go
- Schema support for
kind: dotnet in azure.yaml.json
ExecutionContext with Cwd, EnvVars, BoundaryDir, etc.
Solution Approach
- Create
dotnetExecutor in pkg/tools/language/dotnet_executor.go implementing HookExecutor
- Two execution modes:
- Project mode (with
.csproj): dotnet restore → dotnet build → dotnet run
- Single-file mode (
.cs only, .NET 10+): dotnet run script.cs (if supported by installed SDK)
- Prepare():
- Validate .NET SDK installed via existing
dotnet.Cli — hard error with ErrorWithSuggestion if missing
- Discover
.csproj files via DiscoverProjectFile() (glob pattern *.*proj already in project_discovery.go)
- If project found:
dotnet restore
- If single-file and .NET 10+: no restore needed
- If single-file and .NET < 10: error with suggestion to create a project or upgrade SDK
- Execute():
- Project mode:
dotnet run --project path/to/project.csproj
- Single-file:
dotnet run script.cs
- Pass environment variables via
ExecutionContext.EnvVars
- Cleanup(): No-op
- Register
"dotnet" as named transient in cmd/container.go
- Update
docs/language-hooks.md with .NET section
Acceptance Criteria
Out of Scope
- F# or VB.NET hook support
- .NET Framework (Windows-only) — only .NET SDK (cross-platform)
- NuGet package management beyond what
dotnet restore handles
- Custom MSBuild targets or properties
Testing Expectations
- Unit tests for
dotnetExecutor (project and single-file modes)
- Unit tests for
.csproj discovery and SDK version detection
- E2E test:
.csproj project → restore → build → run → verify output
- E2E test: single-file
.cs on .NET 10+ → run → verify
- E2E test: missing .NET SDK → error with suggestion
- E2E test:
continueOnError: true behavior
- E2E test: service-level .NET hooks
Constraints
- Must reuse existing
dotnet.Cli singleton — do not create a separate .NET wrapper
- Follow the Prepare/Execute/Cleanup lifecycle pattern
- SDK version detection needed for single-file mode gating
Related Issues
Background & Motivation
.NET developers building Azure applications with azd need hook support in C#. The .NET SDK is already commonly installed alongside azd for Aspire and other .NET workloads. The existing
dotnet.Cliin azd provides restore/build/run capabilities that the executor can reuse.Parent Epic: #7435 — Multi-Language Hook Support
Phase 1 PR: #7451 — delivered the extensible framework and Python support
Independent of: #7621 (JavaScript) and #7622 (TypeScript) — can be implemented in parallel
User Story
As an azd user, I want to write hook scripts in C# (
.csfiles or.csprojprojects) so that I can use .NET for hook automation, especially in Aspire-based projects.Existing Infrastructure (from Phase 1)
The following already exist and are shared by all new executors:
HookExecutorinterface (pkg/tools/script.go) — Prepare/Execute/Cleanup lifecycleHookKindconstantdotnet(pkg/tools/language/executor.go)InferKindFromPath()— maps.cs→dotnetpkg/tools/language/project_discovery.go) — walk-up for*.*projhooks_runner.godotnet.Clisingleton incmd/container.gokind: dotnetinazure.yaml.jsonExecutionContextwith Cwd, EnvVars, BoundaryDir, etc.Solution Approach
dotnetExecutorinpkg/tools/language/dotnet_executor.goimplementingHookExecutor.csproj):dotnet restore→dotnet build→dotnet run.csonly, .NET 10+):dotnet run script.cs(if supported by installed SDK)dotnet.Cli— hard error withErrorWithSuggestionif missing.csprojfiles viaDiscoverProjectFile()(glob pattern*.*projalready in project_discovery.go)dotnet restoredotnet run --project path/to/project.csprojdotnet run script.csExecutionContext.EnvVars"dotnet"as named transient incmd/container.godocs/language-hooks.mdwith .NET sectionAcceptance Criteria
.cshook scripts auto-detected and executed via .NET SDK.csprojprojects discovered via walk-up, restored and built before executiondotnet restoreanddotnet buildrun during Prepare phase.csscripts supported on .NET 10+ without requiring a.csprojErrorWithSuggestionkind: dotnetexplicit override works in azure.yaml"dotnet"in container.goOut of Scope
dotnet restorehandlesTesting Expectations
dotnetExecutor(project and single-file modes).csprojdiscovery and SDK version detection.csprojproject → restore → build → run → verify output.cson .NET 10+ → run → verifycontinueOnError: truebehaviorConstraints
dotnet.Clisingleton — do not create a separate .NET wrapperRelated Issues