ekrem özer

her yerde olan şeyler.

.Net Core Hangfire Kullanımı

Merhaba arkadaşlar bu yazımda temel seviyede .net core uygulamalarında hangfire kullanımını anlatmaya çalışacağım. Hangfire arkaplan da çalışacak joblar oluşturup dashbord üzerinden yönetmenizi ve takip etmenizi sağlayan bir açık kaynak kodlu bir kütüphanedir. Aynı zamanda bu işlemleri zamanlanmış görevler olarakta oluşturabilirsiniz. Örneğin günlük, haftalık, aylık vs gibi periodik olarak çalışacak işlemlerinizde de kullanabilirsiniz. Kullanacağınız senaryoya göre Hangfire'da 2'si ücretli olmak üzere 6 adet job çeşiti varıdr. Bunlar;

  1. FireAndForget: Bir kereliğine çalışır ve tanımlamanın ardından hemen tetikledir. İleriki bir zamana ayarlanamaz.
  2. Delayed: Birkereliğine çalışır ve bu job'ı oluştururken çalışacağı zamanı ayarlayabilirsiniz.
  3. Recurring: Tekrarlı olarak çalışır ve çalışma periodunu siz belirlersiniz. Günlük, haftalık, aylık vs.
  4. Continuations: Başka bir job ile ilişkilendirilen job türüdür, bir job'ınız tetiklendikten sonra gelen jobId ile bu job türünü tetikleyebilirsiniz.
  5. Batch (Ücretli): Toplu işlemler için kullanılan birden fazla görevi yapabilen toplu job türüdür.
  6. Batch Continuations (Ücretli): Toplu halde çalışan job türünün tetiklenmesinden sonra çalışan job türüdür.

Job türlerinden bahsettikten sonra küçük bir uygulamayla nasıl kullanacağımızı inceleyelim. Ücertli olan job'lar dışında diğerlerine bir senaryo olmaksızın nasıl kullanıldığına değineceğim.

NetCoreLibraries adında bir blank solution açıp içerisine Hangfire.Web adında .net core mvc web application projesi ekliyorum. Sonra projeme nuget package manager üzerinden ilgili Hangfire kütüpanelerini ekliyorum.

Hangfire.Core
Hangfire.SqlServer
Hangfire.AspNetCore
Hangfire.Dashboard.Basic.Authentication

Yukarıdaki nugetları tek tek yüklemek yerine doğrudan Hangfire nuget'in de yükleyebilirsiniz. Ben bu şekilde kullanmayı tercih ettim. Hangfire çalışmak için kendi veritabanına ihtiyaç duyar ücretsiz versiyonu SqlServer ve In-Memory Db türlerini destekler. Biz projemizde SqlServer kullanacağız. Nugetlerı yükledikten sonra Sql Server üzerinden Hangfire adında bir veritabanı oluşturuyorum. Sonrasında appsettings.json dosyama veritabanı yolunu ve Hangfire dashboard'ına girmek için kullanacağım kullancı adı ve şifresini yazıyorum.

 "ConnectionStrings": {
    "Hangfire": "Server=(localdb)\\MSSQLLocalDB;Database=master;Trusted_Connection=True;Database=Hangfire;"
  },
  "HangfireSettings": {
    "UserName": "admin",
    "Password": "admin"
  }

Şimdi Program.cs dosyamdan gerekli tanımlamaları yapıyorum. .Net 6 ve öncesi sürümler için bu tanımlamaları Startup.cs dosyamızda yapıyorduk.

builder.Services.AddHangfire(config => config.UseSqlServerStorage(builder.Configuration.GetConnectionString("Hangfire")));
builder.Services.AddHangfireServer();

Yukarıdaki kodlarla Hangfire veri tabanı yolunu vererek projeme Hangfire kütüptanemi eklemiş oldum. Şimdide dashboard için gerekli tanımlamaları yapalım.

var hangfireUserName = builder.Configuration.GetSection("HangfireSettings:UserName").Value;
var hangfirePassword = builder.Configuration.GetSection("HangfireSettings:Password").Value;

app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    DashboardTitle = "ekremozer.com",
    Authorization = new[]
    {
        new HangfireCustomBasicAuthenticationFilter{
            User = hangfireUserName,
            Pass = hangfirePassword
        }
    }
});

GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 8 });

Yukarıdaki kodda ilk önce appsettings'den hangfireUserName ve hangfirePassword değerlerimi çektim. Sonra UseHangfireDashboard metoduya dashboardı projeme ekledim. Parametre olarak verdiğim "/hangfire" adresi hangfire panelinin çalışacağı dizini belirtiyor, DashboardOptions paremetleri ile de panelime dair çeşitli ayarları tanımlıyorum. DashboardTitle ile sayfa başlığını, Authorization ile de panel girişinde sorulacak kullanıcı adı ve şifreyi belirtiyorum.

Hangfire hata aldığı job'ı default olarak 10 kez dener, sonra fail'e düşürür. GlobalJobFilters.Filters.Add ile bu default değer istediğimiz tekrar sayısına göre set edebiliriz.

Hangfire'ı kullanmak için yapmamız gerekenler tamam, şimdi örnek kodlamalarımıza geçelim. Başta da belirttiğim gibi bir senaryo üzerinden gitmeyeceğim, kısaca çalışma mantığına değineceğim. Bu nedenle kodlar çalıştığı zaman sadece  Debug output'a yazı yazdıracağım. Bunun için örnek olması açısından projeme Services adına bir klasör ekleyip içerisine EmailService adında bir class ekliyorum.

using System.Diagnostics;

namespace Hangfire.Web.Services
{
    public class EmailService
    {
        public void SendEmail(string to, string subject, string body)
        {
            Debug.WriteLine($"{to} adresine mail gönderildi.", subject);
        }

        public static void StaticSendEmail(string to, string subject, string body)
        {
            Debug.WriteLine($"{to} adresine mail gönderildi.", subject);
        }
    }
}

Bu class'ın email gönderdiğini varsayarak hangfire joblarımı kodlayacağım. Projeme HingfireJobs adında bir klasör açıp içerisine her job türü için birer class ekliyorum.

Klasör ve class yapım yukarıdaki gibi oldu. Şimdi sırasıyla job classlarını kodlayalım. İlk olarak FireAndForgetJobs classını inceliyoruz.

public static class FireAndForgetJobs
{
	[AutomaticRetry(Attempts = 5)]
	public static string Example()
	{
		//Example:1
		var jobId = BackgroundJob.Enqueue<EmailService>(x => x.SendEmail("ekrem@mail.com", "Hangfire Fire And Forget Job", "Example"));

		//Example:2
		var jobId2 = BackgroundJob.Enqueue(() => EmailService.StaticSendEmail("ekrem@mail.com", "Hangfire Fire And Forget Job", "Example"));

		return jobId;
	}
}

Hangfire'da job'a metod eklemenin iki farklı türü var. birincisi ilk örnekte olduğu gibi generic yapıda kullanıp lambda ile direk classın içerisindeki fonksiyonlara erişirsiniz, ikinciside fonksiyonu direkt çağırıp içerisinde fonksiyonu çağırmak, ikinci yöntemde fonksiyona erişebilmek için fonksiyonun static olması gerekiyor. İlkinde böyle bir zorunluluk yok ancak classın içinde constructor metodda bir değişken üretiyorsanız program.cs de servis olarak eklemeniz gerekiyor. Fire And Forget Job yukarıda bahsettiğimiz gibi çağırıldığında hemen çalışır, bu nedenle BackgroundJob.Enqueue ile yukarıdaki gibi tanımlamak yeterlidir. AutomaticRetry attiribute'u ile Program.cs de global olarak set ettiğimiz hata alınca tekrar deneme sayısını burada metodun özelinde set edebiliyoruz. Hiç bir şey ayarlamaksak default olarak 10 kabul edilir.

DelayedJobs class'ı

public class DelayedJobs
{
	public static string Example()
	{
		//Example:1
		var jobId = BackgroundJob.Schedule<EmailService>(x => x.SendEmail("ekrem@mail.com", "Hangfire Delayed Job", "Example"), TimeSpan.FromSeconds(5));

		//Example:2
		var jobId2 = BackgroundJob.Schedule(() => EmailService.StaticSendEmail("ekrem@mail.com", "Hangfire Delayed Job", "Example"), TimeSpan.FromSeconds(5));

		return jobId;
	}
}

BackgroundJob.Schedule metoduyla kullanılır, Fire And Forget'den farklı olarak delay parametresi alır, bu parametrede job'ın ne kadar süre sonra tetikleneceğini belirler. Yukarıdaki örnekte ben 5 saniye olarak ayarladım.

ContinuationsJobs class'ı

public class ContinuationsJobs
{
	public static void Example()
	{
		var jobId = FireAndForgetJobs.Example();

		//Example:1
		var jobId2 = BackgroundJob.ContinueJobWith(jobId, () => Debug.WriteLine($"{jobId} Id'li job çalıştı."));

		//Example:2
		var jobId3 = BackgroundJob.ContinueJobWith<EmailService>(jobId, x=>x.SendEmail("ekrem@mail.com", "Hangfire Continuations Job", "Example"));
	}
}

BackgroundJob.ContinueJobWith metoduyla kullanılır, parametre olarak hangi jobdan sonra çalışacaksa o job'ın Id'sini alır.

HingfireJobs class'ı

public class RecurringJobs
{
	public static void Example()
	{
		//Example:1
		RecurringJob.AddOrUpdate<EmailService>("RecurringJob-1", x => x.SendEmail("ekrem@mail.com", "Recurring Job", "Example"), Cron.Minutely);

		//Example:2
		RecurringJob.AddOrUpdate("RecurringJob-2", () => EmailService.StaticSendEmail("ekrem@mail.com", "Recurring Job", "Example"), Cron.Minutely);
	}
}

Recurring Job'lar belirlenen periyota göre sürekli çalışır, parametre olarak unique olarak verdiğimiz bir job'ıd, çalışacak metod be cron formatında job'ın çalışma periyotu. Bu job'ı RecurringJob.AddOrUpdate metoduyla ekliyoruz, verdiğimiz job'ıd de bir job varsa güncelliyor yoksa yenisini ekliyor. Cron hakkında detaylı bilgiyi https://en.wikipedia.org/wiki/Cron bu adresten alabilirsiniz. Hangfire'ın bizim için hazırlamış olduğu bir Cron sınıfı var;

Bu sınıf ile istediğimiz periyotta çalışacak joblar oluşturabiliriz. Ancak bu class'ında yetersiz kaldığı kısımlarda çok spesipik zaman dilimine ait cronları cronmaker.com üzerinden oluşturup oradan alacağınız Cron format'ı yukarıdaki örnekte Cron.Minutely'ye denk gelen parametre yerine kullanabilirsiniz. Siteyi incelemek faydalı olur diye düşünüyorum elimden geldiğince siteyi nasıl kullanacağımızı anlatmaya çalışayım.

Dakikalık Cron Üretimi

Sitenin ilk tabında job'ın kaç dakikada bir çalışacağını söyleyeceğiniz cron'u üretiyorsunuz.

Kırmızı ile işaretlediğim kısmı kopyalayıp kullanabilirsiniz.

Saatlik Cron Üretimi

Sitenin ikinci tabında job'ın kaç saatte bir çalışacağını söyleyeceğiniz cron'u üretiyorsunuz.

Saatlik kısımda iki çeşit kullanım şekli var, birincisi Every ile başlayan kısım. Burada job'ın kaç saatte bir çalışacağını belirliyoruz. İkincisi Starts at ile başlayan kısım. Burada da job'ın saat kaçta başlayacağını belirliyoruz.

Günlük Cron Üretimi

Sitenin üçüncü tabında job'ın kaç günde bir çalışacağını söyleyeceğiniz cron'u üretiyorsunuz.

