ติดตั้ง .NET บน VSCode แบบไม่ง้อ C# DevKit (Mac/Ubuntu)

ติดตั้ง .NET บน VSCode แบบไม่ง้อ C# DevKit (Mac/Ubuntu)

คู่มือ .NET Backend บน VSCode ฉบับสมบูรณ์ — ไม่มี C# DevKit ก็ได้ประสบการณ์เทียบเท่า Visual Studio

คู่มือติดตั้งและตั้งค่าสำหรับทีมที่ใช้ GitHub Copilot โดยไม่ต้องการ C# DevKit


สารบัญ

ติดตั้งและตั้งค่า

  1. ทำความเข้าใจ Architecture ก่อนติดตั้ง
  2. ติดตั้ง .NET SDK
  3. จัดการ SDK Version
  4. Extensions ที่ต้องติดตั้ง
  5. VSCode Profiles
  6. ตั้งค่า VSCode สำหรับ .NET

เริ่มใช้งาน

  1. สร้าง Solution และ Project Structure
  2. จัดการ Project Reference และ NuGet
  3. Configuration Files
  4. VSCode Snippets สำหรับทีม
  5. GitHub Copilot Custom Instructions

ใช้งานประจำวัน

  1. รัน Test ใน VSCode
  2. Debug ใน VSCode
  3. สรุป Workflow

Appendix


บทความนี้เป็นส่วนหนึ่งของ .NET Ecosystem บน VSCode — ครอบคลุมตั้งแต่การติดตั้ง SDK, การตั้งค่า extensions, ไปจนถึง workflow การพัฒนา Backend ด้วย Clean Architecture และ GitHub Copilot โดยไม่ต้องพึ่ง Visual Studio หรือ C# DevKit ที่ต้องมี license

1. ทำความเข้าใจ Architecture ก่อนติดตั้ง

ก่อนติดตั้ง ควรทำความเข้าใจว่าแต่ละ component ทำอะไร เพื่อไม่ให้สับสนระหว่างการใช้งาน

Roslyn, OmniSharp, SonarQube คืออะไร

Roslyn, OmniSharp และ SonarQube Architecture

Componentทำงานตอนไหนoutput คืออะไร
OmniSharpระหว่างพิมIntelliSense, error highlight
Roslynembedded ใน OmniSharp/SonarLint/dotnet buildsyntax tree, semantic model, .dll
Roslynatorระหว่างพิมcode analysis 500+ rules (plugin ของ OmniSharp)
SonarLintระหว่างพิมsecurity, code smell
SonarQube Scannerตอน build ใน CI/CDQuality Gate report
Kestrelตอน app รันรับ HTTP request จาก client
Language Serverระหว่างพิม≠ interpreter, ไม่รันโค้ด อ่านอย่างเดียว

หมายเหตุ: Roslyn ไม่ใช่ขั้นตอนที่รันก่อน — เป็น library ที่ทั้ง OmniSharp, SonarLint และ dotnet build ต่างฝัง (embed) ไว้ในตัวเองและเรียกใช้แยกกัน


2. ติดตั้ง .NET SDK

สำหรับผู้ใช้ Windows: บทความนี้ครอบคลุมเฉพาะ macOS และ Ubuntu — แนะนำให้ติดตั้ง WSL2 แล้วเลือก Ubuntu เป็น distro แทนการรัน .NET บน Windows โดยตรง จากนั้นทำตามขั้นตอน Ubuntu ด้านล่างได้เลย

ทำไมต้อง Ubuntu+WSL แทน Windows ตรงๆ?

  • Environment ตรงกับ Production — server จริงเกือบทั้งหมดรัน Linux ใช้ WSL ทำให้ path, file permission, line ending (LF vs CRLF) และ shell script ทำงานตรงกับ server ทันที ไม่ต้องแก้ bug ที่เกิดเฉพาะบน Windows
  • Docker ทำงานได้ดีกว่า — Docker Desktop บน Windows ใช้ทรัพยากรสูงและมีข้อจำกัดด้าน volume performance ส่วน WSL2 รัน Docker engine ใน Linux kernel โดยตรง เร็วกว่าและเสถียรกว่า
  • Shell script ใช้ได้เลย — build script, CI/CD script และ tool ส่วนใหญ่ในทีมเขียนเป็น bash ใช้ได้ใน WSL โดยไม่ต้องดัดแปลง
  • OmniSharp เสถียรกว่า — มีรายงานปัญหา OmniSharp crash และ IntelliSense ช้าบน Windows โดยเฉพาะกับ solution ขนาดใหญ่ WSL ช่วยลดปัญหานี้ได้
  • ยังใช้ UI ของ Windows ได้ปกติ — VSCode บน Windows เชื่อมต่อเข้า WSL ผ่าน Remote - WSL extension ได้โดยตรง ไม่ต้องเปิด terminal แยก

