最近用 C# 寫了幾支小工具要交給 End-User 桌面執行,為簡化部署程序,將參照 DLL 併入整成單一 EXE 檔是我愛用的做法。

免安裝,不必解壓一堆 EXE、DLL 存到特定資料夾,整個工具就一個 EXE 檔,放到桌面上點兩下便能用,很棒吧!

.NET Core 3 支援編譯封裝成單一執行檔 (參考:封裝 .NET Core 應用程式成單一可執行檔並優化檔案大小 by Poy Change),執行時再將內嵌的 DLL 存放在暫存資料夾使用。去年底發佈的 .NET 5 則更上層樓,做到真正的單一 EXE 檔執行,不再需要將參照 DLL 寫成實體暫存檔。

雖已看到美好未來,但手邊這幾個工具仍是用 .NET Framework 開發。在 .NET Framework 實現單檔部署,我優先想到的是 MSBUild.ILMerge.Task,但我的 Console Apllcation 用到 SharePoint CSOM 不幸踩到 CSOM 跟 ILMerge 不相容的雷,靠著改用自動內嵌參照 DLL + AppDomain.CurrentDomain.AssemblyResolve 大絕才解決。

剩下一個小問題 - 程式有用 NLog 記錄執行歷程以備偵錯,一般需要有 NLog.config 定義存 Log 的路徑。都說要追求單一執行檔了,留個 NLog.config 小尾巴有點阿雜。另一方面,若使用者將程式放在桌面,NLog.config 及 Log 目錄跟 EXE 一起出現在桌面也不太優。經評估,用程式動態指定 Log 目錄是較完美的做法,一方面不用多產生 NLog.config,再則可以視情況動態修正 - 預設將 Log 存在 EXE 同路徑的 MyToolLogs 資料夾;若 EXE 被放在桌面,就改存在 TEMP 下的 MyToolLogs 資料夾以免有礙觀瞻。

強大的 NLog 支援以程式碼設定路徑,讓我們能輕鬆完成任務,下面是程式範例:

using NLog;
using NLog.Targets;
using System;
using System.IO;
using System.Reflection;

namespace MyTool
{
    class Program
    {
        //透過程式設定 NLog 時,要留意 statci logger 取得物件時機
        //要排在 SetupNLog() 之後
        static ILogger logger = null;
        
        static void SetupNLog()
        {
            var config = new NLog.Config.LoggingConfiguration();
            //偵測程式是不是被放在桌面
            var onDesktop =
                Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ==
                Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
            var logFile = new NLog.Targets.FileTarget("f") { 
                FileName = 
                    (onDesktop ? Path.GetTempPath() : "${basedir}") +
                    "/MyToolLogs/${shortdate}.log" 
            };
            config.AddRule(LogLevel.Trace, LogLevel.Fatal, logFile);
            LogManager.Configuration = config;
            logger = NLog.LogManager.GetLogger("debug");
        }

        static void Main(string[] args)
        {
            //趕在引用 NLog 程式庫前完成設定
            SetupNLog();

            //檢查 Log 路徑並列印出來
            //REF: https://blog.darkthread.net/blog/get-nlog-path/
            Console.WriteLine(
                (LogManager.Configuration.FindTargetByName("f") as FileTarget).FileName
                .Render(new LogEventInfo() { 
                    TimeStamp = DateTime.Now, LoggerName = "loggerName" 
                }));

            logger.Debug("Hi there.");
            Console.ReadLine();
        }
    }
}

測試成功!

Example of how to config the NLog path dynamically by code.


Comments

# by Alex

statci (x) static (o)

# by Jeffrey

to Alex, 噗,寫在註解沒有 Compiler 盯著就又滑了 XD

Post a comment