The Complete Guide to Setting Up Visual Studio Code for .NET Debugging

dotnet vscode how-to
08 Dec 2025

Visual Studio Code (VSCode) is favored by developers for its lightweight footprint, rich extension ecosystem, and powerful capabilities. When it comes to .NET development, VSCode can be fine-tuned to deliver a robust debugging experience on par with full-featured IDEs like Visual Studio. In this comprehensive guide, we’ll walk you through every step of setting up VSCode for .NET debugging-from installing essential extensions and configuring launch settings to harnessing advanced debugging features.

Table of Contents


Prerequisites

Before setting up VSCode for .NET debugging, ensure you have:

.NET SDK Installation

Download and install the .NET SDK from https://dotnet.microsoft.com/download

Verify installation:

dotnet --version

You should see output like:

10.0.100

Check installed SDKs:

dotnet --list-sdks

Installing Required Extensions

C# Dev Kit (Primary Extension)

The C# Dev Kit is the official Microsoft extension for .NET development in VSCode.

Installation Steps:

  1. Open VSCode
  2. Access Extensions (Ctrl+Shift+X on Windows/Linux, Cmd+Shift+X on macOS)
  3. Search for “C# Dev Kit”
  4. Install the extension by Microsoft

The C# Dev Kit automatically installs these companion extensions:

  • C# - Base language support
  • IntelliCode for C# Dev Kit - AI-assisted development
  • .NET Install Tool - Manages .NET SDK installations

What C# Dev Kit Provides:

  • IntelliSense (code completion)
  • Code navigation (Go to Definition, Find References)
  • Debugging support
  • Solution Explorer
  • Test Explorer
  • Project templates
  • Refactoring tools

Verifying Extension Installation

  1. Open Command Palette (Ctrl+Shift+P / Cmd+Shift+P)
  2. Type “Developer: Show Running Extensions”
  3. Verify these extensions are active:
    • C# Dev Kit
    • C#
    • C# IntelliCode

Understanding the .vscode Folder

When you configure a .NET project for debugging, VSCode creates a .vscode folder in your workspace root with three key files:

your-project/
├── .vscode/
│   ├── launch.json      # Debug configurations
│   ├── tasks.json       # Build and task automation
│   └── settings.json    # Workspace-specific settings
├── Program.cs
├── YourProject.csproj
└── YourSolution.sln

File Purposes:

  • launch.json: Defines how to launch and debug your application
  • tasks.json: Defines build, test, and other automation tasks
  • settings.json: Workspace-specific editor settings

Configuring launch.json

The launch.json file is the heart of your debugging configuration. Let’s build one from scratch.

Auto-Generating launch.json

The easiest way to create a launch.json:

  1. Open your .NET project in VSCode
  2. Go to Run and Debug view (Ctrl+Shift+D / Cmd+Shift+D)
  3. Click “create a launch.json file”
  4. Select ”.NET 5+ and .NET Core” from the environment list

VSCode will generate a basic configuration.

Understanding launch.json Structure

Here’s a detailed breakdown:

{
    "version": "0.2.0",
    "configurations": [
        {
            // Human-readable name shown in debug dropdown
            "name": ".NET Core Launch (console)",

            // Debugger type: "coreclr" for .NET Core/5+
            "type": "coreclr",

            // "launch" = start new process
            // "attach" = attach to running process
            "request": "launch",

            // Task to run before debugging
            "preLaunchTask": "build",

            // Path to compiled .dll
            "program": "${workspaceFolder}/bin/Debug/net10.0/YourApp.dll",

            // Command line arguments
            "args": [],

            // Working directory when app runs
            "cwd": "${workspaceFolder}",

            // Console type: "integratedTerminal", "internalConsole", "externalTerminal"
            "console": "integratedTerminal",

            // Whether to break at entry point
            "stopAtEntry": false
        }
    ]
}

Variable Substitution

VSCode supports these variables in launch.json:

  • ${workspaceFolder} - Root folder opened in VSCode
  • ${workspaceFolderBasename} - Folder name without slashes
  • ${file} - Currently opened file
  • ${fileBasename} - Currently opened filename
  • ${fileDirname} - Directory of currently opened file
  • ${fileExtname} - Extension of current file
  • ${cwd} - Current working directory
  • ${env:VARIABLE} - Environment variable

Real-World Example: Multiple Launch Configurations