Burada da 2 çeşit kullanım şekli var. Birincisi hergün (Everyday), ikincisi de hafta içi(Every weekday). Starts at kısmından da günün hangi saatinde çalışacağını belirtiyorsunuz.

Haftalık Cron Üretimi

Sitenin dördüncü tabında job'ın haftanın hangi günleri çalışacağını söyleyeceğiniz cron'u üretiyorsunuz.

Bu kısımda da haftanın hangi günleri ve saat kaçta çalışacağını belirtiyorsunuz.

Aylık Cron Üretimi

Sitenin beşinci tabında job'ın kaç ayda bir ayın hangi gün ve saatinde çalışacağını söyleyeceğiniz cron'u üretiyorsunuz.

Burada da iki çeşit kullanım yöntemi var. Birincisinde ayın kaçıncı günü ve kaç ayda bir çalışacağını belirtiyorsunuz. İkincisinde ise ay içerisinde ki kaçıncı haftanın kaçıncı gününde ve kaç ayda bir çalıştığını belirtiyorsunuz. Yani yukarıdaki The First Monday of every 1 month(2) şunu ifade ediyor, her ayın ilk pazartesisi ayda bir çalışsın. Starts at ile de yine çalışacağı günün saatini belirtiyoruz.

Yıllık Cron Üretimi

Sitenin altıncı tabında job'ın yılın hangi ayında, ayın hangi gün ve saatinde çalışacağını söyleyeceğiniz cron'u üretiyorsunuz.

Burada da iki çeşit kullanım biçimi var. Birincisi hangi ayın hangi gününde çalışacağı. İkincisi ise hangi ayda, ayın kaçıncı haftasında ve haftanın hangi gününde çalışacağını belirtiyoruz. Starts at ile de yine çalışacağı günün saatini belirtiyoruz.

Örnek kodlarımızı ve cron yapısını anlattıktan sonra şimdi joblarımızı tetikleyerek hangfire panelini inceleyelim. Ben her job için HomeController'a birer action ekleyip link ile tetikleyeceğim. Actionları ekledikten sonra ana sayfama linkleri ekleyip tek tek tetikliyorum.

Linklere tıklayıp Degub output'umu kontrol ediyorum.

Görüldüğü gibi 4 job'ımda tetiklendi. Şimdi paneli inceleyelim.

Panelin ana sayfasında Canlı grafik ve Geçmiş grafik olarak çalışan joblarımı görebiliyorum. Örneğin canlı grafikte 15:37 de ben paneldeyken RecurringJob'ım 1 kez tetiklendi.

  1. İşler(Jobs) Sayfası: Blasla
    1. Sıradakiler(Enqueued): Sırada olan henüz tetiklenmemiş joblar.
    2. Planlananlar(Scheduled): İleri tarihli jobları gösterir.
    3. İşlenenler(Processing): O an işlemde olan job'ları gösterir.
    4. Başarılılar(Succeeded): Başarılı olarak tamamlanan jobları gösterir.
    5. Başarısızlar(Failed): Hata almış jobları gösterir.
    6. Silinenler(Deleted): Silinmiş jobları gösterir
    7. Beklenenler(Awaiting): Sırasını bekleyen jobları gösterir
  2. Yeniden Denenenler(Retries) Sayfası: Hataya düşmüş job'ların tekrar denendiği süre içerisinde bu sayfada kaçıncı kez denendiğini bu sayfada görebilirsiniz. 
  3. Tekrarlayan İşler(Recurring Jobs) Sayfası: Periyodik olarak tekrar tekrar çalışan joblarınızı bu sayfada görebilirsiniz. Tetiklenme zamanı gelmemiş bir job'ı manuel olarak tetikleyebilirsiniz.
  4. Sunucular(Servers) Sayfası: Hangfire'a tanımlanmış tüm sunucuların listesini buradan görebilirsiniz.

Bu makalede temel seviyede Hangfire'ı nasıl kullanırız onu anlatmaya çalıştım. Daha detaylı bilgi için bu adresi ziyaret edebilirsiniz. docs.hangfire.io

Projenin kaynak kodları: https://github.com/ekremozer/NetCoreLibraries