微軟開發



Visual Studio

開發環境簡介

Visual Studio 是由微軟開發的一個整合式開發環境(IDE),支援多種程式語言如 C++、C#、VB.NET、Python、JavaScript 等。適用於開發桌面應用程式、網站、雲端服務及行動應用程式。

主要功能

版本區別

平台支援

Visual Studio 支援 Windows 作業系統,另有 Visual Studio for Mac,專為 macOS 設計。

常見用途



Visual Studio 的語法格式化

1. 自動格式化代碼

Visual Studio 提供快捷鍵來快速格式化代碼,適用於各種編程語言: - 使用快捷鍵 `Ctrl + K, Ctrl + D` 格式化整個檔案。 - 使用快捷鍵 `Ctrl + K, Ctrl + F` 格式化選中的代碼區塊。

2. 啟用自動格式化

Visual Studio 支援在檔案儲存時自動格式化代碼: 1. 點擊 **工具 > 選項**。 2. 前往 **文字編輯器 > [語言] > 編碼樣式 > 常規**。 3. 勾選 **在檔案儲存時重新格式化代碼**。

3. 修改格式化設定

1. 打開 **工具 > 選項**。 2. 前往 **文字編輯器 > [語言] > 編碼樣式 > 格式化**。 3. 在此可以調整縮排、括號樣式、空白行等詳細格式化設定。

4. 使用編碼樣式設定檔

Visual Studio 支援編碼樣式設定檔(.editorconfig)來統一團隊的代碼風格: 1. 在專案的根目錄新增一個 `.editorconfig` 檔案。 2. 定義格式規則,例如:
   [*.cs]
   indent_style = space
   indent_size = 4
   
3. 檔案儲存後,格式化會自動遵循這些規則。

5. 安裝擴展工具

如果內建格式化功能無法滿足需求,可以安裝擴展工具: - 在 **擴展管理員** 中搜尋並安裝工具,例如 **CodeMaid** 或 **ReSharper**。 - 這些工具提供更多代碼格式化與重構選項。

6. 快速修正格式問題

當格式問題出現時,可以使用快速修正功能: - 在問題代碼上點擊右鍵,選擇 **快速動作與重構**。 - 選擇 **格式化文件** 或 **格式化選區**。

7. 總結

- 使用快捷鍵快速格式化代碼。 - 啟用檔案儲存時自動格式化功能。 - 使用 `.editorconfig` 統一代碼風格。 - 安裝擴展工具提升格式化能力。 - 善用快速修正功能改善代碼可讀性。

.NET SDK 版本

檢查已安裝的 .NET SDK

您可以透過命令列工具檢查電腦上安裝的 .NET SDK 版本:

dotnet --list-sdks

若出現類似下列結果,表示已安裝的 SDK 版本:

8.0.100 [C:\Program Files\dotnet\sdk]
9.0.100-preview.3.24172.9 [C:\Program Files\dotnet\sdk]

如果未出現您想使用的版本,表示該版本尚未安裝。

下載與安裝 .NET SDK

  1. 前往 .NET 官方下載頁面
  2. 選擇對應版本(例如 .NET 9)與作業系統平台(Windows、macOS、Linux)
  3. 下載並執行安裝程式

在 Visual Studio 中開啟命令提示字元

  1. 開啟 Visual Studio
  2. 選單列點選:工具 > 命令列 > 開發人員命令提示字元
  3. 將會開啟一個已設定好開發環境路徑的命令提示字元視窗,可直接使用 dotnet 命令

若未看到此選項,您也可以使用 Windows 的開始選單搜尋「Developer Command Prompt for VS」開啟。

