Seringkali kita membutuhkan suatu eksekusi kode yang bisa berjalan secara background pada waktu – waktu tertentu yang kita butuhkan, misalkan pada tengah malam mengeksekusi proses di database secara otomatis. Pada lingkungan sistem operasi windows sendiri sebetulnya ada Windows Service yang memungkinkan aplikasi berjalan secara background di sistem operasi dan ada juga Windows Task Scheduller , di sistem operasi berbasis Linux/Unix pun juga terdapat daemon dan cron job schedulled job yang memungkinkan sistem operasi menjalankan tugas tertentu pada waktu yang telah dijadwalkan.
Namun karena kita mengembangkan aplikasi dengan platform web di mana aplikasi kita dihosting pada suatu Web host yang bisa jadi berbeda beda lingkungan sistem operasinya, kebutuhan akan suatu background service yang bisa running secara terjadwal dan berjalan otomatis merupakan suatu kebutuhan penting dan harus dipikirkan. Bayangkan aplikasi kita dihosting di IIS Windows Server, atau mungkin berjalan under Kestrel di Linux, atau bisa jadi running di Azure Application Service. Maka salah satu pilihan terbaik yang bisa juga dipertimbangkan adalah membuat sendiri background job worker yang running under ASP.NET Core MVC.
Hosted service yang bisa menjalankan background task sudah ada sejak .net Core 3.1 , dan hingga saat ini pada saat tulisan ini dibuat adalah .NET 9 masih mendukung penuh Hosted Service terhadap background task. Setiap background task yang dibuat, harus merupakan suatu implementasi dari interface IHostedService. Keuntungan dari IHostedService adalah dukungan terhadap DI (Dependency Injection) sehingga class yang dibuat dapat mengakses service – service yang telah dideklarasikan di startup/program ASP.NET Core MVC. Berikut ini adalah implementasi minimal dari IHostedService
namespace WebApplicationEF.Worker
{
public class JobWorker : IHostedService,IDisposable
{
private IServiceProvider svcProvider;
public JobWorker(IServiceProvider serviceProvider)
{
this.svcProvider = serviceProvider;
}
public Task StartAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
}
}
Dari listing code di atas, kita membuat Background task dengan nama class JobWorker. Bagian StartAsync adalah method yang akan dipanggil ketika service pertama kali dijalankan, sedangkan bagian StopAsync merupakan method yang akan dipanggil ketika aplikasi ASP.NET Core berhenti berjalan dan aplikasi memberi tahu background task bahwa aplikasi akan mengalami shutdown.
Kita akan membuat background job yang akan menjalankan perintah SQL pada jam tertentu secara otomatis dan me-log-nya. Perintah SQL yang dijalankan adalah query sederhana yang menghitung jumlah total dari SUM(LineTotal) pada jam tertentu. Jam nya kita set setiap jam 03.00 AM dan bisa diganti sesuai kebutuhan (Catatan: Query yang dijalankan hanyalah contoh , dan bisa saja diganti dengan eksekusi stored procedure atau semacamnya. Query yang digunakan mengakses DB Sample AdventureWork2022).
Pertama dari project web ASP.NET Core , buat folder worker dan new file JobWorker.cs seperti pada gambar berikut

Pada file JobWorker.cs tersebut, tambahkan listing kode berikut ini
// File : JobWorker.cs
using EF.AppDbContext;
using Microsoft.EntityFrameworkCore;
namespace WebApplicationEF.Worker
{
public class JobWorker : IHostedService,IDisposable
{
private IServiceProvider svcProvider;
private System.Timers.Timer timer;
public JobWorker(IServiceProvider serviceProvider)
{
this.svcProvider = serviceProvider;
timer = new System.Timers.Timer();
timer.Enabled = true;
timer.Interval = 30000;
timer.Elapsed += Timer_Elapsed;
}
public Task StartAsync(CancellationToken cancellationToken)
{
timer.Start();
return Task.CompletedTask;
}
private bool IS_PROCESS_RUNNING = false;
private TimeOnly TimeToCheck = new TimeOnly(3, 0, 0);
private bool IS_JOB_FINISHED = false;
private DateTime? LastRunning = null;
private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
if (!IS_PROCESS_RUNNING)
{
var currentTime = TimeOnly.FromDateTime(DateTime.Now);
if(currentTime >= TimeToCheck)
{
// wah sudah waktunya
if (!IS_JOB_FINISHED
&& (LastRunning == null || LastRunning < DateTime.Now))
{
IS_PROCESS_RUNNING = true;
try
{
using(var dbContext = new SalesDbContext())
{
string sql = $@" SELECT sum(o.[LineTotal]) as Value FROM [AdventureWork2022].[Sales].[SalesOrderDetail] o";
var retval = dbContext.Database.SqlQueryRaw<decimal?>(sql).FirstOrDefault() ?? 0;
WriteLog($"PROSES: Total penjualan hari ini adalah USD $ {retval:#,##0.00}.");
}
}
catch (Exception ex)
{
WriteLog($"PROSES: Terjadi error: {ex.Message}");
}
IS_PROCESS_RUNNING = false;
IS_JOB_FINISHED = true;
LastRunning = DateTime.Now;
}
else
{
// hari ini sudah pernah berjalan jobnya, besok lagi ya
WriteLog("Job sudah berjalan, Idle, tidak ada yang perlu dikerjakan. Bisa santai dan sambil jajan.");
}
}
else
{
// belum waktunya
WriteLog("Idle, tidak ada yang perlu dikerjakan karena belum waktunya Running ! Bisa ngopi ngopi cantik dulu.");
if(LastRunning != null && LastRunning?.Date < DateTime.Now.Date)
{
// sudah beda hari, aman kosongkan flag
IS_JOB_FINISHED = false;
LastRunning = null;
}
}
}
else
{
// skip
WriteLog("Proses masih berjalan, menunggu proses selesai dijalankan...");
}
}
private void WriteLog(string msg)
{
var logpath = System.IO.Path.Combine(AppContext.BaseDirectory, $"{DateTime.Now:yyyyMMdd}-{DateTime.Now:HH}00.log");
System.IO.File.AppendAllText(logpath,$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {msg}\r\n" );
}
public Task StopAsync(CancellationToken cancellationToken)
{
timer.Stop();
timer.Enabled = false;
return Task.CompletedTask;
}
public void Dispose()
{
if (timer != null)
{
timer.Dispose();
timer = null;
}
}
}
}
Setelah selesai, tambahkan potongan kode di bagian Program.cs. Bagian ini intinya adalah menambahkan code AddHostedService agar supaya class JobWorker yang telah dibuat bisa teregistrasi sebagai background task yang dijalankan oleh ASP.NET Core MVC. Perhatikan listing kode di bawah pada baris 15-16.
// File: Program.cs
using WebApplicationEF.Worker;
namespace WebApplicationEF
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var services = builder.Services;
services.AddHostedService<JobWorker>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.MapStaticAssets();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.WithStaticAssets();
app.Run();
}
}
}
Coba jalankan project web tersebut di Visual Studio 2022 dan tunggu hingga aplikasi web sudah bisa dibuka.

Buka folder /bin/Debug/net9.0 dan cari file dengan ekstensi file .log seperti pada gambar di bawah


Nah mudah sekali kan untuk membuat Background Job Service yang dihosting di suatu web ASP.NET Core MVC. Untuk listing lengkapnya bisa diunduh di link berikut


