Hangfire Setting on AspDotnet Core

前言 你有排程相關的需求嗎? 需要定期呼叫指定的方法來做事嗎? 相信大家多多少少都有接過類似的需求,不過除了使用 Windows 的排程來做或是使用 cron job 這些傳統工具來做到之外你還有更現代的選擇 - Hangfire。 內文 其實 .NET solution 的排程選擇不只 Hangfire 還有老牌的 Quartz.NET 也是非常好的選擇。單純以排程作業來說 Quartz 可以做到的控制是比 Hangfire 還要來的更細緻,不過 Hangfire 就優勢來說大概就是比較沒有歷史包袱,管理介面也是本身內建,不像 Quartz 是需要另外找第三方來幫助 (ex. GitHub - CrystalQuartz)。 好啦….其實兩個都很好我只是雞蛋裡挑骨頭…只是最近有用到 Hangfire 所以就用這個來練習排程吧~ Initialize your project for Hangfire Hangfire 最吸引人的地方除了他學習成本較 Quartz.NET 低,另外就是他自帶漂亮 Dashboard 而且設定還很容易。想要套用 Dashboard 的話先引用套件 1 dotnet add package Hangfire.AspNetCore 這個套件會相依 Hangfire.Core 所以可以先裝這個就好,然後在 Configure 設定區塊裡補上以下這段 1 2 3 4 5 6 7 8 9 10 11 12 13 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { //... app.UseEndpoints(endpoints => { endpoints.MapHangfireDashboard( new DashboardOptions { StatsPollingInterval = 3600000 }); } //... } StatsPollingInterval 這個參數的預設值是 2000 它的單位是毫秒,也就是 2秒它就會去背景檢查 Job 執行的狀態,這個數值在測試環境基本上不用太擔心什麼,不過若是正式環境的話建議要調整,不然會讓背後儲存 Job 的位置壓力很大 (若是存在關聯式資料庫的話就更要小心了)。 ...

April 5, 2022 · 3 分鐘 · 530 字

NSwag on Asp.net Core

前言 ASP.NET core 的名字還沒記熟它又要改版本名了,不過對未來的發展這或許是好事吧。依據 roadmap 所示未來的版本名稱就會比較清楚一點。而在 .net 發長趨於穩定時開發應用時讓你又愛又恨的文件撰寫,你還在獨立寫一份嗎?程式碼跟文件之間的更新成本就不能在壓低一點嗎?若你還沒聽過 NSwag 的話這是一個你值得做的嘗試,另外不可或缺的就是 log 系統,你還在用 NLog 或是內建的日誌 lib 嗎?不體驗一下 Serilog 就太可惜了,尤其是目前應用多數跑在 container 裡,在系統 micro-service 化的現代裡,將 log 寫在本機裡在 container 數量漸多後管理上是有點麻煩,而 serilog 的外掛可以幫你把 log 直接送去指定的地方。 內文 本文將會基於 Asp.NET Core 3.1 版本來開發,雖然 .NET 5 已經推出但是它並非 LTS 版本所以並非首選。 本文的 source code 在 這裡 第一個步驟就是把必要的 Packages 裝一裝 1 dotnet add .\AspNetCoreSerilog\AspNetCoreSerilog.csproj package Serilog.AspNetCore 在 appsetting.json 直接將原本的 Logging 區塊取代為以下設定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "Serilog": { "Using": [ "Serilog.Sinks.Console" ], "MinimumLevel": "Information", "WriteTo": [ { "Name": "Console", "Args": { "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}" } } ], "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], "Properties": { "Application": "AspNetCoreSerilog" } } } 而在 Program.cs 也就是入口程式那邊補上以下程式 ...

June 25, 2021 · 3 分鐘 · 627 字

Asp.Net Core With Docker

Docker Image 我們可以從 DockerHub 中找到 Microsoft 官方的 Images 連結入口。以這次要執行 ASP.NET 程式來說我們可以在這裡找到可以用的 Images。基本上上面都會有簡易的教學要怎麼用,不過琳瑯滿目的 Images 裡面還是有目前相對比較推薦的,像是 Alpine 為基底的 Images 應該是目前佔用空間最小的 Images,當然以此為代價,裡面安裝的工具真的是再基本不過。 ASP.NET Core Install 選擇 .NET Core SDK 來安裝,官方載點。目前版本 3.1 是 LTS 所以我們就選擇這個來裝吧(記得要選 Build Apps,也就是 SDK 來裝,不要選 runtime)。 一旦安裝完畢在 CMD/Power Shell 中就可以使用 dotnet 來操作 dotnet core SDK 了。 可以執行 dotnet --list-sdks 來看看自己目前安裝了哪些 SDK。 我們可以使用 CLI 提供的 new 指令搭配 template 來生成預設的範本,這邊就先使用 asp.net core mvc 當作示範。若需要詳細的 template 種類的話,有官方文件可參考,或可以執行 dotnet new --help 指令來看 new 指令可以有哪些操作,而用 dotnet new --list 來取得目前 SDK 所提供的所有 Template,輸出大致會長以下這樣: ...

May 24, 2020 · 4 分鐘 · 753 字

Serilog Essential Furthermore

前言 三年前寫了一篇跟 Serilog 有關的極致入門文章後就把這個主題閒置到現在,他的很多特色不要說點到為止,而是根本沒提到,但這樣對於這樣一個好用的 Library 來說實在太糟蹋,所以打算重啟這個主題。因為當時就有位這個主題開個 repo 所以我們也就不要浪費網路的空間了,直接回收利用吧(還順便把 solution 檔給升級了)~ source code 這次將會針對設定做一點初步的介紹,雖然近期事情開始漸漸變多,但看看能抽出多少時間來把相關的內容介紹一下,也許有機會變成一個小的系列 (也許啦 XD)。 內文 這邊會展示在進行 Serilog 設置時可以採行什麼樣的寫法在 Production 環境下可以有比較好的閱讀性與維護性,還有我們可以針對需要寫 Log 的情境給予一些過濾條件,讓這個系統更有彈性,最後我們整合目前官方網站上有提供的一些 Sink 來稍稍展示 Serilog 搭配上 Sink 之後可以展現的可能性。 在先前的文章之中我們直接針對 Serilog 的 LoggerConfiguration 類別來設定,但若是需要設定的條件眾多那麼光是這個設定就會讓程式看起來很複雜。這邊會使用一個小技巧來讓設定看起來比較簡潔一點。 首先建立一個給 Serilog 做初始的一個類別,並使用最簡單的設定方式來為 Logger 做設定。 ps. 這邊的 LogFactory 類別只是作範例簡單使用,重點在於 InitMyLogger 方法內的寫法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class LogFactory { public static void InitMyLogger() { Log.Logger = new LoggerConfiguration() .WriteTo.NLog( Serilog.Events.LogEventLevel.Error, "[AppId:{AppId}]{Message:lj}{NewLine}{Exception}") .WriteTo.Console( restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information, outputTemplate: "{Timestamp:HH:mm:ss} [AppId:{AppId} {Level:u3}] {Message:lj}{NewLine}{Exception}") .WriteTo.Logger(configer => { configer.Filter .ByIncludingOnly(Matching.FromSource<BusinessLogic>()) .WriteTo.Console( Serilog.Events.LogEventLevel.Warning, "{Timestamp:HH:mm:ss} [AppId:{AppId2}{AppId} {Level:u3}]$ {Message:lj}{NewLine}{Exception}"); }).CreateLogger(); } } 這邊不難看出目前雖然只是簡單的三樣設定就已將讓這個設定開始顯得有些複雜了。若這是一段需要被維護的程式碼不難想像應該會是個痛苦的開始。 ...

March 31, 2020 · 4 分鐘 · 842 字

.NET Polly Essential

Polly .NET foundation 的 Polly 專案是為了解決暫時性錯誤處理(transient-fault-handling)的一個函式庫,裡面提供了處理眾多相關錯誤處理的函式呼叫。 目前的應用程式越來越少是自己獨立作業就把一個 request 或是一項作業給處理完成的,通常都會跟內部或是外部系統合作,而這個部份通常為了解相依關係,會定義好介面常見的就是定義 Api 他的傳入與回傳值,尤其是在這個微服務觀念盛行的時期,這個作法尤其常見。 但是像這種鬆耦合的系統架構,雖然美其名是開發/佈署不會互相影響彼此,但單一服務獨活於系統中又顯的很雞肋,偏偏依賴 Api 的方式其實並不可靠,網路基本上就不是建立在可靠的傳輸之上,所以連線失敗/服務暫時下線可以說是需要假定會隨時發生的事情。 不過很多時候這種連線失敗很可能只是一個突波,下一個 request 可能就成功了,而 Polly 就像是把這些繁瑣設定與工作包起來方便重複使用的函式庫。 其實這個函式庫,跟混亂工程有著密不可分的關係,不過這個部份主題很大,這篇就不提了,只列出一些可以供參考的資料。 Demo source code GitHub: link How to install Polly 若你覺得安裝 Package 就是一行 nuget 指令就可以,那其實也沒錯,不過你會發現 Polly 這個套件相依的 packages 預設實在多的驚人。 Install-Package Polly -Version 7.1.1 真的這樣幹的話你會發現,用預設的版本安裝相依套件的話,它會幫你安裝超多 System 相關的 Library, 主要原因就是 NETStandard.Library 預設安裝的版本實在太低,導致這些東西會一起裝進去,若想要避免這種情況,的話就需要先把相依的套件先升級。 Install Microsoft.NETCore.Platforms 這是為了 NETStandard 2.x 要安裝的 預設版本號是需要大於 1.1.0 不過這邊可以先升到頂 Install NETStandard.Library 這邊就直接裝到 2.x 以上的版本,Polly 目前的版本 (7.1.1) 預設最低版號要求到 1.6.1,但若真的只裝到 1.6.1 的話就需要安裝一堆 System 相關的套件,所以不太推薦。 ...

November 24, 2019 · 3 分鐘 · 597 字

Serilog Rookie's Note

前言 Serilog 是一款給 .NET 平台的 Log Library ,它最大的特色應該是使用事件來驅動,而不是單純的寫下 Log ,這點讓它跟其他的 Library 有很大的不同,而他所提供的多樣Sink讓這點的發揮更加強大。他可以做到同時記下 Log ,發送信件,存到DB並且傳到 Slack 中通知維運人員,這些事情都可以在記下 Log 時一併做完,只需要設定一次。這點是我覺得這個 Library 最強大的地方。 source code 內文 Serilog 除了可以在 .NET 平台使用外,他還支援了 .NET Core ,這讓它在非 Windows 平台上的使用是可能的, Source Code 跟以下的文章就是在MAC環境下搭配 .NET Core 使用 Serilog。 開啟一個新的專案後在 Dependencies 按下右鍵選擇 Add Packages,再使用關鍵字「Serilog」查詢~ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /// <summary> /// My logger. /// </summary> public class MyLogger { /// <summary> /// The logger. /// </summary> private ILogger _logger; /// <summary> /// Initializes a new instance of the <see cref="T:BenSerilogNlog.MyLogger"/> class. /// </summary> public MyLogger() { ////最基本的建立Logger的方式 this._logger = new LoggerConfiguration() .WriteTo.Console() .CreateLogger(); } /// <summary> /// Logs the info. /// </summary> /// <param name="message">Message.</param> public void LogInfo(string message){ this._logger.Information(message); } } 呼叫端可以這樣使用 ...

November 27, 2017 · 1 分鐘 · 189 字

Autofact Memory Leak Experiment

前言 現在有很多系統除非太小,不然應該都會使用第三方開發的 DI 容器,來實現 IoC。但是使用 DI 容器時免不了要對於物件的生命週期有一定的了解,不然到時候出現相關的問題就手忙腳亂了。因此這一篇文章就記錄了我使用 Autofac 時,遇到的 memory leak 的問題,並把所做的實驗記錄下來。 實驗的情境主要是 run 在 console 環境中,模擬類似 windows service 的情境,所以 web 的 ASP.Net 環境並不在此實驗的考慮範圍內。 這篇文章使用的原始碼連結如下(已更新專案為 .NET 5): GitHub repo url 內文 物件的生命週期與最初註冊物件的依賴是怎樣的方式,預設是 InstancePerDependency 這個方法,它會確保你每次呼叫 Resolve 方法時可以取得新的物件,並不會與其他呼叫端使用同一份實體。這個特性在大多數的時候都會符合需求。 因此在注入時我們先這樣寫: ps.快照時間基本上都是10秒30秒各拍一次,執行超過50秒就結束實驗。 實驗一 1 2 3 4 5 6 7 8 9 10 public class DAModules : Autofac.Module { protected override void Load(ContainerBuilder builder) { var assembly = Assembly.Load("NineYi.ERP.DA.ERPDB"); builder.RegisterType<deamonresourcerepository>() .As<ideamonresourcerepository>() .InstancePerDependency(); } } 接下來就是我們這次的實驗目標的寫法: ...

December 5, 2016 · 4 分鐘 · 747 字

Using Strong Name Tool To Encryption Message

前言 一般來說在.net中實現加密時我們會使用 RSACryptoServiceProvider 這個類別來實現產生公私鑰進行加解密的行為,而且一個簡單的 [ToXMLString()]](https://docs.microsoft.com/zh-tw/dotnet/api/system.security.cryptography.rsa.toxmlstring?view=netframework-4.7.2) 方法就能把公鑰(或甚至是私鑰,但實務上很不建議)給吐出來。不過除了使用這個類別來產生鑰匙外我們其實還可以利用強式名稱工具來幫我們產生鑰匙,好處是不但可以作為加解密的鑰匙來源,沒有金鑰容器名稱以及存取層級(User Layer, Machine Layer)的管理 issue 簡單用,還可以順便用它來簽署組件,一檔多用資源徹底利用。以下就是利用強式名稱工具來實作的方式。 內文 強式名稱工具(Strong Name Tool) 是安裝 Visual Studio 之後就會附上的開發用工具我們可以開啟 “Visual Studio x64 Win64命令提示字元”,鍵入 “sn.exe /?” 來觀看可以使用的參數,這裡我們會用到的就是最簡單的 -k 參數而且就是使用 default 值。我們就利用這個工具產生一組全新的 snk 檔案放在 D 槽吧~ 我們已經可以在D槽的跟目錄下面看到產生好的 Strong Name Key File 了~ 要注意的是使用預設的話 key size 長度就是 1024 bit 這長度與能拿來加密的文字長度會有直接關係使用預設長度的話一次能夠加密的文字長度為 128bits,不過要小心在 .net 中會有 padding 的 issue 造成實際上可以加密的文字長度會更短(註一),這個問題的解決方式我們會在稍後的範例中看到。 準備好之後我們就來開啟一個專案試試看這 snk 檔要怎麼幫我們加密資料吧~ 我們這邊就使用最單純的 Console 專案來進行測試即可~ ps.這邊記得把 D 槽根目錄中的 snk 檔案放在專案的 bin 資料夾底下。 ps2.為了保持目的單純不小心類別就被搞大了… 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 using System; using System.Collections; using System.IO; using System.Security.Cryptography; class Program { private static string _strongNameKeyFile = "Test.snk"; public static string GetPublicKey { get; private set; } private static string GetPrivateKey { get; set; } public static string Data(string plaintext) { using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { rsa.ImportCspBlob(File.ReadAllBytes(_strongNameKeyFile)); GetPublicKey = rsa.ToXmlString(false); GetPrivateKey = rsa.ToXMLString(true); // base-64 encoded. return Convert.ToBase64String(EncryptData(plaintext)); } } // 若資料量很大的話(資料量小以預設來說就是資料量小於117 bits,那可以直接呼叫Encrypt()來進行加密及可) // 就需要分段加密不然一定會炸掉,吐一個Bad Length的例外訊息。 private static byte[] EncryptData(string rawdata) { // 憑證的金鑰預設就是1024 bit // buffer扣去11 bits是為了padding使用,請參考註一的連結。 const int encryptionBufferSize = (1024 / 8) - 11; const int DecryptionBufferSize = 1024 / 8; RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); // 這邊利用公鑰來加密,因此解密的一方需要取得密鑰才能解密,這樣可以確保有私鑰的人才有辦法看到資訊。 rsa.FromXmlString(GetPublicKey); // 進行加密時都需要把字串轉成byte陣列來操作,這邊需要注意轉換的問題! byte[] dataEncoded = System.Text.Encoding.UTF8.GetBytes(rawdata); using (MemoryStream ms = new MemoryStream()) { byte[] buffer; int pos = 0; int copyLength = encryptionBufferSize; while (true) { if (pos + copyLength > dataEncoded.Length) copyLength = dataEncoded.Length - pos; buffer = new byte[copyLength]; Array.Copy(dataEncoded, pos, buffer, 0, copyLength); pos += copyLength; // 這邊不使用OEAP padding,使用 PKCS#1 v1.5 padding(填補法的差異請參考註一) // 另外也要注意雖然加密的資料需要扣除 padding 量,但是寫入的長度依然不變。 ms.Write(rsa.Encrypt(buffer, false), 0, DecryptionBufferSize); Array.Clear(buffer, 0, copyLength); if (pos >= dataEncoded.Length) break; } return ms.ToArray(); } } public static string GetPlainDataBack(string encryptdata) { string result = string.Empty; using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { const int decryptionBufferSize = 1024 / 8; // 使用公鑰加密就必須用私鑰解密。 rsa.FromXmlString(GetPrivateKey); byte[] encryptdataarray = Convert.FromBase64String(encryptdata); using (MemoryStream ms = new MemoryStream(encryptdataarray.Length)) { byte[] buffer = new byte[decryptionBufferSize]; int pos = 0; int copyLength = buffer.Length; while (true) { Array.Copy(encryptdataarray, pos, buffer, 0, copyLength); pos += copyLength; byte[] resp = rsa.Decrypt(buffer, false); ms.Write(resp, 0, resp.Length); Array.Clear(resp, 0, resp.Length); Array.Clear(buffer, 0, buffer.Length); if (pos >= encryptdataarray.Length) break; } result = System.Text.Encoding.UTF8.GetString((ms.ToArray())); } } return result; } static void Main(string[] args) { var plaintext = "This is a plain text!"; var result = Data(plaintext); Console.WriteLine("Orignal Text:{0}", plaintext); Console.WriteLine("After Encryption:{0}", result); result = GetPlainDataBack(result); Console.WriteLine("After Decryption:{0}",result); Console.Read(); } } 結語 以上就是使用強式名稱工具來達成資訊加密、解密的需求,我們可以發現若對方要可以解讀這個密文那 snk 檔是對方勢必得取得的檔案,因此該檔案的存取是一定要控管的,這是不方便之處。另外其實實務上除了加解密之外還會順手做數位簽章….這就留到下次吧… ...

April 19, 2013 · 3 分鐘 · 509 字

ASP.NET Web Form Life Cycle

開發 ASP.NET Web Form 的工程師對於網頁生命週期(Page Life Cycle)一定要熟記在心中,也要知道每個循環中的會發生的個事件的主要目的,才不會在需求要修改或是出現Bug時不知道該往哪裡找問題。不過,講是這樣講其實 ASP.NET 的網頁生命週期需要注意的細節是滿多的,而且一個週期內會發生的事件可能比你想像中的還要多。 一般來說網頁生命週期會經過 網頁要求 => 開始 => 初始化 => 載入 => 回傳事件處理 => 呈現 => 卸載 資料來源 表一:MSDN 一般網頁生命週期階段 Page Life Cycle 階段 說明 網頁要求 在網頁生命週期開始前會發生網頁要求。當使用者要求網頁時,ASP.NET 會判斷是否需要剖析和編譯網頁 (因此開始網頁的生命週期),或是可以在不執行網頁的情況下,傳送網頁的快取版本做為回應。 開始 在開始階段,會先設定網頁屬性,如 Request 與 Response。在這個階段中,網頁也會判斷要求是否為回傳或是新的要求,然後設定 IsPostBack 屬性。網頁同時也會設定 UICulture 屬性。 初始化 在網頁初始化期間,可以使用網頁上的控制項,並且設定每個控制項的 UniqueID 屬性。如果適用,也會將主版頁面與佈景主題套用到網頁。如果目前的要求是回傳,則尚未載入回傳資料,並且控制項屬性值並未還原至檢視狀態提供的值。 載入 在載入期間,如果目前的要求是回傳,就會使用從檢視狀態和控制項狀態復原的資訊載入控制項屬性。 回傳事件處理 如果是回傳要求,會先呼叫控制項事件處理常式,然後才會呼叫所有驗證程式控制項的 Validate 方法,以設定個別驗證程式控制項與網頁的 IsValid 屬性。 呈現 在呈現前,會儲存網頁和所有控制項的檢視狀態。在呈現階段,網頁會呼叫每個控制項的 Render 方法,藉此提供文字寫入器將其輸出寫入網頁之 Response 屬性的 OutputStream 物件。 卸載 完整呈現網頁之後,會引發 Unload 事件,然後傳送至用戶端予以捨棄。此時將會執行網頁屬性 (如 Response 與 Request) 的卸載及清除工作。 而在這樣的生命週期階段中會有數十個事件依序發生,而不論是作為網頁容器的 Page 類別或是使用者控制項(User Control)或是伺服器控制項(Server Constrol)幾乎都有相對應的事件。 Page 頁面是每個 Web Form 都會繼承的類別,依據需求上面也可以放置自行撰寫的使用者控制項(User Control),或是裡面也可以直接擺上 Server Control,因此我們可以把這個類別是為一個容器,裡面可以擺放很多顯示用的控制項,而這個控制項裡會有很多事件會在一個 request 進來後依序引發,以下就是網頁生命週期的「生命週期事件」: ...

November 28, 2012 · 5 分鐘 · 890 字

ASP.NET Web Form Single Login

會有這樣的題目主要是因為業主的要求(廢話)。明確的說是這樣的,同一時間同一帳號只能在一個地方登入,之後登入的可以把前面登入的剔除。這個機制其實之前我並沒有想到要怎麼解,或是說沒想到不用資料庫的方式要怎麼解…(超遜的啦!)。不過認真想過後才發現這個機制其實不會太難,或是說我的解法其實很簡單,而且應該也可以用在MVC架構裡。 研究方法 Session ASP.NET提供Session讓我們可對工作狀態進行管理,可以讓我們跨越多個要求儲存與瀏覽器工作階段關連的訊息,Session提供Key-value pairs的方式來存取這些資料。雖然我們很常拿Session來儲存使用者的資訊,像是使用者的帳號或是一些其他資訊,不過Session是每個使用者各自有的,這裡面的資訊並不會共享,因此利用Session來達成需求顯然是不行。我們需要的應用系統的全域變數,或是資料結構來對整個系統的使用者進行管理。這樣的需求我想全域應用程式類別(預設檔名為Global.asax)就是我這邊提出的解答! 簡述關於Global.asax的運作 當ASP.NET應用程式收到第一個要求的時候會先使用ApplicationManager建立應用程式定義域(應用程式定義域可以隔離應用程式間的全域變數,而且能夠允許每個應用程式個別進行卸載。)。在所有核心物件初始化後會藉由建立System.Web.HttpApplication類別的實體來執行應用程式。若應用程式中有Global.asax(繼承自HttpApplication)則會改用衍生自HttpApplication的Global.asax來建立執行應用程式的實體。而當處理要求的時候則會執行下述事件:(注意!這邊僅列達成需求出最主要的兩個) 1 2 3 4 5 6 7 8 9 ///當要求ASP.NET應用程式中的第一個資源(如:網頁)時呼叫, ///這個方法在應用程式生命週期中只會被呼叫一次。 ///我們讓全域變數在這邊進行宣告。 Application_Start(object sender, EventArgs e) { } ///這個方法在MSDN裡面的解釋有點怪,不過在應用程式生命週期中該事件發生也是只有一次, ///就是當IIS重啟或是應用程式正常關閉時就會發起該事件。 ///我們讓全域變數所用到的資源在這裡釋放。 Application_End(object sender, EventArgs e) { } 開始撰寫時我們要先準備一下這個管理使用者的容器類別: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 /// 系統中記錄目前線上全部使用者的識別號的容器。 public sealed class SystemUserPool { private static ConcurrentDictionary<string, string> _UserPool = new ConcurrentDictionary<string, string>(); /// 記錄系統中新登入的使用者。 /// userNumber = 登入者的帳號。 /// userIp = 登入者的 IP 位址。 /// 使用者登入成功與否。 public Boolean AddUser(String userNumber, String userIp) { return _UserPool.TryAdd(userNumber, userIp); } /// 剔除系統中指定的使用者。 /// userNumber = 使用者的帳號。 /// 剔除成功與否。 public Boolean DeleteUser(String userNumber) { string str = String.Empty; return _UserPool.TryRemove(userNumber, out str); } /// 列出目前系統中在線上的使用者。 /// 使用者列表。 public List<string> ListAllUser() { return _UserPool.Keys.ToList<string>(); } /// 判斷使用者是否仍在線上。 /// userNumber = 欲查明的使用者。 /// 若在線上則為 true,反之則為 false。 public Boolean IsOnLine(String userNumber) { return _UserPool.Keys.Contains(userNumber); } /// 判斷使用者是否從同一個地方登入。 /// userNumber = 使用者帳號。 /// userIp = 使用者目前 IP 位址。 /// 若 userIp 與系統中使用者的 IP 位址一致表示從同一個地方登入。 public Boolean IsTheSameIP(String userNumber, String userIp) { String orignalIP = String.Empty; if (_UserPool.TryGetValue(userNumber, out orignalIP)) { if (orignalIP == userIp) return true; return false; } return false;//若沒能取得,表示使用者已經離線。 } /// 更新使用者的 IP 位址。 /// userNumber = 使用者帳號。 /// userIP = 使用者新的 IP 位址。 public void UpdateUserIP(String userNumber, String userIP) { if (!IsOnLine(userNumber)) return; _UserPool[userNumber] = userIP; } /// 把系統使用者儲蓄池清空。 public void ClearUserPool() { _UserPool.Clear(); } } 在Application_Start中就這樣寫: ...

September 3, 2012 · 2 分鐘 · 336 字