.Net 6.0 Üzerinde Hangfire İmplementasyonu
Selamlar,
Bu makalede Hangfire’ı derinlemesine incelemek yerine, .Net 6.0 ile olan entegrasyonuna ve ilk başlayanlar için karşılaşılan sorunlara karşı, hap niteliğinde çözümlere yer verilecektir.
Şimdi gelelim Hangfire ne işe yarar sorusuna ?
Hangfire, arkada asenkron olarak çalışan, yani kullanıcıyı bekletmeyen (Background job) olarak tanımladığımız işler için kullanılan bir tooldur. Mutlaka bir depolama alanına, yani DB’ye ihtiyaç duyar. Depolama alanı olarak, birçok veritabanını destekler (SQL Server, MSMQ, Redis) gibi. Genellikle Time Schedule şeklinde tekrarlayan belli zaman aralıkları ile çalışan işlerde kullanılsalar da, birçok farklı kullanım şekilleri vardır.
Hangfire Çalışma Şekilleri:
- Fire & Forget: Bir kez çalışır ve biter. Hiç kullanma ihtiyacı duymadım :)
- Delayed: Yukarıdaki çalışma şekline benzeyen bir job çeşididir. Tek farkı, belirli bir sürenin sonunda, tek seferlik çalışan bir jobdır.
- Recurring: Tekrarlı olarak belirli bir zaman aralığında çalışacak olan bir job çeşididir. Bu makalenin esas değineceği job, budur.
- Continuations: Bir başka job’ın (scheduled) başarılı şekilde çalışmasından sonra, çalışacak olan job çeşididir.
Hangfire IIS üzerinde çalışan, bir JOB Schedule Tool’u dur.
Aslında IIS üzerinde belli zaman aralıkları ile çalışan yapılar, Cloud ile daha çok hayatımıza girmiştir. Çünkü, eskiden bu tarz işler için örneğin Windows ortamı için, Windows Servisler, daha sonra örneğin Azure ile cross platform çalışan, Worker Serviceler hayatımıza girdmiştir. Ama bu tarz servisler için, sanal bir sunucunun ayağı kaldırılması gerekmektedir. Bu da extra bir maliyete yol açtığı için, IIS üzerinde çalışan Quartz.NET, Hangfire gibi toolara ihtiya. duyulmuştur. Mesela Azure Cloud üzerinde de bunun karşılığı, “WebJobs” lardır. Makalesine buradan erişebilirsiniz.
Öncelikle, aşağıdaki gibi sıfırdan bir “HangfireTest” adında ASP .NET CORE Web API projesi yaratılır.
Hangfire’ın kullanılacağı .Net 6.0 projesine, aşağıdaki kütüphaneler eklenir.
Proje yaratıldığında otomatik gelen WeatherReport.cs sayfası, aşağıdaki gibi modifiye edilir:
“ReportWeather()” ve “ReportWeather2()” belli bir zaman aralığı ile Hangfire tarafından çalıştırılacak olan Dummy methodlardır. Belli zaman aralıkları ile, random olarak üretilen hava durumu sonuçları console’a yazmaktadırlar.
WeatherReport.cs:
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 |
using System.Diagnostics; namespace HangfireTest { public class WeatherReport : IWeatherReport { public static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; public void ReportWeather() { var array = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); foreach (var item in array) { Debug.Write(item.Date + " | "); Debug.Write(item.TemperatureC + " | "); Debug.WriteLine(item.Summary); Debug.WriteLine("".PadRight(40, '*')); } } public void ReportWeather2() { var array = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); foreach (var item in array) { //if (item.TemperatureC > 50) { throw new Exception("OverFlow Hottt!"); } Debug.Write("2." + item.Date + " | "); Debug.Write("2." + item.TemperatureC + " | "); Debug.WriteLine("2." + item.Summary); Debug.WriteLine("".PadRight(40, '*')); } } } } |
Console basılan WeatherForecast modeli, aşağıdaki gibidir.
WeatherForecast.cs:
1 2 3 4 5 6 7 8 9 10 |
namespace HangfireTest { public class WeatherForecast { public DateTime Date { get; set; } public int TemperatureC { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string? Summary { get; set; } } } |
Şimdi sıra geldi, ilgili methodların çalışma zamanlarının belirlenmesine:
RecurringJobs.cs: Aşağıda görüldüğü gibi, yukarıda tanımlanan “ReportWeather” ve “ReportWeather2” methodları, belirli zaman aralıkları ile çalıştırılmak için Hangfire’ın => RecurringJob Sınıfına eklenmektedir.
1-) Hangfire Genel Sorunlardan 1.nin Çözümü:
Sorun, çalışması belli bir zaman aralığında tekrar edilecek methodların, Hangfire’a birden fazla eklenmesi. Kısaca, aynı methodun Hangfire’da çoklaması.
Çözüm => “RecurringJob.RemoveIfExists(nameof(weather.ReportWeather))“: İlgili methodun ismi “nameof()” şeklinde alınıp “RemoveIfExists()” methodu ile, aynısı var ise kaldırılır.
- “RecurringJob.AddOrUpdate<IWeatherReport>(nameof(weather.ReportWeather), x =>” İlgili method interface’i ile “AddorUpdate()” methodu ile “RecurringJob” sınıfına “nameof()” methodu ile eklenir. Böylece daha sonra ilgili methodun zaten olup olmadığı, varsa kaldırılması gene bu “nameof()” methodu ile kontrol edilebilmektedir.
- Ayralanacak zamanlama için birçok farklı yöntem kullanılabilir. İlk örnekde “Cron.Daily(16, 15)” şeklinde hergün saat 16:15 çalışmak üzere => “ReportWeather()” methodu tanımlanmıştır.
- 2. örnekde “ReportWeather2()” methodu, “Cron.MinuteInterval(2)” tanımlaması ile her 2 dakkada bir çalışacak şekilde ayarlanmıştır.
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 |
using Hangfire; namespace HangfireTest { //https://crontab.guru/#15_14_1_*_* public static class RecurringJobs { [Obsolete] public static void GetHourlyWeatherReport() { WeatherReport weather = new(); RecurringJob.RemoveIfExists(nameof(weather.ReportWeather)); RecurringJob.RemoveIfExists(nameof(weather.ReportWeather2)); RecurringJob.RemoveIfExists(nameof(weather.ReportWeather2) + "copy"); RecurringJob.AddOrUpdate<IWeatherReport>(nameof(weather.ReportWeather), x => x.ReportWeather(), Cron.Daily(16, 15), TimeZoneInfo.FindSystemTimeZoneById("Turkey Standard Time")); //16:15 RecurringJob.AddOrUpdate<IWeatherReport>(nameof(weather.ReportWeather2), x => x.ReportWeather2(), Cron.MinuteInterval(2), TimeZoneInfo.FindSystemTimeZoneById("Turkey Standard Time"));//2 dakkada bir RecurringJob.AddOrUpdate<IWeatherReport>(nameof(weather.ReportWeather2) + "copy", x => x.ReportWeather2(), Cron.Daily(17, 02), TimeZoneInfo.FindSystemTimeZoneById("Turkey Standard Time"));//17:15 } } } |
2-) Hangfire Genel Sorunlardan 2.nin Çözümü:
Sorun Hangfire’da tanımlanan zamanın, gerçek zamandan 2 saat sonra gerçekleşmesi. Yani tanımlanan zamanın, Türkiye saatine uymaması.
Çözüm => “TimeZoneInfo.FindSystemTimeZoneById(“Turkey Standard Time”))” : Hangfire için çalışma zamanı, tanımlanan methodun TimeZone’ı, Türkiye için ayrıca tanımlanmalıdır.
Peki çok daha spesifik bir zaman aralığında, istenen methodun çalışması gerekir ise ? Size, şiddetle “https://crontab.guru” sitesini öneririm.
Örneğin : “RecurringJob.AddOrUpdate(() => weather.ReportWeather(), “5 4 * * sun”)” tanımlaması ile her pazar saat “4:05″‘de, ilgili methodun çalışması sağlanabilir. Bu tanımlamaya, “Cron” tanımlaması diyoruz. Ben bunu biraz, Regex’e benzetiyorum :)
Sıra geldi “Program.cs”‘de gerekli tanımların yapılarak, Hangfire’ın ayağa kaldırılmasına:
1-) Yazının başında bahsedildiği gibi, Hangfire bir DB’ye ihtiyaç duymaktadır. Benim tavsiyem, Hangfire’ı var olan projenin DB’sinde değil de, kendine özel ayrı bir DB’de çalıştırmanızdır. Aşağıda, local’de “Hangfire” adında yeni bir DB yaratılacak şekilde DB Connection, tanımlanmıştır. Ayrıca HangfireServer, servislere eklenmiştir.
Hangfire ile beraber otomatik oluşan MsSqlDB aşağıdaki gibidir.
Not: Bazen yetkiden dolayı, ilgili DB otomatik oluşmaya biliyor. Bu durumda, manuel olarak “Hangfire” adında ya da yukarıdaki config’de “initial catalog”‘da ne tanımlanır ise, o isimde bir DB yaratmanız gerekmektedir. Uygulama ayağı kaldırılınca Hangfire’a ait ilgili tablolar, otomatik oluşacaktır.
2-) İstenir ise Hangfire, browser üzerinden yönetilir ve monitor edilebilir. Aşağıda Hangfire ekranına erişim için gerekli routing ve güvenlik için “username”, “password” tanımlanmaktadır. Username ve Password config dosyasından alınmaktadır. Tanımlanan routing sayesinde, “https://localhost:7290/job” yazılarak Hangfire sayfasına erişilebilir.
appsettings.json:
1 2 3 4 5 6 7 8 9 |
{ . . "HangfireSettings": { "UserName": "admin", "Password": "123456" }, "AllowedHosts": "*" } |
İstenir ise, RecurringJob’a eklenmiş olan methodlar zamanından önce de, manuel ekran üzerinden trigger edilebilir.
3-) Hangfire Genel Sorunlardan 3.nün Çözümü:
Hangfire servisinin, otomatik olarak ayağı kaldırılması, yani zamanlıyıcılı background işlerin kurulması, nasıl olmaktadır ? Detayalı cevap, hemen aşağıdaki 3. Maddede mevcuttur.
3-) Bu örnekde, Hangfire için tanımlanan methodlar, RecurringJobs class’ında “GetHourlyWeatherReport()” methodu altında tanımlanmıştır. İlgili sınıfa ait tüm methodların belirli bir perioda göre ayağ kaldırılması, aşağıdaki gibi “RecurringJobs.GetHourlyWeatherReport(_coreService)” şeklinde çağrılması ile otomatik olarak gerçekleşmektedir.
-
“new AutomaticRetryAttribute { Attempts = 3 }” : Tanımlaması ile, ilgili method başarılı şekilde çalıştırılamaz ise, hata alınılmayıncaya kadar 3 defa tekrar edilmektedir.
Program.cs:
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 |
using Hangfire; using HangfireBasicAuthenticationFilter; using HangfireTest; using HangfireTest.Service; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //Hangfire builder.Services.AddHangfire(x => x.UseSqlServerStorage("Data Source=.;initial catalog=Hangfire;Trusted_Connection=True;")); builder.Services.AddHangfireServer(); builder.Services.AddSingleton<IWeatherReport, WeatherReport>(); builder.Services.AddTransient<ICoreService, CoreService>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); IConfiguration _configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); //app.UseHangfireDashboard(); //Install-Package Hangfire.Dashboard.Basic.Authentication //app.UseHangfireDashboard("/job", new DashboardOptions()); app.UseHangfireDashboard("/job", new DashboardOptions { Authorization = new[] { new HangfireCustomBasicAuthenticationFilter { User = _configuration.GetSection("HangfireSettings:UserName").Value, Pass = _configuration.GetSection("HangfireSettings:Password").Value } } }); app.UseHangfireServer(new BackgroundJobServerOptions()); GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3 }); RecurringJobs.GetHourlyWeatherReport(); app.Run(); |
*4-) Hangfire Genel Sorunlardan 4.nün Çözümü:
Peki bu çağrılan Hangfire methodlara, harici bir servis Inject’de etmek ister isek, bunu nasıl gerçekleştirebiliriz ?
Öncelikle gelin, solution’a “Service” adında yeni bir folder açıp “ICoreService“‘i aşağıdaki gibi oluşturalım.
Not: Gerçek bir projede, service katmanını ayrı bir Proje olarak yaratmanızda fayda var. Böylece, Solution’ın her yerinden ilgili servise erişebilirsiniz.
Service/ICoreService:
1 2 3 4 5 6 7 |
namespace HangfireTest.Service { public interface ICoreService { public bool ConvertNumberToText(int num, out string result); } } |
Service/CoreService: Amaç, girilen sayısal sıcaklık değerinin yukarıda da görüldüğü gibi yazı ile de, ekrana basılmasını sağlamaktır. Yani kısaca örnek amaçlı bu servisde, rakkam => yazıya dönüştürülmektedir.
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 |
namespace HangfireTest.Service { public class CoreService : ICoreService { public bool ToolConvertNumberToText(int num, out string text) { string[] ones = { "Bir", "Iki", "Uc", "Dort", "Bes", "Alti", "Yedi", "Sekiz", "Dokuz", "On", "Onbir", "Oniki", "Onuc", "Ondort", "Onbes", "Onaltı", "Onyedi", "Onsekiz", "Ondokuz", }; string[] tens = { "On", "Yirmi", "Otuz", "Kırk", "Elli", "Altmis", "Yetmis", "Seksen", "Doksan", "Yuz" }; string result = ""; text = ""; int single, tenss, hundreds; if (num > 1000) return false; hundreds = num / 100; num = num - hundreds * 100; if (num < 20) { tenss = 0; single = num; } else { tenss = num / 10; num = num - tenss * 10; single = num; } result = ""; if (hundreds > 0) { result += ones[hundreds - 1]; result += "Yuz"; } if (tenss > 0) { result += tens[tenss - 1]; } if (single > 0) { result += ones[single - 1]; } text = result; return true; } public bool ConvertNumberToText(int num, out string result) { string tempString = ""; int thousands; int temp; result = ""; if (num > 100000) { return false; } if (num < 0) { ToolConvertNumberToText(num * -1, out tempString); result += "-" + tempString; } if (num == 0) { result += "Sıfır"; } if (num < 1000 && num > 0) { ToolConvertNumberToText(num, out tempString); result += tempString; } else if (num > 0) { thousands = num / 1000; temp = num - thousands * 1000; ToolConvertNumberToText(thousands, out tempString); result += tempString; result += "Yuz"; ToolConvertNumberToText(temp, out tempString); result += tempString; } return true; } } } |
Peki şimdi bu servisi, Hangfire methodlarına nasıl Inject edeceğiz ?
1-) Program.cs: Öncelikle program.cs’e gelip ilgili “ICoreService”‘i ayağa kaldıralım. İlgili servisi başka yerde de kullanılabilsin diye, “Transient” olarak ayağı kaldırılır.
1 |
builder.Services.AddTransient<ICoreService, CoreService>(); |
Ayrıca program.cs altında aşağıdaki gibi serviceProvider oluşturlup, coreService bu provider ile ayağı kaldırılır. Son olarak da, “GetHourlyWeatherReport(_coreService)” constractor’ına parametre olarak verilir.
1 2 3 4 |
var serviceProvider = builder.Services.BuildServiceProvider(); var _coreService = serviceProvider.GetService<ICoreService>(); RecurringJobs.GetHourlyWeatherReport(_coreService); |
2-) RecurringJobs.cs: Constructor’da “ICoreService“‘i, parametre olarak beklemektedir. Böylece aşağıda görüldüğü çalışma zamanı belirlenecek ve “WeatherReport” sınıfına ait methodlar’a erişilirken, ICoreService’in kullanılması için => WeatherReport sınıfının constructor’ına, ilgili servis parametre olarak geçilecektir.
1 2 3 4 5 |
public static void GetHourlyWeatherReport(ICoreService service) { WeatherReport weather = new(service); . . |
3-) WeatherReport.cs: Aşağıda görüldüğü gibi “public WeatherReport(ICoreService service) { _service = service; }” constructor’ında, “ICoreSerices”‘i almıştır.
- “_service.ConvertNumberToText(item.TemperatureC, out string text)“: Sayısal olarak gelen “TemperatureC” => yazıya ilgili servis sayesinde çevrilmiştir.
- “Debug.Write(text + ” | “)“: Sıcaklığın yazı ile olan karşılığı, yeni bir kolon olarak console’a yazdırılmıştır.
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 |
using HangfireTest.Service; using System.Diagnostics; namespace HangfireTest { public class WeatherReport : IWeatherReport { ICoreService _service; public WeatherReport(ICoreService service) { _service = service; } public static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; public void ReportWeather() { var array = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); foreach (var item in array) { Debug.Write(item.Date + " | "); Debug.Write(item.TemperatureC + " | "); _service.ConvertNumberToText(item.TemperatureC, out string text); Debug.Write(text + " | "); Debug.WriteLine(item.Summary); Debug.WriteLine("".PadRight(40, '*')); } } public void ReportWeather2() { var array = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); foreach (var item in array) { //if (item.TemperatureC > 50) { throw new Exception("OverFlowe Hottt!"); } Debug.Write("2." + item.Date + " | "); Debug.Write(item.TemperatureC + " | "); _service.ConvertNumberToText(item.TemperatureC, out string text); Debug.Write(text + " | "); Debug.WriteLine("2." + item.Summary); Debug.WriteLine("".PadRight(40, '*')); } } } } |
Böylece, “ICoreservice” => “WeatherReport” içinde, aşağıdaki adımlardan geçerek kullanılabilmiştir.
Program.cs (ICoreService ayağı kaldırıldı) => GetHourlyWeatherReport(Constructor) => WeatherReport(Usage)
5-) Hangfire Genel Sorunlardan 5 .nin Çözümü:
Hangfire’ın zaman zaman durması, ya da IIS üzerinde hiçbir işlem olmadığı noktada, sleep’e düşmesi.
1-) IIS Üzerinde => Application Pools => .Net Core Project => Advanced Settings sekmesinden aşağıdaki ayarlar yapılır:
- Start Mode: “AlwaysRunning“
- Idle Time-out: 0
2-) IIS =>Sites altında Advanced Settings => “Preload Enable : True” şeklinde atanır.
Geldik bir makalenin daha sonuna. Bu makalede Hangfire’a hangi durumlarda ihtiyacımız olabileceğine, .Net 6.0 bir WebApi projesine nasıl entegre edilebileceğine ve karşılaşılabilecek bir takım temel sorunların üstesinden, nasıl gelinebileceğine değindik.
Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source Code: https://github.com/borakasmer/HangfireNet6
Kaynaklar:
- https://www.hangfire.io
- http://www.softwareandfinance.com/CSharp/Number_To_Text.html
- https://procodeguide.com/programming/hangfire-in-aspnet-core-schedule-jobs/
Bundan 3 ay önce implemente ettim hangfire’ı malesef elimde böyle bir kaynak yoktu ve bahsettiğiniz sorunların hepsini yaşadım neredeyse. İhtiyacı olanlar için mükemmel bir kaynak olmuş elinize emeğinize sağlık hocam
Teşekkür ederim Muhammed..
Bu yazınızın faydalı olduğuna o kadar çok eminim ki ama yazılıma başlayalı henüz 1 hafta oldu :)
Terimlere aşina olmak adına makaleler okumaya başladım ve bir hocam bize bu makalenizi paylaştı.
Eğitim sürecim boyunca makalelerinizin tamamını okumaya karar verdim. Sizin gibi başarılı bir developer olmayı çok istiyorum.
Rabbim size ve ailenize sağlıklı, huzurlu bir ömür versin. Kimseye muhtaç etmesin.
Teşekkürler Faruk,
Zaten önce konuyu anlamak için terimlere aşina olmak yani neden bahsettiğimizi bilmek lazım.
Bu yüzden de konuyu anlamasak dahi, sadece içinde geçen terimleri bile anlamak, yeni başlayan arkadaşlara büyük katkı sağlayacaktır.
Görüşmek üzere..
Eline sağlık bu güzel yazı için. “Hangfire IIS üzerinde çalışan, bir JOB Schedule Tool’u dur” ifadesi yanıltıcı olabilir. Sanki windows ortamı şart gibi bir algı oluşabilir.
İşleri her ne kadar da sıraya alsa da worker count ı sınırlamanız gerekebilir. birden fazla sitede host ediliyorsa görevi başka hostlara paylaştırıyor. localiniz de canlı sql bağlı ise görev sizin bilgisayarınıza da gelebilir. ayrıca hangfire, her verilen görevi işlemek zorunda da değil (bazı görevleri es geçebiliyor, görevi kesin yapacak diye bir şart yok, çok önemli bir görev tanımladıysanız görevin olup olmadığını ayrıca kontrol edin.)
IIS üzerinden projeyi yayınlarken , App Pool üzerinde Recycling ayarı var , Fixed Intervals 1740 minutes olarak ayarlı olabiliyor , o ayarı kapatmamız lazım yoksa üç günde 1 Hangfire sunucusu düşüyor.
bora hocam hangfire projesi ayrı normal backend projesi ayrı olacak sekılde bir yapı oldugunda backend business işlemlerinde hangfire jobs görev olusturmak için backend projesine de hangfire sunucu ayarları yapmak zorundamıyız yoksa client olarak jobs aktarımı yapabilir miyiz
Selamlar,
Hayır ayrı bir ayar yapmanıza gerek yok ama,Hangfire’ın çalıştıracağı backend projesini, Hangfire projesine referans olarak vermek zorundasın..
İyi çalışmalar.
Merhaba BOra hocam. .net 6 da CronJobService kullanıyorum. çok fazla job var. monitör tool arıyorum? var mı bildiğiniz?