Hero Image
[.NET] C# 將 PDF 轉為列印文件送出至印表機

PdfiumViewer 是開源的 C# 控件,用於顯示和列印 PDF 文件。它基於 Chromium 瀏覽器使用的 PDF 渲染引擎 Pdfium 所開發。 而Pdfium 是 Chromium 瀏覽器使用的 PDF 渲染引擎,由 Google 和 Mozilla 共同開發。它是一個開放原始碼的函式庫,用於 PDF 文件的解碼、渲染和編輯。 PdfiumViewer 提供以下功能: 顯示 PDF 文件的所有頁面。 支持縮放、旋轉、翻頁等操作。 支持列印 PDF 文件。 在 c# 中使用 PdfiumViewer 可以將 PDF 轉換為列印文件,送至印表機進行列印。 安裝 Nuget 套件: PdfiumViewer PdfiumViewer.Native.x86.v8-xfa 引用 using PdfiumViewer; using System.Drawing.Printing; using System.IO; 程式範例 從記憶體列印: var pdfBytes = new byte[] { }; // todo: 取得 PDF by docid var printerName = ""; // todo 取得印表機名稱 // 列印 using (MemoryStream memoryStream = new MemoryStream(pdfBytes)) { var pageSettings = new PageSettings() { Margins = new Margins(0, 0, 0, 0) }; var printerSettings = new PrinterSettings(); if (!string.IsNullOrEmpty(printerName)) printerSettings.PrinterName = printerName; using (var document = PdfDocument.Load(memoryStream)) { using (PrintDocument printDocument = document.CreatePrintDocument()) { printDocument.PrinterSettings = printerSettings; printDocument.DefaultPageSettings = pageSettings; printDocument.PrintController = new StandardPrintController(); printDocument.Print(); } } } 從檔案列印: var file = ""; // todo 取得檔案路徑 var printerName = ""; // todo 取得印表機名稱 // 列印 var pageSettings = new PageSettings() { Margins = new Margins(0, 0, 0, 0) }; var printerSettings = new PrinterSettings(); if (!string.IsNullOrEmpty(printerName)) printerSettings.PrinterName = printerName; using (var document = PdfDocument.Load(file)) { using (PrintDocument printDocument = document.CreatePrintDocument()) { printDocument.PrinterSettings = printerSettings; printDocument.DefaultPageSettings = pageSettings; printDocument.PrintController = new StandardPrintController(); printDocument.Print(); } } Reference Github-PdfiumViewer Nuget-PdfiumViewer Nuget-PdfiumViewer.Native.x86.v8-xfa

Hero Image
[.NET] WebView2 單一檔案部屬

有些部屬環境要求能單一執行檔,如果要使用自己的 DLL 就會有問題,研究了內嵌 DLL 的作法應用於 WebView2 專案上。 以 .NET Framework 4.7.2 的 WinForm 專案為例,目標環境為 windows x64。 Dependency 安裝 Nuget 上的 Microsoft.Web.WebView2。 把這些資料夾底下的 Dll 複製到專案資料夾下,並加入版控 packages\Microsoft.Web.WebView2.1.0.1823.32\runtimes packages\Microsoft.Web.WebView2.1.0.1823.32\lib\net45 參考移除上述 Dll 參考(移除 Nuget 參考),改直接參考專案資料夾下的 Dll。 把參考的 Dll 調整為內嵌資源 AssemblyHelper.cs public class AssemblyHelper { public string Name { get { return this.assembly.GetName().Name; } } public string AppDataPath { get; set; } private Assembly assembly; public AssemblyHelper() { assembly = Assembly.GetCallingAssembly(); AppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); AppDataPath = Path.Combine(AppDataPath, Name); } /// <summary> /// Extract embeded dll to target path /// </summary> /// <param name="resourceName">Dll embed path</param> /// <param name="targetPath">Dll extract distination</param> public void ExtractEmbeddedDLL(string resourceName, string targetPath) { var targetDir = Path.GetDirectoryName(targetPath); if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir); using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) { using (FileStream fileStream = new FileStream(targetPath, FileMode.Create)) { resourceStream.CopyTo(fileStream); } } } /// <summary> /// 設置解析組件路徑的事件處理常式 /// </summary> public void EnableEmbededManifestDll() => AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly; /// <summary> /// Assembly 解析行為 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> /// <returns></returns> public static Assembly OnResolveAssembly(object sender, ResolveEventArgs args) { Assembly assembly = Assembly.GetCallingAssembly(); string project = Assembly.GetEntryAssembly().GetName().Name; string manifestItem = $"{project}.{new AssemblyName(args.Name).Name}.dll"; using (Stream stream = assembly.GetManifestResourceStream(manifestItem)) { if (stream == null) return null; byte[] assemblyRawBytes = new byte[stream.Length]; stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length); return Assembly.Load(assemblyRawBytes); } } } Program.cs internal static class Program { [STAThread] static void Main() { try { var asm = new AssemblyHelper(); // Load an extracted DLL dynamically asm.EnableEmbededManifestDll(); var loaderDllFolderPath = Path.Combine(asm.AppDataPath, "runtimes\\win-x64\\native"); var dll = Path.Combine(loaderDllFolderPath, "WebView2Loader.dll"); var loaderDllEmbedPath = $"{asm.Name}.runtimes.win_x64.native.WebView2Loader.dll"; asm.ExtractEmbeddedDLL(loaderDllEmbedPath, dll); // 將需注入 DLL 的邏輯抽離 Main 才能跑 run(loaderDllFolderPath); } catch (Exception ex) { MessageBox.Show(ex.Message); } } private static void run(string loaderDllFolderPath) { CoreWebView2Environment.SetLoaderDllFolderPath(loaderDllFolderPath); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } } 隱藏暫存檔 假設 webview2 元件變數是wv:

Hero Image
[.NET] 開發階段管理應用程式的敏感資料

基於資訊安全的理由,密碼等敏感性資訊不應該出現在程式碼裡面, 應該把敏感性資料儲存在專案以外的地方,防止對 Git Server 提交專案程式碼的時候把密碼推送到伺服器上, 因此程式開發、部屬階段都應該用適當的策略存放敏感性資料讓程式讀取使用, .NET 儲存敏感性資料大致上來說可以用這兩種方式: 環境變數 Secret Manager 這裡紀錄要如何在 .NET 開發環境以 Secret 儲存敏感性資料,以及程式讀取的方式。 Secret Manager Secret Manager 就是在本地端特定路徑存放 secret.json 檔案: %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json ~/.microsoft/usersecrets/<user_secrets_id>/secrets.json 需要先針對個別專案啟用專案的 Secret Storage 支援,切換到專案目錄執行: dotnet user-secrets init 在專案檔裡的 UserSecretsId 區段會得到一段 GUID,這個要作為 user_secrets_id 資料夾名稱。 <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <UserSecretsId>79a3edd0-2092-40a2-a04d-dcb46d5ca9ed</UserSecretsId> </PropertyGroup> 以指令設置一組 secret,例如連線字串: dotnet user-secrets set "ConnectionStrings:POSTGRES" "User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;Pooling=true;Min Pool Size=0;Max Pool Size=100;Connection Lifetime=0;" --project "D:\workspace\MySolution\MyProject" 以檔案直接設置 secret windows type .\input.json | dotnet user-secrets set Linux/MacOS

Hero Image
[.NET Core] 不阻塞的非同步控制器(Non-Blocking Asynchronous Controllers)

ASP .NET Core 當中的 Web 控制器屬於IO密集的應用程式,當中主要使用的 TAP 是一種簡易使用、語言層級的非同步設計模式。 透過 TAP 可設計出非同步(Asynchronous)/非阻塞(Non-Blocking)的 Web API,大幅提高 Web 應用程式的併發性(Concurrency)。 非同步方法 C# 當中基於 TAP 設計的的非同步方法 (TAP method) 有幾個特性: 產生可等待 awaitable 型別 (Task, Task<TResult>, ValueTask, 和 ValueTask<TResult>), 其中以 Task、Task<TResult>最常見。 非同步方法的參數順序通常跟同步版本的方法相同,但方法名稱以 Async 結尾。 async、await await 運算子用來等待非同步行為完成, 或等待非同步行為完成後解析回傳值, await 運算子只能用在非同步方法中, 因此 await 運算子的外層方法必須套用 async 修飾, 否則會出現錯誤。 非同步 Action 設計原則: 總是加上 async 關鍵字 async 的方法裡面可以等待非同步方法。 action 前加上 async 的作用在於建立一個管理回傳任務的狀態機(state machine), 當 async 方法擲出例外時會被狀態機捕獲並放到任務中回傳, 而這也是以 Task 作為回傳值的方法的預期行為。 如果沒有 async 關鍵字則擲出的例外會被直接傳遞到呼叫者(caller), 因此除非確定該 aciton 不會擲出任何例外,否則一律加上 async。