使用 Visual Studio 的內建終端機(2022 起支援)

  1. 在 Visual Studio 中點選:檢視 > 終端機 或使用快速鍵 Ctrl + `
  2. 選擇終端機類型(例如 PowerShell 或 CMD)
  3. 可直接輸入 dotnet 命令檢查或操作 SDK


C# 語言

語言特色

基本範例

using System;

class Program {
    static void Main() {
        string name = "世界";
        Console.WriteLine($"哈囉, {name}!");
    }
}

常見應用領域

進階功能



CS8618 非Nullable未初始化

警告內容

CS8618 意思是:在建構函式結束時,非 Nullable 的屬性沒有被初始化,因此 C# 編譯器警告它可能會是 null

範例問題

public class Product {
    public string Name { get; set; }  // 警告 CS8618
}

解決方式

方法一:在建構函式中賦值(推薦)

public class Product {
    public string Name { get; set; }

    public Product(string name) {
        Name = name;
    }
}

方法二:給屬性預設值

public class Product {
    public string Name { get; set; } = string.Empty;
}

方法三:允許 Null

public class Product {
    public string? Name { get; set; }
}

→ 適用於 Name 合理允許為 null 的情況。

方法四:使用 required 修飾元(C# 11+ 支援)

public class Product {
    public required string Name { get; set; }
}

// 呼叫端必須初始化
var p = new Product { Name = "手機" }; // OK

建議



.NET 程式

將 float 轉成字串避免科學記號

問題說明

在 C++/CLI 中,如果直接用 floatSystem::String 相加,例如:

System::String^ tmpStr = "Value: " + someFloat;
someFloat 很大或很小時,可能會自動以科學記號(e.g. 1.23E+05)顯示。

解決方式

可用 System::String::FormatToString 搭配格式字串,指定小數位數並避免科學記號。

範例程式


using namespace System;

float value = 123456.789f;

// 方法1:String::Format
String^ tmpStr1 = String::Format("Value: {0:F2}", value); // F2 表示小數點後 2 位
Console::WriteLine(tmpStr1);

// 方法2:ToString 搭配格式
String^ tmpStr2 = "Value: " + value.ToString("F2");
Console::WriteLine(tmpStr2);

// 方法3:更多位數
String^ tmpStr3 = "Value: " + value.ToString("F6");
Console::WriteLine(tmpStr3);

格式化代碼說明



.NET 與 std str 轉碼亂碼

原因

在 C++/CLI 中,gcnew System::String(stdStr.c_str()) 會將 std::string(通常是 ANSI / UTF-8 編碼)直接轉成 .NET System::String,而 System::String 預期是 UTF-16
如果 stdStr 內含非 ASCII 字元(例如中文),就會出現亂碼。

解法一:使用 marshal_as(建議)

需引用命名空間:

#include <msclr/marshal_cppstd.h>
using namespace msclr::interop;

std::string stdStr = "中文測試";
System::String^ netStr = marshal_as<System::String^>(stdStr);

✅ 此方法能自動依 UTF-8 / ANSI 正確轉成 .NET Unicode。

---

解法二:如果確定 std::string 是 UTF-8,可手動轉換

#include <codecvt>
#include <locale>

std::string utf8Str = u8"中文測試";
std::wstring wideStr = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>{}.from_bytes(utf8Str);
System::String^ netStr = gcnew System::String(wideStr.c_str());
---

解法三:如果來源是 std::wstring

若 C++ 端原本使用寬字元字串,直接使用即可:

std::wstring wstr = L"中文測試";
System::String^ netStr = gcnew System::String(wstr.c_str());
---

建議



.NET List

基本介紹

在 .NET(C++/CLI、C#、VB.NET 等語言)中,List<T> 是一個泛型集合類別, 可儲存任意型別的資料(T),並提供動態調整大小的功能。 它屬於 System::Collections::Generic 命名空間。

宣告與初始化


// 匯入命名空間
using namespace System;
using namespace System::Collections::Generic;

int main()
{
    // 宣告一個 List 儲存 int
    List<int>^ numbers = gcnew List<int>();

    // 初始化時直接加入元素
    List<String^>^ names = gcnew List<String^>({ "Alice", "Bob", "Charlie" });
}

新增、移除與存取元素


List<int>^ nums = gcnew List<int>();

// 新增元素
nums->Add(10);
nums->Add(20);
nums->Add(30);

// 插入指定位置
nums->Insert(1, 15); // 在索引 1 插入 15

// 移除指定值
nums->Remove(20);

// 移除指定索引
nums->RemoveAt(0);

// 取得元素
int value = nums[1]; // 取得索引 1 的元素

// 檢查是否包含
if (nums->Contains(30))
    Console::WriteLine("找到 30");

// 清空所有元素
nums->Clear();

遍歷 List


// for 迴圈
for (int i = 0; i < nums->Count; i++)
{
    Console::WriteLine("第 {0} 個元素: {1}", i, nums[i]);
}

// for each 迴圈
for each (int n in nums)
{
    Console::WriteLine(n);
}

常用屬性與方法

屬性 / 方法說明
Count目前元素數量
Capacity內部容量(可自動成長)
Add(item)加入一個元素
AddRange(collection)加入另一個集合的所有元素
Insert(index, item)在指定位置插入元素
Remove(item)移除指定值(找到第一個匹配項)
RemoveAt(index)移除指定索引的元素
Clear()移除所有元素
Contains(item)檢查是否包含指定值
IndexOf(item)回傳元素索引(若不存在則為 -1)
Sort()排序元素(適用於可比較型別)
Reverse()反轉元素順序

範例輸出

10
15
30


.NET Dictionary

基本介紹

在 .NET(C++/CLI、C#、VB.NET 等語言)中,Dictionary<TKey, TValue> 是一種泛型集合, 用於儲存「鍵值對 (key-value pair)」。每個 key 必須唯一,而 value 則可重複。 它屬於 System::Collections::Generic 命名空間。

宣告與初始化


// 匯入命名空間
using namespace System;
using namespace System::Collections::Generic;

int main()
{
    // 建立一個 Dictionary,Key 為 int,Value 為 String
    Dictionary<int, String^>^ users = gcnew Dictionary<int, String^>();

    // 直接初始化
    users->Add(1, "Alice");
    users->Add(2, "Bob");
    users->Add(3, "Charlie");

    return 0;
}

新增、修改、刪除與存取元素


Dictionary<String^, int>^ ages = gcnew Dictionary<String^, int>();

// 新增元素
ages->Add("Tom", 25);
ages->Add("Jerry", 30);

// 修改值(透過索引器)
ages["Tom"] = 26;

// 新增或修改(若 key 不存在則自動新增)
ages["Spike"] = 40;

// 刪除元素
ages->Remove("Jerry");

// 存取元素
if (ages->ContainsKey("Tom"))
{
    Console::WriteLine("Tom 的年齡是 {0}", ages["Tom"]);
}

// 嘗試取得值(避免例外)
int age;
if (ages->TryGetValue("Spike", age))
{
    Console::WriteLine("Spike 的年齡是 {0}", age);
}

遍歷 Dictionary


Dictionary<int, String^>^ users = gcnew Dictionary<int, String^>();
users->Add(1, "Alice");
users->Add(2, "Bob");
users->Add(3, "Charlie");

// 使用 KeyValuePair 迴圈
for each (KeyValuePair<int, String^> entry in users)
{
    Console::WriteLine("Key = {0}, Value = {1}", entry.Key, entry.Value);
}

// 只取 Keys
for each (int key in users->Keys)
{
    Console::WriteLine("Key: {0}", key);
}

// 只取 Values
for each (String^ name in users->Values)
{
    Console::WriteLine("Name: {0}", name);
}

常用屬性與方法

屬性 / 方法說明
Add(key, value)新增一組鍵值對
Remove(key)移除指定的鍵及其對應的值
Clear()清空所有項目
ContainsKey(key)檢查是否存在指定的鍵
ContainsValue(value)檢查是否存在指定的值
TryGetValue(key, out value)安全地取得值,不會拋出例外
Count目前字典中的元素數量
Keys回傳所有鍵的集合
Values回傳所有值的集合

範例輸出

Key = 1, Value = Alice
Key = 2, Value = Bob
Key = 3, Value = Charlie
Tom 的年齡是 26
Spike 的年齡是 40


使用 .NET C++ 取得目錄中最新的檔案

簡介

在 .NET C++ 中,可以使用 System::IO 命名空間提供的功能來操作檔案與目錄。 取得目錄中最新的檔案,可以透過讀取所有檔案資訊並比較最後修改日期實現。

範例程式碼

以下是一個完整的範例,展示如何取得指定目錄中最新的檔案:

#include "stdafx.h"
#include <iostream>
#include <cliext/vector>
#include <System.IO>

using namespace System;
using namespace System::IO;
using namespace cliext;

int main()
{
    try
    {
        // 指定目錄路徑
        String^ directoryPath = "C:\\Your\\Directory\\Path";

        // 檢查目錄是否存在
        if (!Directory::Exists(directoryPath))
        {
            Console::WriteLine("目錄不存在: {0}", directoryPath);
            return -1;
        }

        // 取得目錄中的所有檔案
        array^ files = Directory::GetFiles(directoryPath);

        // 如果目錄中沒有檔案
        if (files->Length == 0)
        {
            Console::WriteLine("目錄中沒有檔案。");
            return 0;
        }

        // 找到最新的檔案
        String^ newestFile = nullptr;
        DateTime newestTime = DateTime::MinValue;

        for each (String^ file in files)
        {
            // 取得檔案的最後修改時間
            DateTime lastWriteTime = File::GetLastWriteTime(file);

            // 比較時間並更新最新檔案資訊
            if (lastWriteTime > newestTime)
            {
                newestTime = lastWriteTime;
                newestFile = file;
            }
        }

        // 輸出最新檔案資訊
        Console::WriteLine("最新檔案: {0}", newestFile);
        Console::WriteLine("最後修改時間: {0}", newestTime);
    }
    catch (Exception^ ex)
    {
        Console::WriteLine("發生錯誤: {0}", ex->Message);
    }

    return 0;
}

程式碼解說

應用場景

注意事項



.NET: System.Reflection

1. System.Reflection 是什麼?

System.Reflection 是 .NET 框架中的一個命名空間,提供了檢查和操作元數據的工具,使開發者可以在運行時動態檢查類型、方法、屬性等,並動態創建和操縱對象。

2. System.Reflection 的用途

3. 常見的 System.Reflection 類別

4. 使用範例

以下是一個使用 System.Reflection 的範例程式,展示如何動態檢查類型和方法,並調用方法。


// 定義一個簡單的範例類別
public class SampleClass {
    public string SayHello(string name) {
        return $"Hello, {name}!";
    }
}

// 使用 Reflection 來動態調用方法
using System;
using System.Reflection;

class Program {
    static void Main() {
        // 創建 SampleClass 類別的實例
        Type sampleType = typeof(SampleClass);
        object sampleInstance = Activator.CreateInstance(sampleType);

        // 獲取 SayHello 方法資訊
        MethodInfo methodInfo = sampleType.GetMethod("SayHello");

        // 動態調用 SayHello 方法
        object result = methodInfo.Invoke(sampleInstance, new object[] { "World" });
        Console.WriteLine(result); // 輸出: Hello, World!
    }
}
        

在上述範例中,我們使用 Activator.CreateInstance 來創建類別的實例,並使用 MethodInfo.Invoke 來調用方法 SayHello

5. 常見應用場景



System::Management::ManagementClass

用途

System::Management::ManagementClass 是 .NET Framework 中提供用於操作 Windows Management Instrumentation (WMI) 的類別之一。它允許開發者讀取、操作和管理系統資訊,例如硬體、作業系統、網路設定等。

命名空間

System::Management 是位於 System.Management.dll 中的命名空間,使用此功能需先加入參考。

常見用途

簡單範例

// C++/CLI 寫法
using namespace System;
using namespace System::Management;

int main() {
    try {
        ManagementClass^ mc = gcnew ManagementClass("Win32_OperatingSystem");
        for each (ManagementObject^ mo in mc->GetInstances()) {
            Console::WriteLine("OS 名稱: {0}", mo["Caption"]);
        }
    }
    catch (Exception^ ex) {
        Console::WriteLine("錯誤: {0}", ex->Message);
    }
    return 0;
}

常見錯誤與排除

相關類別



修復 Win32_NetworkAdapterConfiguration 發生 Invalid class 錯誤

錯誤代碼說明

當使用 `System::Management::ManagementClass("Win32_NetworkAdapterConfiguration")` 時出現錯誤代碼 `0x80041010`,表示 WMI 無法找到該類別,這通常代表:

解決步驟

以下為完整修復方式,可重新建立 WMI 儲存庫,適用於 Windows 10/11:
net stop winmgmt
winmgmt /resetrepository
net start winmgmt

說明

修復後驗證

可使用下列任一方式確認是否修復成功: 方式一:PowerShell 查詢
Get-WmiObject Win32_NetworkAdapterConfiguration
方式二:WMI 測試工具 (wbemtest)
  1. 按 Win + R,輸入 wbemtest
  2. 點選「Connect」,輸入 root\cimv2 並連線
  3. 點選「Query」,輸入 SELECT * FROM Win32_NetworkAdapterConfiguration

補充建議



.NET C++ 讀取 HTML 表格

概述

在 .NET C++ (或更常見的 C++/CLI) 專案中,讀取和解析檔案中的 HTML 表格,通常需要藉助一個外部的 HTML 解析函式庫,因為標準的 .NET 框架和 C++ 本身沒有內建強大的 HTML DOM 解析功能。一個常見且高效的選擇是使用 C# 或其他 .NET 語言的函式庫,然後在 C++/CLI 中引用。

對於 C++/CLI 專案,最實用且推薦的方法是使用 Html Agility Pack (HAP),這是一個非常流行的 .NET HTML 解析器。雖然 HAP 是用 C# 編寫的,但它可以無縫地在任何 .NET 語言(包括 C++/CLI)中作為參考使用。

使用 Html Agility Pack 的步驟

  1. 建立 C++/CLI 專案: 確保您的專案是 C++/CLI 專案(例如:CLR Console Application 或 Windows Forms App)。
  2. 獲取 Html Agility Pack:
  3. 程式碼實作(C++/CLI):

程式碼範例

以下是一個 C++/CLI 的程式碼片段,演示如何讀取一個本地 HTML 檔案並解析其中的第一個表格 (<table>) 的內容:


#using <System.dll>
#using <System.Xml.dll>
#using <HtmlAgilityPack.dll> // 確保已加入參考

using namespace System;
using namespace System::IO;
using namespace HtmlAgilityPack;

void ParseHtmlTable(String^ filePath)
{
    // 檢查檔案是否存在
    if (!File::Exists(filePath))
    {
        Console::WriteLine("錯誤:檔案不存在。");
        return;
    }

    // 1. 載入 HTML 文件
    HtmlDocument^ doc = gcnew HtmlDocument();
    try
    {
        // 從檔案載入 HTML
        doc->Load(filePath);
    }
    catch (Exception^ ex)
    {
        Console::WriteLine("載入檔案時發生錯誤:" + ex->Message);
        return;
    }

    // 2. 選擇第一個 <table> 節點
    HtmlNode^ table = doc->DocumentNode->SelectSingleNode("//table");

    if (table != nullptr)
    {
        Console::WriteLine("找到 HTML 表格,開始解析...");

        // 3. 選擇所有的 <tr> (表格列) 節點
        HtmlNodeCollection^ rows = table->SelectNodes(".//tr");

        if (rows != nullptr)
        {
            for each (HtmlNode^ row in rows)
            {
                // 4. 選擇 <td> 或 <th> (儲存格) 節點
                // 使用 | 運算子選擇 <td> 或 <th>
                HtmlNodeCollection^ cells = row->SelectNodes("td | th"); 

                if (cells != nullptr)
                {
                    String^ rowData = "";
                    for each (HtmlNode^ cell in cells)
                    {
                        // 獲取儲存格的內部文字並去除首尾空白
                        rowData += cell->InnerText->Trim() + "\t"; 
                    }
                    Console::WriteLine(rowData);
                }
            }
        }
        else
        {
            Console::WriteLine("表格中未找到 <tr> 標籤。");
        }
    }
    else
    {
        Console::WriteLine("檔案中未找到 <table> 標籤。");
    }
}

int main(array<String^>^ args)
{
    // 請將 "your_html_file.html" 替換為您的實際檔案路徑
    String^ htmlFilePath = "C:\\path\\to\\your_html_file.html"; 
    ParseHtmlTable(htmlFilePath);
    return 0;
}

關鍵技術點解釋



.NET C++ HTTP API 伺服器

使用 HttpListener 類別

HttpListener 類別允許您在 C++/CLI 應用程式中建立一個簡單的、自託管(Self-Hosted)的 HTTP 伺服器。

專案要求與設定

在 Visual Studio 中建立一個 C++/CLI 專案(例如 CLR Console Application),並確保已引用 System.dll

程式碼範例

以下是使用 HttpListener 建立一個簡單 API 伺服器並處理 GET 請求的 C++/CLI 程式碼。


#using <System.dll>

using namespace System;
using namespace System::Net;
using namespace System::Threading::Tasks;
using namespace System::Text;

// 處理傳入 HTTP 請求的函式
void HandleRequest(HttpListenerContext^ context)
{
    HttpListenerRequest^ request = context->Request;
    HttpListenerResponse^ response = context->Response;

    // 預設回應設定
    response->ContentType = "application/json; charset=utf-8";
    String^ responseString = "";
    response->StatusCode = 200; // 預設為 OK

    // 檢查請求的路徑和方法
    String^ url = request->RawUrl->ToLower();
    
    if (url == "/api/status" && request->HttpMethod == "GET")
    {
        // 處理 GET /api/status 請求
        responseString = "{\"status\": \"Server Running\", \"language\": \"C++/CLI\"}";
    }
    else
    {
        // 處理其他未定義的請求
        response->StatusCode = 404; // Not Found
        responseString = "{\"error\": \"404 Not Found\", \"path\": \"" + url + "\"}";
    }

    // 將字串回應轉換為位元組,並寫入輸出串流
    array<Byte>^ buffer = Encoding::UTF8->GetBytes(responseString);
    response->ContentLength64 = buffer->Length;
    
    try
    {
        response->OutputStream->Write(buffer, 0, buffer->Length);
        response->OutputStream->Close();
    }
    catch (Exception^)
    {
        // 忽略寫入錯誤,通常是客戶端斷開連線
    }
}

// 啟動 HttpListener 的函式
void StartListener(String^ prefix)
{
    HttpListener^ listener = gcnew HttpListener();
    
    try
    {
        listener->Prefixes->Add(prefix);
        listener->Start();
        Console::WriteLine("C++/CLI API Server 啟動,正在監聽: {0}", prefix);
    }
    catch (Exception^ ex)
    {
        Console::WriteLine("啟動 HttpListener 發生錯誤。請確認權限或位址是否已被佔用。");
        Console::WriteLine("錯誤訊息: {0}", ex->Message);
        return;
    }

    // 異步迴圈,持續接收請求
    while (listener->IsListening)
    {
        try
        {
            // 同步等待下一個請求
            HttpListenerContext^ context = listener->GetContext();
            
            // 使用 Task 異步處理請求,避免阻塞監聽迴圈
            Task::Factory->StartNew(gcnew Action<HttpListenerContext^>(&HandleRequest), context);
        }
        catch (Exception^)
        {
            // 監聽器停止時會拋出例外
            break;
        }
    }
    
    if (listener->IsListening)
    {
        listener->Close();
    }
}

int main(array<String^>^ args)
{
    // 設定監聽的 URL 前綴
    // 注意:在 Windows 中,可能需要管理員權限或使用 netsh 註冊 URL 命名空間。
    String^ listeningPrefix = "http://localhost:8080/"; 
    StartListener(listeningPrefix);
    
    Console::WriteLine("按下任意鍵結束伺服器...");
    Console::ReadKey(true);
    
    return 0;
}

執行權限注意

在 Windows 系統中,如果程式碼沒有以管理員權限運行,HttpListener 監聽特定埠時可能會拋出存取拒絕的例外。您可以透過以下命令註冊 URL 命名空間,以允許非管理員帳戶運行伺服器:


netsh http add urlacl url=http://+:8080/ user=Everyone

其中 http://+:8080/ 應替換為您程式碼中實際使用的前綴。



ASP.NET Core C# HTTP API 伺服器

概述

在 ASP.NET Core 中,建立 HTTP API 伺服器的標準專案類型是 Web API。最新的 .NET 版本(.NET 6 及更高版本)推薦使用 Minimal APIs 來快速且輕量地建立 API 端點。

Minimal APIs 使用最少的檔案和程式碼行,將啟動邏輯 (Startup) 和路由定義 (Routing) 合併到一個檔案 Program.cs 中。

專案設定 (使用 CLI)

要建立一個新的 Minimal API 專案,您可以使用 .NET CLI (命令列介面):


dotnet new web -n MinimalApiServer
cd MinimalApiServer

程式碼範例:Program.cs

以下是 Minimal API 伺服器核心檔案 Program.cs 的完整內容。它定義了伺服器的啟動、中介軟體和兩個 API 端點 (一個 GET,一個 POST)。


// 1. 建立 WebApplication 實例 (取代了傳統的 Startup 類別)
var builder = WebApplication.CreateBuilder(args);

// 2. 註冊服務 (Service Configuration)
// 這裡可以加入資料庫連線、驗證服務等
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); // 啟用 Swagger/OpenAPI 支援

var app = builder.Build();

// 3. 配置 HTTP 請求管線 (Middleware Configuration)

// 開發環境下,啟用 Swagger UI 以供測試
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

// 啟用 HTTPS 重新導向 (生產環境推薦)
// app.UseHttpsRedirection(); 

// 4. 定義 API 端點 (Endpoint Definition)
// 實體類別用於 POST 請求
public record Product(int Id, string Name, decimal Price);

// GET 請求端點:/api/hello
app.MapGet("/api/hello", () => 
{
    return Results.Ok(new { message = "Hello from ASP.NET Core Minimal API!", timestamp = DateTime.Now });
});

// GET 請求端點:/api/products/{id}
app.MapGet("/api/products/{id}", (int id) => 
{
    // 假設從資料庫查詢產品
    if (id == 1)
    {
        return Results.Ok(new Product(1, "筆記型電腦", 1200.00m));
    }
    return Results.NotFound(new { error = $"Product ID {id} not found" });
});

// POST 請求端點:/api/products
app.MapPost("/api/products", (Product product) => 
{
    // ASP.NET Core 會自動將 JSON 請求主體解序列化為 Product 實體
    Console.WriteLine($"收到新產品: {product.Name} (ID: {product.Id})");
    
    // 實際應用中,這裡會執行資料庫新增操作
    // 回傳 201 Created 狀態碼
    return Results.Created($"/api/products/{product.Id}", product);
});

// 5. 啟動應用程式,開始監聽 HTTP 請求
app.Run();

執行與測試

  1. 運行伺服器: 在專案目錄下執行 dotnet run
  2. 測試端點: 伺服器通常預設在 http://localhost:5000https://localhost:7000 運行。

關鍵技術點



.NET 程式在 Chromebook 上執行方法

1. 使用 Chromebook 的 Linux 支援執行 .NET

大多數現代 Chromebook 支援透過 Crostini 專案來運行 Linux 應用程式。

  1. 啟用 Linux 功能:
  2. 安裝 .NET SDK:
  3. 在終端機中編譯並執行 .NET 應用程式。

2. 使用雲端開發環境

無需在本地安裝任何軟體,即可在 Chromebook 上使用雲端平台運行 .NET 程式。

3. 使用 Docker 容器運行 .NET

Chromebook 支援使用容器運行應用程式,您可以透過 Docker 啟動 .NET 環境。

  1. 在 Linux 環境中安裝 Docker。
  2. 下載 .NET 容器映像檔:
    docker pull mcr.microsoft.com/dotnet/runtime:7.0
  3. 在容器內運行您的 .NET 程式。

4. 使用跨平台支援功能

從 .NET 6 開始,應用程式可以跨多平台運行,包括 Linux。將您的應用程式編譯為跨平台格式並部署至 Chromebook。

  1. 在任何機器上編譯您的應用程式:
    dotnet publish -c Release -r linux-x64 --self-contained
  2. 將編譯好的檔案傳輸到 Chromebook,使用附帶的運行時執行。

注意事項



.NET UI 程式設計

主要技術

簡單 WinForms 範例

using System;
using System.Windows.Forms;

public class MyForm : Form {
    public MyForm() {
        Button btn = new Button();
        btn.Text = "點我";
        btn.Click += (s, e) => MessageBox.Show("你點了按鈕!");
        Controls.Add(btn);
    }

    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.Run(new MyForm());
    }
}

簡單 WPF 範例 (XAML + C#)

// MainWindow.xaml
<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        Title="WPF 範例" Height="200" Width="300">
    <StackPanel>
        <Button Content="點我" Click="Button_Click"/>
    </StackPanel>
</Window>

// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e) {
    MessageBox.Show("你點了按鈕!");
}

簡單 MAUI 範例

// MainPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             x:Class="MyApp.MainPage">
    <VerticalStackLayout Spacing="25" Padding="30">
        <Button Text="點我" Clicked="OnButtonClicked"/>
    </VerticalStackLayout>
</ContentPage>

// MainPage.xaml.cs
private void OnButtonClicked(object sender, EventArgs e) {
    await DisplayAlert("通知", "你點了按鈕!", "OK");
}

UI 技術選擇建議

技術 用途 平台
WinForms 快速開發桌面應用 Windows
WPF 複雜桌面應用、MVVM 模式 Windows
MAUI 跨平台應用 Windows、macOS、Android、iOS
Blazor Web 前端開發 跨平台(Web)


.NET 遍歷子控制項以批量修改屬性

以下範例展示了如何使用 Controls->GetEnumerator() 方法來逐一遍歷 .NET 表單中的所有子控制項並批量修改其屬性。

範例程式碼:


using System;
using System.Drawing;
using System.Windows.Forms;

public class FormExample : Form
{
    public FormExample()
    {
        // 初始化一些控制項
        Button button1 = new Button { Text = "Button 1", Location = new Point(10, 10) };
        TextBox textBox1 = new TextBox { Location = new Point(10, 50) };
        
        Controls.Add(button1);
        Controls.Add(textBox1);

        // 使用 GetEnumerator() 遍歷並修改控制項屬性
        ModifyControls();
    }

    private void ModifyControls()
    {
        var enumerator = Controls.GetEnumerator();
        
        while (enumerator.MoveNext())
        {
            Control control = (Control)enumerator.Current;

            // 設定範例:將所有控制項的背景色設為淺藍
            control.BackColor = Color.LightBlue;
            
            // 若控制項為 TextBox,將其設為不可編輯
            if (control is TextBox)
            {
                control.Enabled = false;
            }
        }
    }
    
    // 啟動應用程式
    public static void Main()
    {
        Application.Run(new FormExample());
    }
}

說明

**GetEnumerator()**: 利用 `Controls.GetEnumerator()` 方法來取得控制項的列舉器,這樣可以遍歷所有子控制項。

**條件修改**: 在 `while` 迴圈中,對每個 `Control` 物件進行屬性修改,例如將背景色設為淺藍,並根據控制項類型進行特定修改,如將 `TextBox` 設為不可編輯。

**用途**: 這種方法在需要批量修改屬性時非常有效,比如調整 UI 風格或在特定情況下禁用多個控制項。

執行效果

執行後,所有控制項的背景色會變成淺藍色,且所有 `TextBox` 控制項將被設為不可編輯。



.NET MessageBox

1. 基本語法與常用重載

在 C++/CLI 中,MessageBox::Show 是一個靜態方法,用於彈出對話框。最完整的呼叫方式包含:訊息內容、標題、按鈕類型、圖示以及預設按鈕。


using namespace System::Windows::Forms;

// 最簡單的顯示
MessageBox::Show("操作已完成");

// 完整參數版本
DialogResult result = MessageBox::Show(
    "您確定要刪除此檔案嗎?",      // 訊息內容 (Text)
    "確認刪除",                   // 視窗標題 (Caption)
    MessageBoxButtons::YesNo,     // 按鈕組合
    MessageBoxIcon::Warning,      // 提示圖示
    MessageBoxDefaultButton::Button2 // 預設聚焦在第二個按鈕 (No)
);

2. 處理回傳值 (DialogResult)

當按鈕包含多個選項(如 Yes/No 或 OK/Cancel)時,必須檢查 DialogResult 來決定後續邏輯。


if (result == DialogResult::Yes) {
    // 執行刪除邏輯
} else {
    // 取消操作
}

3. 常用枚舉值參考

類別 常用選項 說明
MessageBoxButtons OK, OKCancel, YesNo, YesNoCancel 定義對話框下方出現哪些按鈕。
MessageBoxIcon Information, Warning, Error, Question 定義左側顯示的圖示與系統音效。
DialogResult OK, Cancel, Yes, No Show 方法的回傳值,代表使用者按了哪個鍵。

4. 進階技巧:結合 std::string 或 float

由於 MessageBox::Show 接收的是 .NET 的 System::String^,如果你有原生 C++ 的資料,需要進行轉換。


#include <string>
#include <msclr/marshal_cppstd.h>

// 將 std::string 顯示在 MessageBox
std::string cpp_str = "核心錯誤";
MessageBox::Show(msclr::interop::marshal_as<System::String^>(cpp_str));

// 將 float 格式化後顯示
float dist = 1.23456f;
MessageBox::Show("距離為: " + dist.ToString("F2")); // 顯示 "距離為: 1.23"

常見錯誤與解決方案



.NET 自定義輸入對話框

1. 為什麼 MessageBox 不支援 Edit Box?

System::Windows::Forms::MessageBox 的設計初衷是為了顯示「通知」或「警告」,並透過標準按鈕獲取使用者決定。它並不具備動態插入控制項的功能。若需輸入文字,標準做法是繼承 System::Windows::Forms::Form 建立自定義對話框。


2. 實作自定義 InputBox

這是一個可重複使用的簡單類別,包含了標籤、文字方塊與確定按鈕。


using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;

public ref class CustomInputBox : public Form {
public:
    TextBox^ txtInput;
    Button^ btnOK;
    Label^ lblPrompt;

    CustomInputBox(String^ title, String^ prompt) {
        // 設定表單基本屬性
        this->Text = title;
        this->Size = System::Drawing::Size(300, 150);
        this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::FixedDialog;
        this->StartPosition = FormStartPosition::CenterParent;
        this->MaximizeBox = false;
        this->MinimizeBox = false;

        // 提示文字
        lblPrompt = gcnew Label();
        lblPrompt->Text = prompt;
        lblPrompt->Location = Point(15, 15);
        lblPrompt->Size = System::Drawing::Size(250, 20);

        // 輸入框
        txtInput = gcnew TextBox();
        txtInput->Location = Point(15, 40);
        txtInput->Size = System::Drawing::Size(250, 20);

        // 確定按鈕
        btnOK = gcnew Button();
        btnOK->Text = "確定";
        btnOK->DialogResult = System::Windows::Forms::DialogResult::OK;
        btnOK->Location = Point(190, 75);
        
        // 加入控制項
        this->Controls->Add(lblPrompt);
        this->Controls->Add(txtInput);
        this->Controls->Add(btnOK);
        
        // 設定預設按鈕(按下 Enter 等同點擊確定)
        this->AcceptButton = btnOK;
    }
};

3. 如何在主程式中呼叫

使用 ShowDialog() 方法以強制回應方式開啟視窗,並檢查回傳值是否為 OK


void OnButtonClick() {
    CustomInputBox^ ibox = gcnew CustomInputBox("系統輸入", "請輸入檔案名稱:");
    
    if (ibox->ShowDialog() == System::Windows::Forms::DialogResult::OK) {
        String^ userInput = ibox->txtInput->Text;
        
        if (!String::IsNullOrWhiteSpace(userInput)) {
            // 處理使用者輸入的字串
            MessageBox::Show("你輸入了: " + userInput);
        }
    }
}

4. 方案對比

方案 實作複雜度 優點 缺點
自定義 Form 完全控制佈局、可加入驗證邏輯(如限數字)。 需撰寫較多介面程式碼。
VB Interaction 一行程式碼完成。 需引用 Microsoft.VisualBasic,樣式固定不可調。
Win32 API 無需 .NET 環境。 在 C++/CLI 中開發極其瑣碎且難以維護。

常見陷阱



PictureBox 紅色打叉錯誤

現象說明

PictureBox 顯示紅色打叉,代表在 OnPaint 執行期間發生例外, 但 .NET 預設將此例外吞掉,不會顯示在一般 Log 或中斷點中,導致難以除錯。

常見原因

原因 說明
Bitmap 已被 Dispose PictureBox 仍持有該 Bitmap 的參考,Paint 時存取已釋放的資源
來源 Stream 已關閉 用 Stream 建立 Bitmap 後關閉 Stream,Bitmap 資料失效
跨執行緒存取 背景執行緒直接修改或指派 Bitmap,與 UI 執行緒衝突
GDI+ 資源耗盡 大量 Bitmap 未 Dispose,導致 GDI+ 記憶體不足

錯誤訊息對照

例外訊息 根本原因 解法方向
ArgumentException: Parameter is not valid Bitmap 已被 Dispose 或 Stream 已關閉 深複製 Bitmap,不依賴 Stream
InvalidOperationException: object is in use elsewhere 跨執行緒同時存取同一個 Bitmap 加 lock,或 Invoke 回 UI 執行緒
OutOfMemoryException GDI+ 資源耗盡或圖片格式不支援 及時 Dispose 舊 Bitmap,檢查圖片來源
ExternalException: A generic error occurred in GDI+ Bitmap 對應的檔案或 Stream 已不存在 確保 Bitmap 生命週期內來源仍有效

除錯步驟

由於紅叉問題不一定常發生,建議依序進行:

  1. 攔截 OnPaint 例外:繼承 PictureBox,覆寫 OnPaint, 用 try/catch 捕捉例外並輸出至 Debug.WriteLine, 捕捉後執行 this.Image = null 避免持續紅叉。
  2. 確認例外訊息:在 Visual Studio Output 視窗觀察訊息, 對照上方錯誤訊息表找出根本原因。
  3. 追蹤 Bitmap 生命週期:在每次指派與 Dispose Bitmap 時, 記錄 Environment.StackTrace,找出誰在什麼時間點釋放了資源。
  4. 防止 Dispose 仍在使用的 Bitmap:Dispose 前先檢查 PictureBox 是否還持有該 Bitmap,若是則先將 Image 設為 null 再 Dispose。

安全換圖模式

所有換圖操作一律透過此模式,確保執行緒安全與 Dispose 順序正確:

// C++/CLI
void SafeUpdateImage(Bitmap^ newBmp) {
    if (pictureBox1->InvokeRequired) {
        pictureBox1->Invoke(gcnew Action<Bitmap^>(
            this, &YourClass::SafeUpdateImage), newBmp);
        return;
    }
    Bitmap^ old = safe_cast<Bitmap^>(pictureBox1->Image);
    pictureBox1->Image = newBmp;   // 先換上新圖
    if (old != nullptr) old->Dispose();  // 再 Dispose 舊圖
}

Stream 建立 Bitmap 的正確做法

// 錯誤:stream 關閉後 bitmap 失效
using (var stream = new FileStream(path, FileMode.Open)) {
    pictureBox1.Image = new Bitmap(stream);
}

// 正確:深複製,不依賴 stream
Bitmap loaded;
using (var stream = new FileStream(path, FileMode.Open))
using (var tmp = new Bitmap(stream)) {
    loaded = new Bitmap(tmp);
}
pictureBox1.Image = loaded;

問題排查清單

檢查項目 確認方式
Bitmap 是否在別處被 Dispose 統一透過 SafeUpdateImage 換圖,記錄 StackTrace
來源 Stream 是否已關閉 改用深複製 new Bitmap(tmp)
是否跨執行緒存取 換圖時檢查 InvokeRequired,一律 Invoke 回 UI 執行緒
紅叉是立即出現還是過一陣子 立即出現多為來源問題;過一陣子多為 Dispose 時機問題


.NET StackTrace中取得 Message m 的內容

問題背景

當堆疊追蹤中出現以下錯誤訊息時,開發者可能需要檢查 Message 物件的內容:

   at ....MainForm.Dispose(Boolean A_0)
   at System.Windows.Forms.Form.WmClose(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   ...
    

此時需要透過覆寫或檢測 WndProc 方法中的 Message m 來檢查其詳細資訊。

解決方法

以下提供幾種方法來檢查或記錄 Message 物件的內容。

1. 覆寫 WndProc 方法

如果可以存取表單或控制項的原始碼,建議覆寫 WndProc 方法,直接記錄或檢查 Message m 的內容。


protected override void WndProc(ref Message m)
{
    try
    {
        // 記錄 Message 的內容
        Console.WriteLine($"Message Details: hWnd={m.HWnd}, Msg={m.Msg}, WParam={m.WParam}, LParam={m.LParam}");
        base.WndProc(ref m);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Exception: {ex}");
        throw; // 保持原有例外行為
    }
}

    

這段程式碼會在每次接收到訊息時記錄相關資訊,並允許開發者進一步分析。

2. 在 Dispose 方法中加入例外處理

如果錯誤發生在 Dispose 方法中,可以在方法內加入例外處理來檢查相關資訊。


protected override void Dispose(bool disposing)
{
    try
    {
        if (disposing)
        {
            // 釋放資源
        }
        base.Dispose(disposing);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Exception in Dispose: {ex}");
        throw;
    }
}

    

這樣可以確保在釋放資源時不會忽略重要的錯誤資訊。

3. 使用全域例外處理

如果無法確定錯誤發生的位置,可以透過全域例外處理來記錄堆疊追蹤和相關資訊。


AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
    Exception ex = (Exception)args.ExceptionObject;
    Console.WriteLine($"Unhandled Exception: {ex}");
    Environment.Exit(1);
};

    

4. 使用 Debug 工具檢查 Message

可以使用 Visual Studio 將斷點設置在 WndProcDispose 方法中,並檢查 Message 物件的內容。

Message 物件的關鍵屬性

注意事項



覆寫 Form.WmClose(Message& m) 在 .NET C++

問題背景

在 .NET C++/CLI 中,WmClose(Message& m)System.Windows.Forms.Form 的內部保護方法,無法直接覆寫。不過,可以透過覆寫 WndProc 方法來攔截並處理 WM_CLOSE 訊息,以達到類似覆寫 WmClose 的效果。

完整範例


#include <Windows.h>
#include <System.Windows.Forms.h>

using namespace System;
using namespace System::Windows::Forms;

public ref class CustomForm : public Form
{
protected:
    // 模擬 WmClose 行為的覆寫
    void WmClose(Message% m)
    {
        // 在這裡加入自定義的 WM_CLOSE 行為
        if (MessageBox::Show("確定要關閉視窗嗎?", "確認", MessageBoxButtons::YesNo) == DialogResult::Yes)
        {
            // 繼續調用基底行為以進行正常關閉
            this->Form::WndProc(m);
        }
        else
        {
            // 阻止關閉視窗
            return;
        }
    }

    // 覆寫 WndProc,攔截 WM_CLOSE 訊息並調用 WmClose
    virtual void WndProc(Message% m) override
    {
        const int WM_CLOSE = 0x0010;

        if (m.Msg == WM_CLOSE)
        {
            WmClose(m); // 調用自定義的 WmClose 方法
        }
        else
        {
            // 處理其他訊息
            Form::WndProc(m);
        }
    }
};

[STAThread]
int main(array<String^>^ args)
{
    Application::EnableVisualStyles();
    Application::SetCompatibleTextRenderingDefault(false);

    CustomForm^ form = gcnew CustomForm();
    form->Text = "覆寫 WmClose 行為範例";
    Application::Run(form);

    return 0;
}

    

程式碼解釋

1. WmClose 方法

模擬覆寫 WmClose 方法,在這裡自定義視窗關閉的行為。透過確認對話框詢問使用者是否要關閉視窗。

2. 覆寫 WndProc

覆寫 WndProc 方法來攔截 WM_CLOSE 訊息,並將其委派給自定義的 WmClose 方法。

3. 繼續處理其他訊息

在沒有攔截 WM_CLOSE 的情況下,調用基底類別的 WndProc 方法處理其他訊息。

注意事項



System::Windows::Forms::Control::InvokeRequired

用途說明

InvokeRequiredSystem::Windows::Forms::Control 的屬性,用來判斷目前執行緒是否為控制項所屬的 UI 執行緒。當從背景執行緒存取 UI 控件時,應使用 Invoke 將操作封送回 UI 執行緒。

完整範例:設定 PictureBox 圖片


using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;

ref class ImageHelper {
public:
    static void SetImageSafe(PictureBox^ pPictureBox, Bitmap^ b) {
        if (pPictureBox == nullptr)
            return;

        if (pPictureBox->InvokeRequired) {
            // 使用 MethodInvoker 呼叫同一函式,但於 UI 執行緒執行
            pPictureBox->Invoke(
                gcnew MethodInvoker(gcnew Action(ImageHelper::InvokeCallback), 
                gcnew Tuple(pPictureBox, b))
            );
        } else {
            ApplyImage(pPictureBox, b);
        }
    }

private:
    // 封送的函式代理
    static void InvokeCallback(Object^ state) {
        Tuple^ args = static_cast^>(state);
        ApplyImage(args->Item1, args->Item2);
    }

    // 實際執行設定圖片邏輯
    static void ApplyImage(PictureBox^ pPictureBox, Bitmap^ b) {
        try {
            if (b != nullptr) {
                if (pPictureBox->Image != nullptr)
                    delete pPictureBox->Image;
                pPictureBox->Image = b;
            }
        }
        catch (System::Exception^ ex) {
            Console::WriteLine("設定圖片失敗: " + ex->Message);
        }
    }
};

重點整理



保護 pPictureBox->Image = b 避免程式崩潰

可能發生崩潰的原因

安全設定圖片的建議作法


using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;

void SetImageSafe(PictureBox^ pPictureBox, Bitmap^ b) {
    if (pPictureBox->InvokeRequired) {
        pPictureBox->Invoke(gcnew MethodInvoker(
            gcnew EventHandler(nullptr, &SetImageInvoker)
        ), gcnew array { pPictureBox, b });
    } else {
        SetImageInternal(pPictureBox, b);
    }
}

void SetImageInvoker(Object^ sender, EventArgs^ e) {
    // 不使用此版本,可忽略,保留為完整形式參考
}

void SetImageInternal(PictureBox^ pPictureBox, Bitmap^ b) {
    try {
        if (b != nullptr) {
            if (pPictureBox->Image != nullptr)
                delete pPictureBox->Image;

            pPictureBox->Image = b;
        }
    }
    catch (System::Exception^ ex) {
        Console::WriteLine("設定圖片失敗: " + ex->Message);
    }
}

簡化版本(建議使用匿名委派)

如果你只是要快速保護 `pPictureBox->Image = b;`,以下寫法更直接有效:

using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;

void SafeAssignImage(PictureBox^ pPictureBox, Bitmap^ b) {
    if (pPictureBox->InvokeRequired) {
        pPictureBox->Invoke(gcnew MethodInvoker(gcnew delegate {
            try {
                if (b != nullptr) {
                    if (pPictureBox->Image != nullptr)
                        delete pPictureBox->Image;
                    pPictureBox->Image = b;
                }
            } catch (System::Exception^ ex) {
                Console::WriteLine("設定圖片失敗: " + ex->Message);
            }
        }));
    } else {
        try {
            if (b != nullptr) {
                if (pPictureBox->Image != nullptr)
                    delete pPictureBox->Image;
                pPictureBox->Image = b;
            }
        } catch (System::Exception^ ex) {
            Console::WriteLine("設定圖片失敗: " + ex->Message);
        }
    }
}

補充說明



以 Blazor 建立統一語言的前端開發環境

什麼是 Blazor?

Blazor 是由微軟推出的前端框架,允許開發者使用 C# 和 Razor 語法來建立互動式 Web 應用程式,無需使用 JavaScript,即可在瀏覽器中執行 C# 程式碼。

開發者效益

Blazor 模式

與 .NET 的整合

Blazor 無縫整合 ASP.NET Core,可與現有的 .NET 應用程式結合,並支援依賴注入、路由、元件架構等功能。

部署方式

小結

對於以 .NET 為主要技術的開發者而言,Blazor 提供了與 Node.js 統一 JavaScript 語言相對應的解決方案,使前後端都能使用 C#,建立一致性更高的開發體驗。



使用 .NET MAUI 建立跨平台應用

什麼是 .NET MAUI?

.NET MAUI(Multi-platform App UI)是微軟推出的跨平台應用框架,可使用 C# 和 XAML 撰寫一次程式碼,部署到 Windows、Android、iOS 甚至 macOS。

開發者效益

支援的平台

Blazor Hybrid 模式

可使用 .NET MAUI 的 Blazor Hybrid 模式,在原生應用中嵌入 Web UI,讓開發者用 Razor 組件開發跨平台使用者介面,仍可存取原生 API。

整合與部署

小結

.NET MAUI 是目前最完整的 .NET 跨平台解決方案,可結合 Blazor 達成 Web、桌面與行動平台的統一應用開發,提供給 .NET 開發者一個語言與技術一致的全端體驗。



ASP.NET Core 與 .NET MAUI 的比較

定位

支援平台

開發語言

UI 與互動

適用場景

整合性

.NET MAUI 可透過 API 呼叫與 ASP.NET Core 建立的後端服務整合,形成完整的「前端 App + 後端 API」架構。

總結

ASP.NET Core 解決「後端服務」與「Web 應用」問題,.NET MAUI 則專注在「用戶端跨平台 App」。兩者並不互斥,反而常搭配使用,構成完整解決方案。



簡單的 .NET MAUI 應用範例

建立基本的 .NET MAUI 應用

以下是一個使用 .NET MAUI 建立的跨平台應用程式範例,這個應用在畫面上顯示一個按鈕,點擊後會更新文字計數。

主要檔案:MainPage.xaml


<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiApp.MainPage">

    <VerticalStackLayout Spacing="25" Padding="30">
        <Label x:Name="counterLabel"
               Text="你尚未點擊按鈕"
               FontSize="24"
               HorizontalOptions="Center" />

        <Button Text="點我"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentPage>

後端程式碼:MainPage.xaml.cs


namespace MauiApp;

public partial class MainPage : ContentPage
{
    int count = 0;

    public MainPage()
    {
        InitializeComponent();
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;
        counterLabel.Text = $"你已經點擊了 {count} 次";
    }
}

執行方式

小結

這個簡單的範例展示了 .NET MAUI 的跨平台能力,開發者只需撰寫一次 XAML 與 C# 程式碼,即可同時在多個平台運作。



在 Visual Studio 中建立 .NET MAUI 專案

步驟一:安裝必要工具

步驟二:建立新的 MAUI 專案

  1. 開啟 Visual Studio
  2. 點選「建立新專案」
  3. 搜尋並選擇「.NET MAUI App」範本
  4. 按「下一步」,輸入專案名稱與位置
  5. 點選「建立」完成專案初始化

步驟三:了解專案結構

步驟四:執行應用程式

補充說明

小結

使用 Visual Studio 建立 .NET MAUI 專案只需幾個步驟,且能一套程式碼同時部署到多個平台,是現代 C# 全端開發者的理想選擇。



MainPage.xaml 實例與解釋

範例:MainPage.xaml

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.MainPage">

    <VerticalStackLayout Spacing="25" Padding="30"
                         VerticalOptions="Center">

        <Label 
            Text="歡迎使用 .NET MAUI!" 
            SemanticProperties.HeadingLevel="Level1"
            FontSize="32"
            HorizontalOptions="Center" />

        <Button 
            x:Name="counterBtn"
            Text="點我!" 
            Clicked="OnCounterClicked"
            HorizontalOptions="Center" />

    </VerticalStackLayout>

</ContentPage>

對應的 MainPage.xaml.cs

using System;
namespace MyMauiApp;

public partial class MainPage : ContentPage
{
    int count = 0;

    public MainPage()
    {
        InitializeComponent();
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;
        counterBtn.Text = $"你已經點了 {count} 次";
    }
}

元件說明

C# 程式碼說明

結果效果

執行後,畫面中央會顯示一個歡迎訊息與一個按鈕。每次點擊按鈕,按鈕上的文字就會更新為「你已經點了 X 次」。



MauiProgram.cs 實例與解釋

範例:MauiProgram.cs

using Microsoft.Extensions.Logging;

namespace MyMauiApp;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        
        builder
            .UseMauiApp() // 指定應用程式的進入點
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

功能說明

常見擴充

總結

MauiProgram.cs 就像 ASP.NET Core 的 Startup.csProgram.cs,是應用啟動與服務註冊的主要設定中心。開發大型 MAUI 應用時,擴充此檔案來加入 DI、日誌、組件設定等是非常常見的做法。



.NET C++ 程式部署至 Ubuntu

前言

在 Ubuntu 系統上部署 .NET C++(C++/CLI 或以 .NET 為基底的 C++ 應用)需要根據專案類型分別處理。若使用純 C++/CLI(依賴 .NET Framework),將無法直接於 Linux 執行,需改為使用 .NET 6/7/8 的跨平台技術(如 C++/CLR → C# 或使用 C++/Native 搭配 .NET 互通)。

部署方式概述

根據應用性質,部署可分為三種情境:

  1. 純 C++ 應用:使用 g++ 編譯,直接於 Ubuntu 執行。
  2. .NET Managed C++ (C++/CLI):需重新設計為 .NET 6 以上版本支援的 C# 或 C++/CLI for Core(僅 Windows 支援)。
  3. 混合式應用:C# 為主,C++ 以動態函式庫(.so)提供性能模組。

環境安裝

在 Ubuntu 中安裝 .NET SDK 與必要工具:

sudo apt update
sudo apt install -y dotnet-sdk-8.0
sudo apt install -y build-essential

(可依版本替換 dotnet-sdk-8.0dotnet-sdk-7.0 或其他版本)

建立專案

若使用 .NET C# 為主程式,並呼叫 C++ 函式庫:

# 建立主應用
dotnet new console -n MyApp
cd MyApp

# 建立 native 函式庫
mkdir native
cd native
nano add.cpp

add.cpp 範例:

extern "C" int add(int a, int b) {
    return a + b;
}

編譯為共享函式庫:

g++ -fPIC -shared -o libadd.so add.cpp

在 C# (.NET) 呼叫 C++ 函式庫

Program.cs 加入:

using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("libadd.so", EntryPoint="add")]
    public static extern int Add(int a, int b);

    static void Main()
    {
        Console.WriteLine("3 + 5 = " + Add(3, 5));
    }
}

執行與測試

回到專案根目錄執行:

dotnet run

若輸出結果為 3 + 5 = 8,表示部署成功。

發佈部署版本

可將程式打包為可獨立執行的部署檔:

dotnet publish -c Release -r linux-x64 --self-contained true

生成的可執行檔位於 bin/Release/net8.0/linux-x64/publish/

Docker 容器部署(選用)

可使用 Docker 將應用容器化,以便在任何 Ubuntu 系統運行:

# Dockerfile 範例
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
COPY ./publish .
COPY ./native/libadd.so /usr/lib/
ENTRYPOINT ["./MyApp"]

建構與執行容器:

docker build -t myapp .
docker run --rm myapp

注意事項

總結

部署 .NET C++ 程式至 Ubuntu 的可行方式通常是採「C# + C++ 原生函式庫」架構。若原始程式依賴 C++/CLI,需重新設計以支援跨平台 .NET。最推薦的方式是使用 .NET 8 + 原生 C++ 模組搭配 Docker,達到穩定、可攜且一致的部署流程。




email: [email protected]
T:0000
資訊與搜尋 | 回dev首頁
email: Yan Sa [email protected] Line: 阿央
電話: 02-27566655 ,03-5924828
阿央
泱泱科技
捷昱科技泱泱企業