protected void Application_Start(...) {
if (DateTime.Today.Day == 15 || DateTime.Today.Day == 30) { // 邏輯錯誤:Equals 比較的是 DateTime 物件非 Day
Timer timer = new Timer();
timer.Elapsed += MyTask;
timer.Enabled = true;
}
}
致命缺陷:
Application_Start 只跑一次,若重啟當天不是 15/30 號,任務永遠不會註冊。Elapsed 事件) 無 HttpContext.Current,你的 CreateExcel 使用 Response 直接拋異常。DateTime.Today.Equals(15) 永遠為 false,應為 DateTime.Today.Day == 15。| 方案 | 可靠性 | 實作難度 | 適用場景 |
|---|---|---|---|
| Windows Service / 排程工作 | ⭐⭐⭐⭐⭐ | 中 | **生產環境標準首選**。獨立進程、不受 IIS 回收影響、可設定復原重啟、支援長時間任務。 |
| Hangfire / Quartz.NET (進程內排程庫) | ⭐⭐⭐⭐ | 低 | 不想部署獨立服務。Hangfire 持久化到 DB,重啟不丟任務,有儀表板監控。需注意 Web App 閒置停止問題 (需設定「永遠啟動」+「預載」)。 |
| Azure WebJobs / AWS Lambda / 雲端排程器 | ⭐⭐⭐⭐⭐ | 低 | 雲端部署首選。Serverless、按執行付費、高可用、內建重試。 |
| SQL Server Agent Job | ⭐⭐⭐⭐ | 中 | 邏輯極簡 (單純存過程導出檔案)。DBA 維護、極穩定。 |
| Global.asax + Timer | ⭐ | 低 | **僅適合本地測試、極低流量內網、容忍遺漏任務的玩具專案。** |
public partial class ExcelExportService : ServiceBase {
private Timer _timer;
public ExcelExportService() { InitializeComponent(); }
protected override void OnStart(string[] args) {
_timer = new Timer(CheckAndExecute, null, TimeSpan.Zero, TimeSpan.FromHours(1)); // 每小時檢查一次
}
private void CheckAndExecute(object state) {
// 邏輯:今天是 15 或 30 號 且 尚未執行過 (需持久化標記,如寫 DB/檔案/註冊表)
if ((DateTime.Today.Day == 15 || DateTime.Today.Day == DateTime.DaysInMonth(DateTime.Today.Year, DateTime.Today.Month))
&& !AlreadyExecutedToday()) {
ExecuteExport(); // 你的導出邏輯,注意:這裡沒有 HttpContext,改寫成檔案流/資料庫操作
MarkExecutedToday();
}
}
private void ExecuteExport() {
DataTable dt = GetDataFromDB();
// 使用 NPOI / Open XML / EPPlus 生成檔案到磁碟/網路磁碟/資料庫 Blob
// File.WriteAllBytes(path, GenerateExcel(dt));
}
}
Install-Package Hangfire.Core
Install-Package Hangfire.SqlServer (或 PostgreSQL, MySQL 等)
// Global.asax.cs Application_Start
GlobalConfiguration.Configuration.UseSqlServerStorage("YourConnectionString");
app.UseHangfireDashboard("/hangfire"); // 選裝:監控頁面
app.UseHangfireServer();
// 註冊排程 (僅需註冊一次,重啟自動恢復)
RecurringJob.AddOrUpdate(() => ExportExcelJob(), Cron.Day(15, 30)); // 每月 15、30 號
// 或 Cron.Monthly(15) + 手動邏輯判斷月末
public void ExportExcelJob() {
// 這裡直接操作 DB、檔案系統、雲端儲存
var data = _dbContext.GetReportData();
var bytes = _excelService.Generate(data);
_blobStorage.Save($"reports/{DateTime.Today:yyyyMMdd}.xlsx", bytes);
_notificationService.Send("報表已生成");
}
關鍵設定 (解決 IIS 閒置停止): IIS 應用程式池 -> 進階設定 -> 閒置逾時 = 0 (關閉) + 啟動模式 = AlwaysRunning。網站 -> 預載 = True。否則 Hangfire Server 會隨 Worker Process 睡著。
Response.Write 改為 MemoryStream / FileStream / NPOI workbook.Write(stream)。HttpUtility.UrlEncode、Response.Charset 等 Web 專屬代碼。**請立即放棄 Global.asax Timer 方案。** 它在生產環境中不可靠、不可控、難除錯。
- **有伺服器控制權** → 寫 **Windows Service** (最穩、最傳統、DBA/運維最愛)。
- **想留在專案裡、愛用儀表板** → 裝 **Hangfire** (設定好 IIS AlwaysRunning 即可)。
- **上雲了** → 直接用雲端原生排程 (WebJobs, Cloud Scheduler, EventBridge)。
核心原則:**背景任務不屬於 HTTP 請求週期,勿綁定 HttpContext。**