Here’s a practical example showing multiple configurations for a CLI application:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "CLI: Import Data",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "${workspaceFolder}/bin/Debug/net10.0/MyCliApp.dll",
            "args": ["import", "--source", "data.csv", "--batch-size", "100"],
            "cwd": "${workspaceFolder}",
            "console": "integratedTerminal",
            "stopAtEntry": false,
            "env": {
                "DATABASE_CONNECTION": "Server=localhost;Database=mydb;",
                "LOG_LEVEL": "Debug"
            }
        },
        {
            "name": "CLI: Export Data",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "${workspaceFolder}/bin/Debug/net10.0/MyCliApp.dll",
            "args": ["export", "--format", "json", "--output", "export.json"],
            "cwd": "${workspaceFolder}",
            "console": "integratedTerminal",
            "stopAtEntry": false,
            "env": {
                "DATABASE_CONNECTION": "Server=localhost;Database=mydb;"
            }
        },
        {
            "name": "CLI: Process Queue",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "${workspaceFolder}/bin/Debug/net10.0/MyCliApp.dll",
            "args": ["process", "--workers", "5", "--timeout", "30"],
            "cwd": "${workspaceFolder}",
            "console": "integratedTerminal",
            "stopAtEntry": false,
            "env": {
                "QUEUE_CONNECTION": "Endpoint=sb://...",
                "ENVIRONMENT": "Development"
            }
        },
        {
            "name": "CLI: Custom Arguments",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "${workspaceFolder}/bin/Debug/net10.0/MyCliApp.dll",
            "args": [],
            "cwd": "${workspaceFolder}",
            "console": "integratedTerminal",
            "stopAtEntry": false,
            "env": {
                "DATABASE_CONNECTION": "Server=localhost;Database=mydb;"
            }
        }
    ]
}

Key Configuration Properties Explained

1. Console Types

"console": "integratedTerminal"  // VSCode's integrated terminal (recommended)
"console": "internalConsole"     // Debug Console panel (no input support)
"console": "externalTerminal"    // System terminal window

When to use each:

  • integratedTerminal: Best for most scenarios, allows user input
  • internalConsole: Read-only output, good for simple logging
  • externalTerminal: When you need native terminal features

2. Environment Variables

"env": {
    "ASPNETCORE_ENVIRONMENT": "Development",
    "SERVICEBUS_CONNECTION_STRING": "Endpoint=sb://...",
    "LOG_LEVEL": "Debug"
}

3. Source File Map (for debugging published apps)

"sourceFileMap": {
    "/Views": "${workspaceFolder}/Views"
}

4. Just My Code

"justMyCode": false  // Enable to step into framework code

5. Require Exact Source

"requireExactSource": false  // Allow debugging when source doesn't match

Configuration for Different Project Types

Console Application

{
    "name": ".NET Console App",
    "type": "coreclr",
    "request": "launch",
    "preLaunchTask": "build",
    "program": "${workspaceFolder}/bin/Debug/net10.0/ConsoleApp.dll",
    "args": [],
    "cwd": "${workspaceFolder}",
    "console": "integratedTerminal",
    "stopAtEntry": false
}

ASP.NET Core Web API

{
    "name": ".NET Web API",
    "type": "coreclr",
    "request": "launch",
    "preLaunchTask": "build",
    "program": "${workspaceFolder}/bin/Debug/net10.0/WebApi.dll",
    "args": [],
    "cwd": "${workspaceFolder}",
    "stopAtEntry": false,
    "env": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000"
    },
    "serverReadyAction": {
        "action": "openExternally",
        "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
    }
}

The serverReadyAction automatically opens your browser when the web server is ready.

Blazor WebAssembly

{
    "name": "Blazor WASM",
    "type": "blazorwasm",
    "request": "launch",
    "cwd": "${workspaceFolder}",
    "browser": "edge"
}

Attach to Running Process

{
    "name": "Attach to Process",
    "type": "coreclr",
    "request": "attach",
    "processId": "${command:pickProcess}"
}

When you debug with this configuration, VSCode shows a process picker.


Configuring tasks.json

The tasks.json file defines automated tasks like building, testing, and publishing.

Auto-Generating tasks.json

  1. Press Ctrl+Shift+P (Cmd+Shift+P on macOS)
  2. Type “Tasks: Configure Task”
  3. Select “Create tasks.json from template”
  4. Choose ”.NET Core”

Understanding tasks.json Structure

{
    "version": "2.0.0",
    "tasks": [
        {
            // Human-readable task name
            "label": "build",

            // Command to execute
            "command": "dotnet",

            // Process type (vs "shell")
            "type": "process",

            // Command arguments
            "args": [
                "build",
                "${workspaceFolder}/YourProject.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary;ForceNoAlign"
            ],

            // How to detect errors in output
            "problemMatcher": "$msCompile",

            // Task grouping
            "group": {
                "kind": "build",
                "isDefault": true  // This is the default build task
            }
        }
    ]
}

Complete tasks.json Example

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "dotnet",
            "type": "process",
            "args": [
                "build",
                "${workspaceFolder}/YourProject.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary;ForceNoAlign"
            ],
            "problemMatcher": "$msCompile",
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "label": "publish",
            "command": "dotnet",
            "type": "process",
            "args": [
                "publish",
                "${workspaceFolder}/YourProject.csproj",
                "-c", "Release",
                "-o", "${workspaceFolder}/publish",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary;ForceNoAlign"
            ],
            "problemMatcher": "$msCompile"
        },
        {
            "label": "watch",
            "command": "dotnet",
            "type": "process",
            "args": [
                "watch",
                "run",
                "--project",
                "${workspaceFolder}/YourProject.csproj"
            ],
            "problemMatcher": "$msCompile",
            "isBackground": true
        },
        {
            "label": "clean",
            "command": "dotnet",
            "type": "process",
            "args": [
                "clean",
                "${workspaceFolder}/YourProject.csproj"
            ],
            "problemMatcher": "$msCompile"
        },
        {
            "label": "test",
            "command": "dotnet",
            "type": "process",
            "args": [
                "test",
                "${workspaceFolder}/YourProject.Tests.csproj"
            ],
            "problemMatcher": "$msCompile",
            "group": {
                "kind": "test",
                "isDefault": true
            }
        }
    ]
}

Task Properties Explained

Problem Matchers

Problem matchers parse task output to detect errors and warnings:

  • $msCompile - MSBuild/C# compiler errors
  • $tsc - TypeScript compiler
  • $eslint-compact - ESLint errors

Custom problem matcher:

"problemMatcher": {
    "owner": "custom",
    "fileLocation": ["relative", "${workspaceFolder}"],
    "pattern": {
        "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
        "file": 1,
        "line": 2,
        "column": 3,
        "severity": 4,
        "message": 5
    }
}

Task Dependencies

Run multiple tasks in sequence:

{
    "label": "build-and-test",
    "dependsOn": ["clean", "build", "test"],
    "dependsOrder": "sequence"
}

Background Tasks

For long-running processes like dotnet watch:

{
    "label": "watch",
    "isBackground": true,
    "problemMatcher": {
        "base": "$msCompile",
        "background": {
            "activeOnStart": true,
            "beginsPattern": "^\\s*Waiting for a file to change",
            "endsPattern": "^\\s*Application started"
        }
    }
}

Debugging Features

Setting Breakpoints

Standard Breakpoints

  1. Click in the gutter (left of line numbers)
  2. Or press F9 on the line
  3. Red dot appears

Conditional Breakpoints

Right-click breakpoint > Edit Breakpoint

Expression condition:

userId == "admin"

Hit count:

>= 10

Log message (tracepoint):

User {userId} processed at {DateTime.Now}

Debug Actions

ActionShortcutDescription
ContinueF5Resume execution
Step OverF10Execute current line, skip function internals
Step IntoF11Enter function call
Step OutShift+F11Exit current function
RestartCtrl+Shift+F5Restart debugging session
StopShift+F5Stop debugging

Debug Views

Variables View

Shows all local variables, arguments, and their current values.

Watch custom expressions:

  1. Hover over variable
  2. Click + to add to Watch

Watch View

Add expressions to monitor:

message.Length
DateTime.Now.ToString()
user?.Email ?? "No email"

Call Stack View

Shows execution path:

Main() Program.cs:15
ProcessMessage() MessageHandler.cs:42
ValidateMessage() MessageValidator.cs:28

Click any frame to see local context.

Breakpoints View

Lists all breakpoints. Check/uncheck to enable/disable without removing.

Debug Console

Evaluate expressions during debugging:

> message.Properties.Count
5
> DateTime.UtcNow
2025-12-11 10:30:00
> user.Email
"admin@example.com"

Execute methods:

> Console.WriteLine("Debug message")
Debug message

Advanced Debugging Techniques

1. Data Breakpoints (Memory Breakpoints)

Break when a variable’s value changes:

In Variables view:

  1. Right-click variable
  2. Select “Break on Value Change”

2. Exception Settings

Configure which exceptions break execution:

  1. Open Run and Debug view
  2. Click “Exception Settings” in toolbar
  3. Check/uncheck exception types

Or in launch.json:

"exceptionOptions": {
    "breakOnAll": false,
    "breakOnAny": [
        "System.NullReferenceException",
        "System.ArgumentException"
    ]
}