Hero Image
[.NET Core] ASP .NET Core 3.1 驗證與授權(二)-驗證設定

驗證方案(Authentication Scheme) 驗證方案包含兩個部分: 驗證處理函式(Authentication handler),可能是 IAuthenticationHandler 或 AuthenticationHandler 的實作,相當於驗證方案的行為,責任範圍涵蓋: 驗證使用者, 驗證成功時,建構呈現使用者識別(user identity)的 AuthenticationTicket。 驗證失敗時,回傳 ’no result’ 或 ‘failure’ 負責從請求上下文(request context)中建構使用者識別 (user identity)。 定義了 challenge/forbid action。 驗證處理函式的設定選項(Opitons of Authentication handler)。 驗證方案當中的 authencate action 負責從請求上下文(request context)中建構使用者識別 (user identity), 常見的例子為: cookie authentication scheme 從 cookie 資訊建構 user identity. JWT bearer scheme 反序列化(deserialize)、驗證(validate) token,並從 token 所攜帶資訊建構 user identity 使用驗證方案 在 Startup.ConfigureServices 以 AddAuthentication 註冊驗證服務時會回傳一個 AuthenticationBuilder, AuthenticationBuilder 設定驗證方案的方式有: 呼叫 scheme-specific 擴充方法,例如 AddJwtBearer、AddCookie,這些擴充方法會自動呼叫 AuthenticationBuilder.AddScheme 設定需要的驗證方式。 以 AuthenticationBuilder 內建方法 AddScheme 手動設定,一般來說較少使用。 P.S.另外可使用 polycy schemes 把多個 scheme 整合到一個使用。

Hero Image
[.NET Core] ASP .NET Core 3.1 驗證與授權(三)-Cookie 驗證實例

前兩篇介紹了驗證、授權在 .NET Core 當中的基本的概念,本節實作 Cookie 驗證的設定、簽發、登出… Configuration 在 Startup.ConfigureServices 方法中設置驗證方案, 並且可以在 AddCookie 當中設置 CookieAuthenticationOptions(見前一節) // 設置 cookie 驗證作為應用程式預設的驗證方案 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) // 將 cookie 服務添加到服務容器當中 .AddCookie(); 在 Startup.Configure 方法中,呼叫 UseAuthentication、UseAuthorization, 啟用驗證中間件並設置 HttpContext.User 屬性, UseAuthentication 必須在 UseAuthorization 之前,且兩者都必須在 UseEndpoints 之前被呼叫: app.UseAuthentication(); // 驗證 app.UseAuthorization(); // 授權 // 端點對應 app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); }); Cookie Policy Middleware 在中間件當中設置的驗證政策會作用於全域(每個請求), 舉例來說,最常用的就是限制應用程式所有 Cookie 的 SameSite 屬性, 所有 Controller 簽發的 Cookie.SamSite 屬性會被限縮為較嚴格(不比 MinimumSameSitePolicy 寬鬆)的設置: app.UseCookiePolicy(new CookiePolicyOptions { // 所有 Cookie.SamSite 設置都會被提升為 Strict MinimumSameSitePolicy = SameSiteMode.Strict, // Cookie.SamSite 設置為 None 的話會被提升為 Lax //MinimumSameSitePolicy = SameSiteMode.Lax, // MinimumSameSitePolicy 設置為最寬鬆,因此不會影響 Cookie.SamSite //MinimumSameSitePolicy = SameSiteMode.None, }); Create an authentication cookie .NET Core 利用 ClaimsPrincipal 將序列化的使用者資訊儲存在 Cookie 當中 而 ClaimsPrincipal 可包含很多 ClaimsIdentity(但通常只有一個); ClaimsIdentity 可以且通常包含很多 Claims(聲明), 而每個 Claims 是包含型別(ClaimType)、值(ClaimValue)。 因此為登入使用者建立 Cookie 驗證的步驟如下: