C# 使用 .NET 建立自己的 CLI 工具

平時我們許多操作都離不開命令列介面 (Command-Line Interface, CLI),例如顯示或變更目前目錄的 cd、列出檔案和資料夾的 ls 。在 dotnet 中我們也可能會使用到 dotent new, run, watch 等,那要如何自己寫一個呢?

我們先使用 .NET 7 建立一個 Console 應用程式

在 ConsoleAppHoliday.csproj 檔案中增加下面三行
    
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net7.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <PackAsTool>true</PackAsTool>
        <ToolCommandName>ruyut</ToolCommandName>
        <PackageOutputPath>./nupkg</PackageOutputPath>
    </PropertyGroup>

</Project>
    

其中 ToolCommandName 可以選擇不輸入,若有該資訊則指令名稱就是 ToolCommandName 的內容,以上面的例子就是 ruyut

PackageOutputPath 則是打包的目標路徑

因為本文的重點是在建立 CLI 工具,所以筆者使用 C# 簡單寫了一個讀取中華民國政府行政機關辦公日曆表 API 的小程式,略過程式碼說明直接開始建立 CLI 工具。完整程式碼附在文末,有興趣的讀者可以自行取用。

打包專案:
    
dotnet pack
    

全域安裝套件:
    
dotnet tool install --global --add-source .\nupkg ConsoleAppHoliday
    

註:ConsoleAppHoliday 為本示範專案的名稱,請自行替換。

查看全域安裝的套件:
    
dotnet tool list -g
    

解除安裝全域套件:
    
dotnet tool uninstall -g ConsoleAppHoliday
    

使用本程式的指令判斷今天是否為假日:
    
ruyut isHoliday
    

使用本程式的指令判斷指定日期是否為假日:
    
ruyut isHoliday 20221225
    

使用本程式的指令尋找下一個假日:
    
ruyut nextHoliday
    

執行截圖:


話說在指令介面自己判斷和處理參數真的是有夠麻煩的,或許可以試試看現成的套件:C# 使用 CommandLineUtils 簡化處理傳入參數 (上)

參考資料:
Microsoft.Learn - Tutorial: Create a .NET tool using the .NET CLI
Tutorial: Install and use a .NET global tool using the .NET CLI

完整程式碼範例

    

using System.Globalization;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;

if (args.Length == 0)
{
    Console.WriteLine("請輸入有效參數:");
    Console.WriteLine("isHoliday [日期]");
    Console.WriteLine("nextHoliday [日期]");
    return;
}

DateTime date = DateTime.Now;

if (args.Length >= 2)
    date = DateTime.ParseExact(args[1], "yyyyMMdd", CultureInfo.InvariantCulture);

List<TaiwanCalenderDto> taiwanCalender = GetTaiwanCalender(date);


if (args[0] == "isHoliday")
{
    IsHoliday(taiwanCalender, date);
}
else if (args[0] == "nextHoliday")
{
    GetNextHoliday(taiwanCalender, date);
}
else
{
    Console.WriteLine("請輸入有效參數:");
    Console.WriteLine("isHoliday [日期]");
    Console.WriteLine("nextHoliday");
    return;
}

// 呼叫 API 取得台灣假日資訊
List<TaiwanCalenderDto> GetTaiwanCalender(DateTime date)
{
    string url = $"https://cdn.jsdelivr.net/gh/ruyut/TaiwanCalendar/data/{date.Year.ToString()}.json";
    var client = new HttpClient();
    var response = client.GetAsync(url).Result;

    if (response.StatusCode == HttpStatusCode.NotFound)
    {
        Console.WriteLine("假日資訊取得錯誤,沒有該年度資料");
        return new List<TaiwanCalenderDto>();
    }

    if (response.StatusCode != HttpStatusCode.OK)
    {
        Console.WriteLine($"假日資訊取得錯誤,錯誤代號: {response.StatusCode}");
        return new List<TaiwanCalenderDto>();
    }

    var json = response.Content.ReadAsStringAsync().Result;
    List<TaiwanCalenderDto> taiwanCalenderDtos = JsonSerializer.Deserialize<List<TaiwanCalenderDto>>(json) ?? new();
    if (!taiwanCalenderDtos.Any())
    {
        Console.WriteLine("假日資訊取得錯誤,沒有該年度資料");
        return new List<TaiwanCalenderDto>();
    }

    return taiwanCalenderDtos;
}


// 判斷是否為假日
bool IsHoliday(List<TaiwanCalenderDto> taiwanCalenderDtos, DateTime date)
{
    string dateStr = date.ToString("yyyyMMdd");
    TaiwanCalenderDto? taiwanCalenderDto = taiwanCalenderDtos
        .FirstOrDefault(x => x.Date == dateStr);
    if (taiwanCalenderDto == null)
    {
        Console.WriteLine("假日資訊取得錯誤,沒有該日期資料");
        return false;
    }

    if (taiwanCalenderDto.IsHoliday)
    {
        Console.WriteLine(
            $"{dateStr} 是假日,說明: {(string.IsNullOrWhiteSpace(taiwanCalenderDto.Description) ? "例假日" : taiwanCalenderDto.Description)}");
        return true;
    }
    else
    {
        Console.WriteLine($"{dateStr} 不是假日");
        return false;
    }
}

void GetNextHoliday(List<TaiwanCalenderDto> taiwanCalenderDtos, DateTime date)
{
    string dateStr = date.ToString("yyyyMMdd");
    TaiwanCalenderDto? nextHoliday = taiwanCalenderDtos
        .FirstOrDefault(x => x.IsHoliday && x.Date.CompareTo(dateStr) > 0);

    if (nextHoliday == null)
    {
        Console.WriteLine("噢不!今年沒有假日了!");
    }
    else
    {
        Console.WriteLine(
            $"下一個假日是 {nextHoliday.Date},說明: {(string.IsNullOrWhiteSpace(nextHoliday.Description) ? "例假日" : nextHoliday.Description)}");
    }
}

/// <summary>
/// TaiwanCalendar Json Data
/// </summary>
public class TaiwanCalenderDto
{
    [JsonPropertyName("date")] public string Date { get; set; }
    [JsonPropertyName("week")] public string Week { get; set; }
    [JsonPropertyName("isHoliday")] public bool IsHoliday { get; set; }
    [JsonPropertyName("description")] public string Description { get; set; }
}
    

留言