3. Edit and Continue

Modify code while debugging (limited support in .NET):

  1. Pause execution at breakpoint
  2. Edit code
  3. Continue - changes apply if possible

Enable in launch.json:

"enableStepFiltering": false,
"enableEditAndContinue": true

Debug NuGet packages with original source code:

"justMyCode": false,
"requireExactSource": false,
"symbolOptions": {
    "searchPaths": [],
    "searchMicrosoftSymbolServer": true,
    "searchNuGetOrgSymbolServer": true
}

5. Multi-Target Debugging

Debug multiple processes simultaneously:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "API",
            "type": "coreclr",
            "request": "launch",
            "program": "${workspaceFolder}/api/bin/Debug/net10.0/api.dll"
        },
        {
            "name": "Worker",
            "type": "coreclr",
            "request": "launch",
            "program": "${workspaceFolder}/worker/bin/Debug/net10.0/worker.dll"
        }
    ],
    "compounds": [
        {
            "name": "API + Worker",
            "configurations": ["API", "Worker"],
            "stopAll": true
        }
    ]
}

6. Remote Debugging

Debug application running on another machine:

On remote machine:

# Install vsdbg
curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg

launch.json:

{
    "name": "Remote Attach",
    "type": "coreclr",
    "request": "attach",
    "processId": "${command:pickRemoteProcess}",
    "pipeTransport": {
        "pipeCwd": "${workspaceFolder}",
        "pipeProgram": "ssh",
        "pipeArgs": [
            "user@remote-host"
        ],
        "debuggerPath": "~/vsdbg/vsdbg"
    }
}

7. Hot Reload

See code changes without restarting:

Start with:

dotnet watch

Or in tasks.json:

{
    "label": "watch",
    "command": "dotnet",
    "args": ["watch", "--project", "${workspaceFolder}/YourProject.csproj"],
    "isBackground": true
}

8. Debugging Tests

Create test-specific configuration:

{
    "name": "Debug Tests",
    "type": "coreclr",
    "request": "launch",
    "program": "dotnet",
    "args": [
        "test",
        "${workspaceFolder}/tests/YourProject.Tests/YourProject.Tests.csproj",
        "--filter", "FullyQualifiedName~YourNamespace.YourTestClass.YourTestMethod"
    ],
    "cwd": "${workspaceFolder}",
    "console": "integratedTerminal"
}

Or use Test Explorer:

  1. Open Testing view (beaker icon in sidebar)
  2. Right-click test
  3. Select “Debug Test”

9. Logpoints (Non-Breaking Breakpoints)

Log without stopping execution:

  1. Right-click line number
  2. Select “Add Logpoint”
  3. Enter message: Message count: {messages.Count}

10. Function Breakpoints

Break when specific function is called:

  1. Debug view > Breakpoints section
  2. Click + dropdown
  3. Select “Function Breakpoint”
  4. Enter function name: MyNamespace.MyClass.MyMethod

Workspace Settings for .NET

Configure .vscode/settings.json for better .NET experience:

{
    // C# specific
    "dotnet.defaultSolution": "YourSolution.sln",
    "csharp.semanticHighlighting.enabled": true,
    "csharp.suppressDotnetInstallWarning": false,

    // IntelliCode
    "csharp.inlayHints.enableInlayHintsForParameters": true,
    "csharp.inlayHints.enableInlayHintsForLiteralParameters": true,
    "csharp.inlayHints.enableInlayHintsForIndexerParameters": true,
    "csharp.inlayHints.enableInlayHintsForObjectCreationParameters": true,
    "csharp.inlayHints.enableInlayHintsForOtherParameters": true,
    "csharp.inlayHints.suppressInlayHintsForParametersThatDifferOnlyBySuffix": false,

    // Code formatting
    "omnisharp.enableEditorConfigSupport": true,
    "omnisharp.enableRoslynAnalyzers": true,
    "omnisharp.organizeImportsOnFormat": true,

    // Terminal
    "terminal.integrated.env.windows": {
        "PATH": "${env:PATH};C:\\Program Files\\dotnet"
    },

    // Files
    "files.exclude": {
        "**/bin": true,
        "**/obj": true
    },

    // Search
    "search.exclude": {
        "**/node_modules": true,
        "**/bower_components": true,
        "**/bin": true,
        "**/obj": true
    }
}

Troubleshooting

Issue: “Cannot find .NET SDK”

Solution:

  1. Verify SDK installation: dotnet --list-sdks
  2. Reload window: Ctrl+Shift+P > “Developer: Reload Window”
  3. Set SDK path in settings:
"dotnet.dotnetPath": "/usr/local/share/dotnet/dotnet"

Issue: “Breakpoint won’t hit”

Solutions:

  1. Verify build configuration is Debug not Release
  2. Check Debug/ folder has recent build
  3. Disable “Just My Code”:
"justMyCode": false
  1. Clean and rebuild:
dotnet clean
dotnet build

Issue: “The preLaunchTask ‘build’ terminated with exit code 1”

Solutions:

  1. Run build manually: dotnet build
  2. Check project path in tasks.json matches your .csproj
  3. Verify .NET SDK version matches project target framework

Issue: Variables show “Cannot evaluate expression”

Solutions:

  1. Ensure debug symbols are generated (check .csproj):
<PropertyGroup>
    <DebugType>portable</DebugType>
</PropertyGroup>
  1. Rebuild in Debug configuration
  2. Disable compiler optimizations

Issue: “Debugging with browser doesn’t work”

Solution for ASP.NET Core:

"serverReadyAction": {
    "action": "openExternally",
    "pattern": "\\bNow listening on:\\s+(https?://\\S+)",
    "uriFormat": "%s"
}

Issue: Can’t debug NuGet packages

Solution:

"justMyCode": false,
"symbolOptions": {
    "searchMicrosoftSymbolServer": true,
    "searchNuGetOrgSymbolServer": true
}

Issue: “Launch.json program path is incorrect”

Solution: Verify paths match your project structure:

"program": "${workspaceFolder}/bin/Debug/net10.0/ProjectName.dll"

Check:

  • Workspace folder is correct
  • Project path is correct
  • Target framework version matches (net10.0, net9.0, etc.)
  • DLL name matches project name

Best Practices

1. Organize Configurations by Environment

{
    "configurations": [
        {
            "name": "Development",
            "env": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            }
        },
        {
            "name": "Staging",
            "env": {
                "ASPNETCORE_ENVIRONMENT": "Staging"
            }
        }
    ]
}

2. Use Descriptive Configuration Names

Bad:

"name": "Launch 1"

Good:

"name": "API: Development (Port 5000)"

3. Leverage User Secrets

Don’t commit sensitive data in launch.json:

Setup:

dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Database" "Server=..."

Access in code:

var connectionString = configuration["ConnectionStrings:Database"];

4. Use Input Variables for Flexibility

{
    "version": "0.2.0",
    "inputs": [
        {
            "id": "messageCount",
            "type": "promptString",
            "description": "Number of messages to process",
            "default": "10"
        }
    ],
    "configurations": [
        {
            "name": "Process Messages",
            "args": ["--count", "${input:messageCount}"]
        }
    ]
}

5. Create Compound Configurations for Microservices

{
    "compounds": [
        {
            "name": "Full Stack",
            "configurations": [
                "Frontend",
                "API Gateway",
                "Auth Service",
                "Data Service"
            ],
            "stopAll": true,
            "preLaunchTask": "build-all"
        }
    ]
}

Useful Keyboard Shortcuts

ActionWindows/LinuxmacOS
Start DebuggingF5F5
Run Without DebuggingCtrl+F5Cmd+F5
Toggle BreakpointF9F9
Step OverF10F10
Step IntoF11F11
Step OutShift+F11Shift+F11
ContinueF5F5
StopShift+F5Shift+F5
RestartCtrl+Shift+F5Cmd+Shift+F5
Show Debug ConsoleCtrl+Shift+YCmd+Shift+Y
Open Run and DebugCtrl+Shift+DCmd+Shift+D

Additional Resources

Official Documentation

Extensions Worth Installing

  • C# Dev Kit - Essential for .NET development
  • NuGet Gallery - Manage NuGet packages
  • REST Client - Test APIs
  • GitLens - Enhanced Git integration
  • Error Lens - Inline error highlighting
  • Better Comments - Colorful comment annotations

Community Resources


Conclusion

Setting up VSCode for .NET debugging is straightforward once you understand the key configuration files:

  1. launch.json - How to run and debug
  2. tasks.json - Build automation
  3. settings.json - Workspace preferences

Start with auto-generated configurations and customize as your project grows. Use multiple configurations for different scenarios, leverage environment variables for secrets, and explore advanced features like conditional breakpoints and hot reload.

With this setup, you’ll have a professional .NET development environment that rivals Visual Studio in debugging capabilities while maintaining VSCode’s speed and flexibility.


Created: December 2025 Version: .NET 10.0 / VSCode Latest Updated: Based on C# Dev Kit latest features