macOS — วิธีที่ 1: GUI Installer (แนะนำสำหรับทีมที่ไม่ถนัด CLI)

  1. ไปที่ https://dotnet.microsoft.com/en-us/download
  2. เลือก SDK version ที่ต้องการ (แนะนำ .NET 8 LTS)
  3. ดาวน์โหลด .pkg แล้วคลิก Next → Next → Finish
  4. ติดได้หลาย version พร้อมกัน ไม่ต้องถอนของเก่า

macOS — วิธีที่ 2: Homebrew

brew install dotnet@8
brew install dotnet@9   # ติดหลาย version พร้อมกันได้

Ubuntu

# Ubuntu 22.04 / 24.04
sudo apt-get update
sudo apt-get install -y dotnet-sdk-8.0

# หรือผ่าน Microsoft feed
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.0

ตรวจสอบ

dotnet --version
dotnet sdk check    # ดู version ทั้งหมดที่ติดอยู่

3. จัดการ SDK Version

global.json — Pin Version ให้ทั้งทีมใช้ตรงกัน

global.json ควรวางที่ solution root (เดียวกับ .sln) จะครอบทุก .csproj ข้างในทั้งหมด ไม่ต้องวางซ้ำในแต่ละ project

MySystem/                        ← วาง global.json ตรงนี้
├── global.json
├── MySystem.sln
├── src/
│   ├── MySystem.API/
│   ├── MySystem.Application/
│   ├── MySystem.Domain/
│   └── MySystem.Infrastructure/
└── tests/
    └── MySystem.Tests/
{
  "sdk": {
    "version": "8.0.404",
    "rollForward": "disable"
  }
}

.NET จะหา global.json โดยเดิน up directory จาก working directory ขึ้นไปเรื่อยๆ จนเจออันแรก

ค่า rollForward ที่ควรรู้

ค่าความหมาย
disableใช้ version นี้เป๊ะ ถ้าไม่มีให้ error — แนะนำสำหรับทีม
patchยอมให้ใช้ patch ใหม่กว่าได้ แต่ minor/major ต้องตรง
latestMinorใช้ minor ใหม่สุดที่มี
latestMajorใช้ version ใหม่สุดที่มีในเครื่อง

กรณีหลาย Solution ใช้ .NET ต่าง Version

repos/
├── OrderService/
│   ├── global.json   ← { "sdk": { "version": "8.0.404" } }
│   └── OrderService.sln
└── LegacyService/
    ├── global.json   ← { "sdk": { "version": "6.0.0" } }
    └── LegacyService.sln

ตรวจสอบ Version ที่ติดอยู่

dotnet sdk check        # ดูสถานะทุก version + แจ้ง patch ใหม่
dotnet --list-sdks      # ดู SDK พร้อม path
dotnet --list-runtimes  # ดู Runtime ที่ติด
dotnet --version        # ดู version ที่ project ใช้อยู่จริง (รันใน folder ที่มี global.json)

output ของ dotnet sdk check:

.NET SDKs:
Version      Status
-------------------------------------------
6.0.420      Up to date.
8.0.100      Patch 8.0.404 is available.   ← มี patch ใหม่
8.0.404      Up to date.

Upgrade Patch Version

macOS — GUI

ดาวน์โหลด installer version ใหม่จาก dotnet.microsoft.com แล้วรันทับได้เลย ไม่ต้องถอนของเก่า

macOS — Homebrew

Homebrew ติด latest patch ของ major version ให้เสมอ ระบุ patch เองตรงๆ ไม่ได้ ต้องใช้ install script หากต้องการระบุ version

brew info dotnet@8          # ดู version ที่มีใน homebrew
brew upgrade dotnet@8       # upgrade เป็น latest patch

macOS / Ubuntu — ระบุ patch version ตรงๆ ด้วย install script

# ดู version ที่มีทั้งหมด
curl -sL https://raw.githubusercontent.com/dotnet/install-scripts/main/src/dotnet-install.sh | bash -s -- --list-versions --channel 8.0

# ติด patch version ที่ต้องการ
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin \
  --version 8.0.404 \
  --install-dir $HOME/.dotnet

Ubuntu — apt

sudo apt-get update
apt-cache madison dotnet-sdk-8.0          # ดู version ทั้งหมดที่มีใน repo
sudo apt-get install dotnet-sdk-8.0=8.0.404-0ubuntu1~22.04.1

Flow Upgrade

dotnet sdk check

เห็นว่ามี patch ใหม่

Mac: brew upgrade dotnet@8
Ubuntu: apt-get install dotnet-sdk-8.0=<version>

dotnet sdk check        ← verify อีกครั้ง

อัปเดต global.json ให้ตรง

dotnet build            ← ทดสอบว่า project ยัง build ได้

Downgrade Patch Version

.NET ติดได้หลาย version พร้อมกัน — downgrade จริงๆ คือ ติด version เก่าเพิ่ม แล้ว pin ด้วย global.json ไม่ต้องถอน version ใหม่ออก

macOS — install script

Homebrew เก็บแค่ latest ของแต่ละ major version ต้องใช้ install script แทน

curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin \
  --version 8.0.100 \
  --install-dir $HOME/.dotnet

dotnet sdk check        # ตรวจสอบว่ามีครบ

Ubuntu — apt

apt-cache madison dotnet-sdk-8.0          # ดู version ที่มีใน repo
sudo apt-get install dotnet-sdk-8.0=8.0.100-0ubuntu1~22.04.1
# apt จะถามว่า downgrade มั้ย กด Y

แก้ global.json หลัง Downgrade

{
  "sdk": {
    "version": "8.0.100",
    "rollForward": "disable"
  }
}

Flow Downgrade

dotnet sdk check

ติด version เก่าที่ต้องการ (install script / apt)

แก้ global.json → version เก่า + rollForward: disable

dotnet --version        ← verify ใน project folder

dotnet build            ← ทดสอบ

4. Extensions ที่ต้องติดตั้ง

ทั้งหมดนี้ ฟรี 100% (GitHub Copilot ต้องจ่ายแต่ทีมมีอยู่แล้ว)

Core — จำเป็นต้องมี

ExtensionIDทำอะไร
C# (OmniSharp)ms-dotnettools.csharpIntelliSense, debug, go-to-def — หัวใจหลัก
.NET Install Toolms-dotnettools.vscode-dotnet-runtimedependency ของ C# extension (ทำงานเบื้องหลังอัตโนมัติ)
Solution Explorerfernandoescolar.vscode-solution-explorerGUI จัดการ .sln, add reference, add project คลิกขวาได้

Productivity

ExtensionIDทำอะไร
NuGet Gallerypatcx.vscode-nuget-galleryGUI ค้นหาและติด NuGet package
Roslynatorjosefpihrt-vscode.roslynatorCode analysis 500+ rules + auto fix ด้วย Ctrl+.
REST Clienthumao.rest-clientทดสอบ API จากไฟล์ .http แทน Postman
.NET Core Test Explorerformulahendry.dotnet-test-explorerรัน/debug test แบบ click-to-run

Utility (Default ใช้ได้ทุก Profile)

Extensionหมายเหตุ
GitHub Copilotใช้ได้ทุกภาษา
GitHub Copilot Chatใช้ @terminal ให้ generate CLI command แทนได้

วิธีใช้ Roslynator

  • วางเคอร์เซอร์บนโค้ดที่ขีดเส้น → กด Ctrl+. → เลือก fix
  • คลิกขวาที่โค้ด → Refactor → เลือก action
  • เปิด Problems panel: Ctrl+Shift+M

Roslynator เปรียบได้กับ ESLint ของ JavaScript — ชี้ปัญหาแบบ real-time และ fix ให้อัตโนมัติ ส่วน SonarQube เปรียบได้กับศูนย์ตรวจรถที่ต้องนำรถไปตรวจเป็นรอบ


5. VSCode Profiles

ควรแยก profile สำหรับแต่ละภาษา เพื่อลด RAM และป้องกัน extension ชนกัน

วิธีสร้าง Profile

ซ้ายล่าง → คลิก ⚙️ → Profiles → Create Profile → ตั้งชื่อ "dotnet"

แนะนำ Profile Structure

ProfileExtensions
dotnetC#, .NET Install Tool, Solution Explorer, NuGet Gallery, Roslynator, REST Client, Test Explorer
frontendESLint, Prettier, Tailwind, TypeScript, Volar
pythonPython, Pylance, Jupyter, Black Formatter
default (ทุก profile)GitHub Copilot, GitHub Copilot Chat, GitLens

Switch Profile

Ctrl+Shift+P → "Profiles: Switch Profile" → เลือก profile

6. ตั้งค่า VSCode สำหรับ .NET

.vscode/settings.json

{
  "omnisharp.enableRoslynAnalyzers": true,
  "omnisharp.enableEditorConfigSupport": true,
  "omnisharp.organizeImportsOnFormat": true,
  "editor.formatOnSave": true,
  "editor.formatOnType": true,
  "dotnet-test-explorer.testProjectPath": "**/*.Tests.csproj",
  "dotnet-test-explorer.autoWatch": true,
  "dotnet-test-explorer.showCodeLens": true,
  "vscode-solution-explorer.trackActiveItem": true,
  "vscode-solution-explorer.showTerminalOnError": true
}

.vscode/tasks.json

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "build",
      "command": "dotnet",
      "type": "process",
      "args": ["build", "${workspaceFolder}"],
      "problemMatcher": "$msCompile"
    },
    {
      "label": "watch",
      "command": "dotnet",
      "type": "process",
      "args": ["watch", "run", "--project", "${workspaceFolder}/src/MySystem.API"],
      "problemMatcher": "$msCompile",
      "isBackground": true
    }
  ]
}

.vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": ".NET Core Launch (web)",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "build",
      "program": "${workspaceFolder}/src/MySystem.API/bin/Debug/net8.0/MySystem.API.dll",
      "args": [],
      "cwd": "${workspaceFolder}/src/MySystem.API",
      "stopAtEntry": false,
      "env": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000"
      }
    }
  ]
}

7. สร้าง Solution และ Project Structure

Shell Script — สร้างครั้งเดียวใช้ซ้ำได้ทั้งทีม

สร้างไฟล์ create-service.sh เก็บไว้ใน repo:

#!/bin/bash
# create-service.sh
# Usage: ./create-service.sh OrderService

NAME=$1

mkdir -p $NAME && cd $NAME

dotnet new sln -n $NAME
mkdir -p src tests

# สร้างทุก project
dotnet new webapi   -n $NAME.API            -o src/$NAME.API
dotnet new classlib -n $NAME.Application    -o src/$NAME.Application
dotnet new classlib -n $NAME.Domain         -o src/$NAME.Domain
dotnet new classlib -n $NAME.Infrastructure -o src/$NAME.Infrastructure
dotnet new xunit    -n $NAME.Tests          -o tests/$NAME.Tests

# เพิ่มทุก project เข้า sln
dotnet sln add src/$NAME.API/$NAME.API.csproj
dotnet sln add src/$NAME.Application/$NAME.Application.csproj
dotnet sln add src/$NAME.Domain/$NAME.Domain.csproj
dotnet sln add src/$NAME.Infrastructure/$NAME.Infrastructure.csproj
dotnet sln add tests/$NAME.Tests/$NAME.Tests.csproj

# Reference chain
dotnet add src/$NAME.API reference src/$NAME.Application/$NAME.Application.csproj
dotnet add src/$NAME.Application reference src/$NAME.Domain/$NAME.Domain.csproj
dotnet add src/$NAME.Infrastructure reference src/$NAME.Application/$NAME.Application.csproj

echo "✅ $NAME solution ready!"
chmod +x create-service.sh
./create-service.sh OrderService

หรือใช้ Copilot Chat แทน

@terminal สร้าง solution ชื่อ OrderService
ใช้ Clean Architecture: API, Application, Domain, Infrastructure, Tests

8. จัดการ Project Reference และ NuGet

Add Project Reference

วิธีที่ 1 — Solution Explorer (GUI คลิกขวา)

Solution Explorer → คลิกขวาที่ project → Add → Project Reference

วิธีที่ 2 — Copilot Chat

@terminal เพิ่ม reference จาก MySystem.Application เข้าไปใน MySystem.API

วิธีที่ 3 — CLI

dotnet add src/MySystem.API reference src/MySystem.Application/MySystem.Application.csproj

วิธีที่ 4 — แก้ .csproj ตรงๆ (OmniSharp จะ reload ให้อัตโนมัติ)

<ItemGroup>
  <ProjectReference Include="..\MySystem.Application\MySystem.Application.csproj"/>
</ItemGroup>

Add NuGet Package

วิธีที่ 1 — NuGet Gallery (GUI)

Ctrl+Shift+P → "NuGet: Open NuGet Gallery" → ค้นหา → เลือก version → Install

วิธีที่ 2 — Copilot Chat

@terminal เพิ่ม MediatR และ FluentValidation ลงใน MySystem.Application

วิธีที่ 3 — CLI

dotnet add src/MySystem.Application package MediatR
dotnet add src/MySystem.Application package FluentValidation

9. Configuration Files

ตำแหน่งไฟล์ใน Solution

MySystem/
├── global.json                        ← SDK version (solution level)
├── MySystem.sln
└── src/
    ├── MySystem.API/
    │   ├── appsettings.json           ← base config
    │   ├── appsettings.Development.json
    │   ├── appsettings.Production.json  ← ใส่ใน .gitignore
    │   └── MySystem.API.csproj
    ├── MySystem.Application/          ← ไม่มี appsettings.json
    ├── MySystem.Domain/               ← ไม่มี appsettings.json
    └── MySystem.Infrastructure/       ← ไม่มี appsettings.json

appsettings.json วางได้เฉพาะ project ที่ run ได้เท่านั้น (เช่น .API) ส่วน global.json วาง 1 ไฟล์ที่ solution root ครอบทุก project

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": ""
  },
  "Jwt": {
    "Issuer": "myapp",
    "ExpiryMinutes": 60
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  }
}

appsettings.Development.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=mydb_dev;Username=postgres"
  }
}

.gitignore — สิ่งที่ต้องระวัง

appsettings.Production.json
appsettings.*.local.json
*.user
.env

10. VSCode Snippets สำหรับทีม

สร้างไฟล์ .vscode/dotnet.code-snippets ใน repo เพื่อให้ทุกคนในทีมใช้ snippets ร่วมกัน

{
  "CQRS Command": {
    "prefix": "cmd",
    "description": "สร้าง MediatR Command + Handler",
    "body": [
      "namespace ${1:MyApp}.Application.${2:Feature}.Commands;",
      "",
      "public record ${3:Create}${2:Feature}Command(",
      "    $4",
      ") : IRequest<${5:Result}>;",
      "",
      "public class ${3:Create}${2:Feature}CommandHandler(",
      "    $6",
      ") : IRequestHandler<${3:Create}${2:Feature}Command, ${5:Result}>",
      "{",
      "    public async Task<${5:Result}> Handle(",
      "        ${3:Create}${2:Feature}Command request,",
      "        CancellationToken cancellationToken)",
      "    {",
      "        $0",
      "    }",
      "}"
    ]
  },
  "CQRS Query": {
    "prefix": "qry",
    "description": "สร้าง MediatR Query + Handler",
    "body": [
      "namespace ${1:MyApp}.Application.${2:Feature}.Queries;",
      "",
      "public record Get${2:Feature}Query($3) : IRequest<${4:Result}>;",
      "",
      "public class Get${2:Feature}QueryHandler(",
      "    $5",
      ") : IRequestHandler<Get${2:Feature}Query, ${4:Result}>",
      "{",
      "    public async Task<${4:Result}> Handle(",
      "        Get${2:Feature}Query request,",
      "        CancellationToken cancellationToken)",
      "    {",
      "        $0",
      "    }",
      "}"
    ]
  },
  "API Controller": {
    "prefix": "ctrl",
    "description": "สร้าง API Controller พร้อม MediatR",
    "body": [
      "namespace ${1:MyApp}.API.Controllers;",
      "",
      "[ApiController]",
      "[Route(\"api/[controller]\")]",
      "public class ${2:Feature}Controller(ISender sender) : ControllerBase",
      "{",
      "    [HttpGet]",
      "    public async Task<IActionResult> GetAll(CancellationToken ct)",
      "    {",
      "        var result = await sender.Send(new Get${2:Feature}Query(), ct);",
      "        return Ok(result);",
      "    }",
      "",
      "    [HttpPost]",
      "    public async Task<IActionResult> Create(",
      "        [FromBody] Create${2:Feature}Command command,",
      "        CancellationToken ct)",
      "    {",
      "        var result = await sender.Send(command, ct);",
      "        return Ok(result);",
      "    }",
      "}"
    ]
  },
  "Domain Entity": {
    "prefix": "ent",
    "description": "สร้าง Domain Entity",
    "body": [
      "namespace ${1:MyApp}.Domain.Entities;",
      "",
      "public class ${2:Entity}",
      "{",
      "    public Guid Id { get; private set; } = Guid.NewGuid();",
      "    $0",
      "",
      "    private ${2:Entity}() { }",
      "",
      "    public static ${2:Entity} Create($3)",
      "    {",
      "        return new ${2:Entity} { $4 };",
      "    }",
      "}"
    ]
  }
}

วิธีใช้ Snippets

พิมกดได้อะไร
cmdTabMediatR Command + Handler
qryTabMediatR Query + Handler
ctrlTabAPI Controller พร้อม MediatR
entTabDomain Entity

11. GitHub Copilot Custom Instructions

เนื่องจากทีมมี subscription อยู่แล้ว สามารถใช้ Custom Instructions เพื่อให้ Copilot รู้บริบทของทีมได้ทันที ไม่ต้องอธิบายซ้ำทุกครั้ง

