← Back to list

.NET İle Hangfire’da Recurring Job’ları Duraklatma ve Devam Ettirme

Web uygulamalarının temel mantığı istek bazlıdır. Bir butona tıklarsın, uygulama yanıt verir. Ama bazen her gün 09:00'da çalışan bir rapor…

İbrahim Çakar · 2026-03-31 15:36 · 6 claps · 3.7 min read
#hangfire #dotnet #background-jobs
Open on Medium ↗
Wiki topics: 🧘 · Spirituality

.NET İle Hangfire’da Recurring Job’ları Duraklatma ve Devam Ettirme

Web uygulamalarının temel mantığı istek bazlıdır. Bir butona tıklarsın, uygulama yanıt verir. Ama bazen her gün 09:00'da çalışan bir rapor, 10 dakikada bir kontrol eden bir senkronizasyon işi gibi arka planda çalışan ihtiyaçlar da oluyor. Bu tür işler için .NET dünyasında yaygın tercihlerinden biri Hangfire. Hangfire’a yeni başlıyorsanız önce şu kaynaklara göz atmanızı öneririm:

Bu yazıda ise Hangfire’ın yıllardır eksik bıraktığı bir özelliği ele alacağım: job’ları silmeden duraklatmak ve devam ettirmek.

Pause/Resume Özelliği Olmaması

Hangfire bu kadar kullanışlı özelliklere sahip olmasına rağmen yıllardır istenen bir özellik yok: Job’ları duraklatma ve devam ettirme.

Diyelim ki bir job’unuz var ve geçici olarak durdurmak istiyorsunuz. Hangfire’da bunu yapmanın tek yolu job’u silmek:

Arayüzden seçilen Job’un silinmesi.

Arayüzden seçilen Job’un silinmesi.

Ama bu yaklaşımın ciddi sorunları var:

  • Job history’si kayboluyor

  • Dashboard’dan job tamamen kayboluyor

  • Job’u tekrar ayağa kaldırmak istediğinizde Pod restart etmek gerekiyor

Ben de projelerimde bu sorunu yaşadım ve aşağıdaki çözümü geliştirdim.

Çözüm: IServerFilter ile Merkezi Kontrol

Hangfire’ın **IServerFilter** interface’i, her job çalışmadan önce araya girmemizi sağlar. Bu sayede:

  • Job schedule’da kalır

  • Pod’u restart etmeye gerek kalmaz

  • History kaybolmaz

  • Runtime’da pause/resume yapılabilir

  • Hiçbir job koduna dokunmaya gerek yok

Kurulum

Adım 1: Job Status Service

İlk olarak job durumlarını yönetecek bir servis oluşturuyoruz. Bu servis hangi job’ların aktif, hangilerinin durdurulmuş olduğunu takip edecek:

public interface IJobStatusService
{
    bool IsJobEnabled(string jobId);
    void EnableJob(string jobId);
    void DisableJob(string jobId);
    Dictionary<string, bool> GetAllJobStatuses();
}

public class JobStatusService : IJobStatusService
{
    // Thread-safe dictionary - Hangfire birden fazla thread kullanır
    private static readonly ConcurrentDictionary<string, bool> _jobStatuses = new();

    public bool IsJobEnabled(string jobId)
    {
        // Varsayılan olarak tüm job'lar aktif
        return _jobStatuses.GetOrAdd(jobId, true);
    }

    public void EnableJob(string jobId)
    {
        _jobStatuses[jobId] = true;
    }

    public void DisableJob(string jobId)
    {
        _jobStatuses[jobId] = false;
    }

    public Dictionary<string, bool> GetAllJobStatuses()
    {
        return new Dictionary<string, bool>(_jobStatuses);
    }
}

// Bu yaklaşım ideal değildir ancak Hangfire filter'larında DI kullanımı sınırlı olduğu için tercih edilmiştir.
// Production ortamında custom activator gibi alternatifler değerlendirilebilir.
public static class JobStatusServiceInstance
{
    public static IJobStatusService? Current { get; set; }
}

ConcurrentDictionary kullanmamızın sebebi Hangfire’ın birden fazla worker thread’i ile çalışması. Birden fazla worker aynı anda bu dictionary’e erişebileceği için, çakışmaları önleyen güvenli bir koleksiyon kullanmamız gerekiyor.

Adım 2: JobPauseFilter

Gelelim asıl önemli kısma - Hangfire filter. Her job tetiklenmeden önce devreye girip job'un durumunu kontrol ediyor:

public class JobPauseFilter : JobFilterAttribute, IServerFilter
{
    public void OnPerforming(PerformingContext filterContext)
    {
        // Recurring job Id'sini alıyoruz
        // Hangfire bu parametreyi otomatik olarak ekliyor
        var jobId = filterContext.GetJobParameter<string>("RecurringJobId");

        var jobStatusService = JobStatusServiceInstance.Current;

        if (jobStatusService != null && !jobStatusService.IsJobEnabled(jobId))
        {
            // Job disabled - çalışmayı iptal ediyor
            filterContext.Canceled = true;

            Console.WriteLine($"[JobPauseFilter] {jobId} skipped at {DateTime.UtcNow}");
        }
    }
}

Burada kritik nokta **RecurringJobId** parametresi. Bu Id, RecurringJob.AddOrUpdate() çağrısında verdiğiniz Id ile aynıdır:

RecurringJob.AddOrUpdate<MyJob>(
    "MyJobId",  // ◄── Bu Id filter'da kullanılıyor
    job => job.Process(), 
    "*/5 * * * *"
);

Adım 3: Program.cs Yapılandırması

Hangfire kurulumuna ek olarak şu kod satarlarını ekliyoruz:


// JobStatusService'i singleton olarak kaydediyoruz
builder.Services.AddSingleton<IJobStatusService, JobStatusService>();

// Filter'ın servise erişebilmesi için static instance'a atıyoruz
JobStatusServiceInstance.Current = app.Services.GetRequiredService<IJobStatusService>();

// Filter'ı tüm job'lara uyguluyoruz
GlobalJobFilters.Filters.Add(new JobPauseFilter());

Adım 4: API Controller

Job’ları yönetmek için API endpoint’leri oluşturuyoruz:

[ApiController]
[Route("api/[controller]")]
public class JobManagementController : ControllerBase
{
    private readonly IJobStatusService _jobStatusService;

    public JobManagementController(IJobStatusService jobStatusService)
    {
        _jobStatusService = jobStatusService;
    }

    [HttpPost("pause/{jobId}")]
    public IActionResult PauseJob(string jobId)
    {
        _jobStatusService.DisableJob(jobId);
        return Ok(new { message = $"{jobId} paused" });
    }

    [HttpPost("resume/{jobId}")]
    public IActionResult ResumeJob(string jobId)
    {
        _jobStatusService.EnableJob(jobId);
        return Ok(new { message = $"{jobId} resumed" });
    }

    [HttpGet("list")]
    public IActionResult ListJobs()
    {
        using var connection = JobStorage.Current.GetConnection();
        var recurringJobs = connection.GetRecurringJobs();

        var jobList = recurringJobs.Select(job => new
        {
            Id = job.Id,
            Cron = job.Cron,
            NextExecution = job.NextExecution,
            LastExecution = job.LastExecution,
            IsEnabled = _jobStatusService.IsJobEnabled(job.Id)
        });

        return Ok(jobList);
    }
}

Test Edelim

Uygulamayı çalıştırdıktan sonra API’yi test edebilirsiniz: Postman, Swagger veya curl kullanabilirsiniz:

# Tüm job'ları listele
curl http://localhost:5000/api/jobmanagement/list

# Job'u duraklat
curl -X POST http://localhost:5000/api/jobmanagement/pause/MyJobId

# Job'u devam ettir
curl -X POST http://localhost:5000/api/jobmanagement/resume/MyJobId

Örnek çıktısının böyle bir şekilde olmasını bekliyoruz:

{
  "totalJobs": 3,
  "activeJobs": 2,
  "pausedJobs": 1,
  "jobs": [
    {
      "id": "SampleJob",
      "cron": "*/1 * * * *",
      "isEnabled": false,
      "status": "Paused"
    },
    {
      "id": "ReportJob", 
      "cron": "*/2 * * * *",
      "isEnabled": true,
      "status": "Active"
    }
  ]
}

Web UI ile Job Yönetimi

API endpoint’leri güzel ama rahatlık ve okunabilirlik için görsel bir arayüz çok daha kullanışlı. Şimdi job’ları yönetmek için bir web arayüzü oluşturalım.

MVC Desteği Ekleme

Öncelikle Program.cs’de MVC view desteğini aktif ediyoruz:

// AddControllers yerine AddControllersWithViews kullanıyoruz
builder.Services.AddControllersWithViews();

// ...

// MapControllers'dan sonra MVC route ekliyoruz
app.MapControllers();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Jobs}/{action=Index}/{id?}");

## **UI Controller**

API controller’dan ayrı bir MVC controller oluşturuyoruz:

// Controllers/JobsController.cs public class JobsController : Controller { [HttpGet] public IActionResult Index() { return View("~/Views/JobManagement/Index.cshtml"); } }



## **View**

`[Views/JobManagement/Index.cshtml](https://github.com/ibrahimcakar/HangfirePauseDemo/blob/main/Views/JobManagement/Index.cshtml)` dosyasında modern bir arayüz oluşturuyoruz. Bu arayüz:

- Tümü / Aktif / Durdurulmuş job’ları filtreleme

- Çoklu seçim ile toplu durdurma/başlatma/silme

- Her job için detaylı bilgiler

View dosyasının tam içeriğini GitHub repom’da bulabilirsiniz.

![Job yönetim arayüzü](https://miro.medium.com/v2/resize:fit:955/1*HZ1yH7VCh2Pnfm_RSga5-w.png)

*Job yönetim arayüzü*

## **Erişim**

Uygulamayı çalıştırdıktan sonra:

- **Job Yönetim UI:** `http://localhost:5117/Jobs`

- **Hangfire Dashboard: **`http://localhost:5117/hangfire`

- **API Endpoints:** `http://localhost:5117/api/JobManagement/list`

## **Sonuç**

Hangfire’ın eksik bıraktığı pause/resume özelliğini IServerFilter ile basitçe ekledik. Job kodlarına dokunmadan, merkezi bir çözüm. Projenin tamamını GitHub’da bulabilirsiniz. Sorularınız varsa yorumlarda yazabilirsiniz.

## Kaynak Kod

[https://github.com/ibrahimcakar/HangfirePauseDemo](https://github.com/ibrahimcakar/HangfirePauseDemo)

## Kaynaklar

[https://medium.com/r?url=https%3A%2F%2Fwww.gencayyildiz.com%2Fblog%2Fasp-net-coreda-hangfire-kutuphanesi-ile-zamanlanmis-gorevler-olusturma%2F](https://medium.com/r?url=https%3A%2F%2Fwww.gencayyildiz.com%2Fblog%2Fasp-net-coreda-hangfire-kutuphanesi-ile-zamanlanmis-gorevler-olusturma%2F)

[https://alperenalp.medium.com/hangfire-nedir-ve-asp-net-coreda-hangfire-kullan%C4%B1m%C4%B1-f6329b3a1c57](https://alperenalp.medium.com/hangfire-nedir-ve-asp-net-coreda-hangfire-kullan%C4%B1m%C4%B1-f6329b3a1c57)

메타데이터
post_id
bb1b46e4f860
slug
net-i̇le-hangfireda-recurring-job-ları-duraklatma-ve-devam-ettirme-bb1b46e4f860
url
https://medium.com/@ibrahimcakar/net-i%CC%87le-hangfireda-recurring-job-lar%C4%B1-duraklatma-ve-devam-ettirme-bb1b46e4f860
canonical_url
https://medium.com/@ibrahimcakar/net-i%CC%87le-hangfireda-recurring-job-lar%C4%B1-duraklatma-ve-devam-ettirme-bb1b46e4f860
author_url
https://medium.com/@ibrahimcakar
status
ok
fetched_at
2026-07-13 06:23:13