วิธีที่ 1 — Repository Level (แนะนำ — ใช้ร่วมกันทั้งทีม)

สร้างไฟล์ .github/copilot-instructions.md:

# Team .NET Backend Guidelines

## Stack
- .NET 8, ASP.NET Core Web API
- Entity Framework Core, PostgreSQL
- MediatR (CQRS pattern)
- FluentValidation

## Code style
- ใช้ primary constructor (C# 12)
- ใช้ async/await ทุก I/O operation
- ใช้ Result pattern แทน throw exception
- XML doc comment บน public method ทุกตัว

## Architecture
- Clean Architecture: Domain / Application / Infrastructure / API
- Repository pattern สำหรับ data access
- Validation ด้วย FluentValidation

## Naming
- Command: `CreateOrderCommand`, `UpdateProductCommand`
- Query: `GetOrderQuery`, `ListProductsQuery`
- Controller suffix: `OrdersController`

## Testing
- xUnit สำหรับ unit test
- Testcontainers สำหรับ integration test

วิธีที่ 2 — VSCode User Settings (per-person)

{
  "github.copilot.chat.codeGeneration.instructions": [
    {
      "text": "ฉันพัฒนา .NET 8 Web API ใช้ Clean Architecture, MediatR, EF Core. ใช้ async/await, Result pattern, FluentValidation เสมอ"
    }
  ]
}

วิธีที่ 3 — ใช้ Copilot Chat แทน CLI (สำหรับทีมที่ไม่ถนัด CLI)

@terminal สร้าง Web API project ชื่อ OrderService ใช้ .NET 8

@terminal เพิ่ม NuGet package MediatR ลงใน project Application

@terminal สร้าง feature Order แบบ vertical slice
มี Command, Query, Validator, DTO

12. รัน Test ใน VSCode

ใช้ .NET Core Test Explorer extension

Test Explorer Panel

TEST EXPLORER
└── 📁 MySystem.sln
    └── 📁 MySystem.Tests
        ├── 📁 OrderTests
        │   ├── ✅ CreateOrder_ValidInput_ReturnsSuccess   ▶
        │   ├── ✅ CreateOrder_EmptyItems_ReturnsFail      ▶
        │   └── ❌ CreateOrder_NullCustomer_Throws         ▶
        └── 📁 PaymentTests
            └── ✅ ProcessPayment_Success                  ▶

CodeLens ใน Editor

// ▶ Run Test | 🐛 Debug Test  ← ปุ่มลอยอยู่เหนือ method
[Fact]
public async Task CreateOrder_ValidInput_ReturnsSuccess()
{
    ...
}

ระดับที่รัน test ได้

ระดับวิธี
ทั้ง solutionคลิก ▶ ที่ node .sln
ทั้ง projectคลิก ▶ ที่ node .csproj
ทั้ง classคลิก ▶ ที่ชื่อ class
test case เดียวคลิก ▶ หน้าชื่อ method
debug testคลิกขวา → Debug Test

ร่วมกับ Copilot

/tests สร้าง unit test สำหรับ CreateOrderCommandHandler
ครอบคลุม happy path และ edge cases

13. Debug ใน VSCode

ActionShortcut
Start debuggingF5
Stop debuggingShift+F5
Step overF10
Step intoF11
Step outShift+F11
Toggle breakpointF9

14. สรุป Workflow

Keyboard Shortcuts สำคัญ

ShortcutAction
F5Run & Debug
Ctrl+Shift+BBuild
F12Go to Definition
Alt+F12Peek Definition
Shift+F12Find all References
F2Rename Symbol
Ctrl+.Quick Fix (Roslynator)
Ctrl+Shift+PCommand Palette

Daily Workflow

1. เปิด VSCode → Switch ไป dotnet profile

2. สร้าง feature ใหม่ → Copilot Chat บอกชื่อ feature

3. สร้าง file เพิ่ม → Snippets (cmd / qry / ctrl / ent)

4. เขียน business logic → Copilot Autocomplete ช่วย

5. รัน test → คลิก ▶ จาก Test Explorer หรือ CodeLens

6. Debug → F5 วาง breakpoint กด F9

7. Test API → REST Client ไฟล์ .http

เปรียบเทียบ VSCode vs Visual Studio

FeatureVisual StudioVSCode + Extensions
IntelliSense✅ OmniSharp
Breakpoint & Debug✅ F5
Hot Reloaddotnet watch run
Go to Definition✅ F12
Refactor / Rename✅ F2 + Roslynator
NuGet GUI✅ NuGet Gallery
Solution Explorer✅ Solution Explorer ext
Add Project Reference✅ GUI✅ Solution Explorer ext
Test Runner GUI✅ .NET Core Test Explorer
Code Analysis✅ Roslynator
ราคา💰✅ ฟรี (ยกเว้น Copilot)

สรุป Extensions ทั้งหมด

ExtensionIDฟรี
C# (OmniSharp)ms-dotnettools.csharp
.NET Install Toolms-dotnettools.vscode-dotnet-runtime
Solution Explorerfernandoescolar.vscode-solution-explorer
NuGet Gallerypatcx.vscode-nuget-gallery
Roslynatorjosefpihrt-vscode.roslynator
REST Clienthumao.rest-client
.NET Core Test Explorerformulahendry.dotnet-test-explorer
GitHub Copilotgithub.copilot❌ (ทีมมีอยู่แล้ว)
GitHub Copilot Chatgithub.copilot-chat❌ (ทีมมีอยู่แล้ว)


FAQ

Q: VSCode กับ Visual Studio ต่างกันอย่างไรสำหรับการพัฒนา .NET?

Visual Studio เป็น full IDE ที่มี GUI ครบทุกอย่างแต่ต้องใช้ license (Windows เป็นหลัก) ส่วน VSCode เป็น lightweight editor ที่ติด extensions เพิ่มได้ฟรี — เมื่อใช้ร่วมกับ OmniSharp, Solution Explorer, NuGet Gallery และ GitHub Copilot จะได้ประสบการณ์ใกล้เคียงกันมาก และรองรับทั้ง Mac และ Ubuntu


Q: จำเป็นต้องใช้ C# DevKit มั้ย ถ้าพัฒนา .NET บน VSCode?

ไม่จำเป็นครับ C# DevKit ต้องการ license แต่ C# extension (OmniSharp) ซึ่งเป็น open source ให้ IntelliSense, debug, go-to-definition และ refactoring ครบถ้วนโดยไม่มีค่าใช้จ่าย บทความนี้ใช้ OmniSharp เป็นหลักตลอด


Q: OmniSharp กับ Roslyn ต่างกันอย่างไร?

Roslyn คือ compiler platform ของ Microsoft ที่ทำหน้าที่ parse และวิเคราะห์โค้ด C# — OmniSharp คือ Language Server ที่ embed Roslyn ไว้ข้างใน แล้วส่งผลลัพธ์กลับมาแสดงใน VSCode แบบ real-time ทั้งสองทำงานพร้อมกันขณะพิม ไม่ใช่ sequential


Q: GitHub Copilot ช่วยการพัฒนา .NET บน VSCode ได้อย่างไร?

Copilot ช่วยได้ 3 ทาง:

  • @terminal — generate dotnet CLI command แทนการพิมเอง เหมาะกับทีมที่ไม่ถนัด CLI
  • Custom Instructions ใน .github/copilot-instructions.md — ทำให้ Copilot รู้ว่าทีมใช้ Clean Architecture, MediatR, EF Core โดยไม่ต้องอธิบายซ้ำทุกครั้ง
  • /tests ใน Copilot Chat — สร้าง unit test ครอบคลุม happy path และ edge cases ได้ทันที

Q: ข้อผิดพลาดที่พบบ่อยเมื่อติดตั้ง .NET บน Mac/Ubuntu คืออะไร?

มี 3 เรื่องที่พบบ่อย:

  • ไม่มี global.json — ทำให้ทีมแต่ละคนใช้ SDK version ต่างกันโดยไม่รู้ตัว แก้ด้วยการวาง global.json ที่ solution root พร้อม rollForward: disable
  • OmniSharp ไม่ทำงาน — เพราะ .NET Install Tool extension ขาดหาย ต้องติด extension นี้ก่อนเสมอ
  • บน Ubuntu ใช้ apt-get upgrade แทน apt-get install — ทำให้ไม่สามารถระบุ patch version ได้ ควรใช้ apt-get install dotnet-sdk-8.0=<version> ตรงๆ

Q: ควรใช้ VSCode Profile แยกสำหรับ .NET มั้ย?

ควรครับ โดยเฉพาะถ้าทีมเขียนหลายภาษา การแยก profile ทำให้ OmniSharp และ Language Server อื่นๆ ไม่รันพร้อมกัน ลด RAM ได้ชัดเจน switch profile ด้วย Ctrl+Shift+P → Profiles: Switch Profile ได้ทันที


Appendix — ถอนการติดตั้ง .NET ออกจากเครื่องทั้งหมด

ตรวจสอบก่อนถอน

dotnet sdk check        # ดู version ทั้งหมดที่ติดอยู่
dotnet --list-sdks      # ดู SDK พร้อม path ที่ติดตั้ง
dotnet --list-runtimes  # ดู Runtime ที่ติดตั้ง

สิ่งที่ต้องถอนทั้งหมด

ส่วนขนาดโดยประมาณ
.NET SDK (1 version)~300–500MB
.NET Runtime~100–200MB
NuGet cache~500MB–2GB (แล้วแต่ใช้มาเท่าไหร่)
OmniSharp cache~100–300MB
Global toolsแล้วแต่ติดไว้

macOS — GUI (แนะนำ)

Microsoft มี uninstall tool โดยเฉพาะ ดาวน์โหลดฟรี:

https://learn.microsoft.com/en-us/dotnet/core/additional-tools/uninstall-tool

เลือก version ที่ต้องการถอน → คลิก Uninstall — ปลอดภัยกว่าลบ folder เองครับ

macOS — Homebrew

brew uninstall dotnet@8

# ถ้าติดแบบ cask
brew uninstall --cask dotnet-sdk

macOS — ลบ folder ตรงๆ (กรณีติดผ่าน install script)

# ดู path ก่อน
dotnet --list-sdks
# output: 8.0.100 [/usr/local/share/dotnet/sdk]

# ลบ SDK version ที่ต้องการ
sudo rm -rf /usr/local/share/dotnet/sdk/8.0.100

# ลบ Runtime ที่เกี่ยวข้องด้วย
sudo rm -rf /usr/local/share/dotnet/shared/Microsoft.NETCore.App/8.0.0
sudo rm -rf /usr/local/share/dotnet/shared/Microsoft.AspNetCore.App/8.0.0

Ubuntu — apt

# ถอน SDK version เดียว
sudo apt-get remove dotnet-sdk-8.0

# ถอนทั้งหมดที่เกี่ยวกับ dotnet
sudo apt-get remove --purge dotnet*
sudo apt-get autoremove

ถอน VSCode Extensions

Extensionถอนด้วย
C# (OmniSharp)VSCode Extensions panel → Uninstall
.NET Install ToolVSCode Extensions panel → Uninstall
Solution ExplorerVSCode Extensions panel → Uninstall
NuGet GalleryVSCode Extensions panel → Uninstall
RoslynatorVSCode Extensions panel → Uninstall
.NET Core Test ExplorerVSCode Extensions panel → Uninstall
REST Client⚠️ ใช้กับภาษาอื่นได้ด้วย แล้วแต่จะเก็บ

ลบ Cache และ Folder ที่เหลือ

rm -rf ~/.omnisharp/        # OmniSharp cache
rm -rf ~/.nuget/packages/   # NuGet cache
rm -rf ~/.dotnet/            # dotnet folder (กรณีติดผ่าน install script)

ตรวจสอบหลังถอน

dotnet --list-sdks      # ควรไม่มี version ที่ถอนแล้ว
dotnet --version        # ถ้าถอนหมด command นี้จะ error หรือ command not found

ข้อควรระวัง

กรณีต้องทำอะไร
ถอน version ที่ global.json ชี้อยู่แก้ global.json ให้ชี้ version อื่นก่อน
ถอนแล้ว project build ไม่ผ่านรัน dotnet sdk check ดูว่ามี version ที่ใช้ได้อยู่มั้ย
ถอน runtime แต่ยังมี SDK อยู่SDK จะติดตั้ง runtime ให้อัตโนมัติตอน build

ลำดับที่ควรถอน

1. ถอน VSCode Extensions ทั้งหมดที่เกี่ยวกับ .NET

2. ถอน .NET SDK + Runtime (GUI tool / apt / brew)

3. ลบ NuGet cache     → ~/.nuget/packages/

4. ลบ OmniSharp cache → ~/.omnisharp/

5. ลบ ~/.dotnet/       (ถ้าติดผ่าน install script)

6. ตรวจสอบ
   dotnet --version    ← ควร error หรือ command not found
Supawut Thomas

Supawut Thomas

Software Developer

มีประสบการณ์พัฒนา Software ระดับ Enterprise มากกว่า 10 ปี ผ่านงานจริงหลากหลายโปรเจกต์องค์กร — เชื่อว่าความรู้ที่ดีที่สุดคือความรู้ที่มาจากประสบการณ์จริง และอยากแบ่งปันสิ่งเหล่านั้นให้เพื่อน Developer ทุกคนได้นำไปพัฒนาตัวเองได้ดีขึ้นในทุกๆ วัน