TCIS.Hangfire 1.0.0

dotnet add package TCIS.Hangfire --version 1.0.0
                    
NuGet\Install-Package TCIS.Hangfire -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TCIS.Hangfire" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TCIS.Hangfire" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="TCIS.Hangfire" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TCIS.Hangfire --version 1.0.0
                    
#r "nuget: TCIS.Hangfire, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TCIS.Hangfire@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TCIS.Hangfire&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=TCIS.Hangfire&version=1.0.0
                    
Install as a Cake Tool

TCIS.Hangfire

Đưa IWorkContext đi qua ranh giới job của Hangfire: bắt ngữ cảnh lúc enqueue, dựng lại lúc thực thi, dọn sạch sau đó. Kèm nối lại W3C trace để log của job nằm cùng một trace với request đã tạo ra nó.

Gói này không biết gì về multi-tenancy — xem §6.

Mục lục

  1. Đăng ký
  2. Ngữ cảnh đi qua ranh giới job như thế nào
  3. Hợp đồng trên dây: tham số wc:*
  4. CorrelationId đổi mỗi lần chạy — có chủ đích
  5. Nối lại trace
  6. Scope DI và cách ly giữa các job
  7. Job đa cảng
  8. Những chỗ dễ vấp

1. Đăng ký

dotnet add package TCIS.Hangfire
using Hangfire;
using TCIS.Hangfire.Extensions;

builder.Services.AddHangfire(config =>
{
    config.UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection"));
    config.UseTWorkContext();
});

builder.Services.AddHangfireServer();

Đổi danh tính mặc định của job hệ thống:

config.UseTWorkContext(o =>
{
    o.DefaultSystemUsername = "svc-billing";
    o.DefaultSystemCaller   = "hangfire-nightly";
});

Không cần DI. TWorkContextJobFilter đọc/ghi qua TWorkContextAccessor, mà lớp đó giữ ngữ cảnh trong một AsyncLocal static — mọi instance dùng chung một ô nhớ. Nhờ vậy bộ lọc dựng được bằng new và không cần service provider.


2. Ngữ cảnh đi qua ranh giới job như thế nào

Giai đoạn Hook Việc
Enqueue OnCreating Đọc WorkContext của luồng đang gọi, làm phẳng thành tham số wc:*. Không có WorkContext thì không ghi gì
Thực thi OnPerforming Dựng lại WorkContext từ tham số; không có tham số nào thì dựng ngữ cảnh hệ thống
Xong OnPerformed WorkContext = null, dispose Activity

Theo loại job:

Loại Ngữ cảnh khi chạy
Fire-and-forget, Delayed, Continuation Bản sao ngữ cảnh của luồng đã enqueue
Recurring đăng ký lúc khởi động Ngữ cảnh hệ thống (system-scheduler / hangfire-recurring)
Enqueue từ một job khác Ngữ cảnh của job cha, vì lúc đó WorkContext đang được nạp

Ngữ cảnh bị ĐÓNG BĂNG lúc enqueue. Một job nằm trong hàng đợi qua đêm sẽ chạy với tên người dùng, Roles, mã đối tác… đúng như thời điểm nó được tạo. Đó là hành vi đúng cho truy vết (ai đã yêu cầu việc này), nhưng sai nếu dùng để phân quyền — quyền có thể đã bị thu hồi. Job cần kiểm quyền thì phải đọc lại từ nguồn, đừng tin ngữ cảnh mang theo.


3. Hợp đồng trên dây: tham số wc:*

Hằng số ở JobParameterNames (public — gói cầu nối multi-tenancy đọc wc:siteCode). Tiền tố wc: để không đụng tham số nội bộ của Hangfire. Trường rỗng không được ghi.

Nhóm Tham số
Trace wc:correlationId, wc:traceParent
Tenant wc:tenantId, wc:siteCode
User wc:userId, wc:username, wc:fullName, wc:roles, wc:userIdentifier, wc:isMasterAccount
Client wc:caller, wc:clientId, wc:language
Business wc:agentCode, wc:linerCode, wc:partnerCode, wc:partnerType, wc:taxCode

Không đi qua ranh giới job (có chủ đích hoặc đã biết):

Trường Vì sao
Client.UserAgent, Client.Ip Thuộc về một request HTTP, không thuộc về job
Tenant.IsolationLevel Đã gỡ khỏi ITenantContext (14/08/2026) — mức cách ly là sự thật hạ tầng, worker tra Tenant Store bằng wc:siteCode
User.SubAccountIds ⚠️ Khoảng trống đã biết, chưa vá. Job nhận danh sách rỗng. Không hại nếu nghiệp vụ chưa dùng tới; nhưng nếu phân quyền dựa vào sub-account thì job sẽ nhìn thấy phạm vi hẹp hơn thực tế

4. CorrelationId đổi mỗi lần chạy — có chủ đích

OnPerforming luôn sinh CorrelationId mới, không dùng lại cái đã enqueue. Một job retry 5 lần cho ra 5 CorrelationId khác nhau — nhờ vậy log của từng lần chạy tách bạch được.

Sợi dây nối về nguồn nằm ở Client.Caller:

Caller = "hangfire | <correlation-id-lúc-enqueue>"     ← có ngữ cảnh khi enqueue
Caller = "hangfire-worker"                             ← không có

Đừng tra cứu xuyên ranh giới job bằng CorrelationId — nó không còn giống nhau. Dùng Caller, hoặc trace.id (§5), vốn thực sự xuyên suốt.


5. Nối lại trace

OnCreating ghi traceparent (W3C) của Activity đang chạy; OnPerforming dựng một Activity con từ đó và dispose ở OnPerformed. Kết quả: trace.id trong log của job trùng với request đã enqueue nó.

Không có traceparent (recurring job) thì vẫn khởi một Activity mới — để bản ghi trong lúc chạy có trace.id riêng, thay vì rỗng.


6. Scope DI và cách ly giữa các job

Gói này không tạo scope nào. Khác TCIS.EventBus, nơi SubscribeInvoker tự mở một scope cho mỗi message (await using var scope = _serviceProvider.CreateAsyncScope()), ở Hangfire scope do JobActivator của host tạo — và bộ lọc chạy ngoài nó.

Thứ tự thật, đo trên Hangfire 1.8.18 (ghim bằng test Run_Server_Filters_Outside_The_Job_Scope):

OnPerforming (order tăng dần)  →  scope.begin  →  thân job  →  scope.dispose  →  OnPerformed (ngược lại)

Ba hệ quả:

Bộ lọc không dùng được service Scoped Lúc nó chạy chưa có scope nào. Đây là lý do TenantJobFilter nhận root provider
Ngữ cảnh được ghi trước khi scope tồn tại Nên mọi service dựng trong scope (DbContext chọn chuỗi kết nối theo tenant) đều đọc được — cùng ý đồ với "chuẩn bị ngữ cảnh trước GetInstance" của EventBus
Dọn ngữ cảnh chạy sau khi scope đã dispose Tenant vẫn còn nguyên trong lúc DbContext đóng kết nối. Ở điểm này Hangfire an toàn hơn EventBus, nơi Cleanup() chạy trước lúc scope được dispose

Cách ly giữa hai job dựa vào OnPerformed, không dựa vào scope. Worker Hangfire là luồng sống lâu, xử lý job này tới job khác. Ngữ cảnh nằm trong AsyncLocal, và thứ duy nhất xoá nó là OnPerformed của bộ lọc. Không đăng ký bộ lọc thì không có gì dọn — job sau sẽ chạy với ngữ cảnh của job trước.

EventBus không có rủi ro này vì scope của nó là ranh giới. Ở đây ranh giới là bộ lọc.

Ghim bằng test Not_Leak_The_Tenant_From_One_Job_To_The_Next: hai job chạy nối tiếp trên cùng một luồng, job thứ hai không mang mã cảng và phải thấy trống.

Scope có được tạo hay không là việc của host, không phải của gói này. TCIS.Hangfire chỉ tham chiếu Hangfire.Core, mà Hangfire.Core dùng JobActivator mặc định — nó gọi Activator.CreateInstance, không có DI và không có scope. Muốn có scope một-job-một-scope thì host phải dùng AddHangfireServer() của Hangfire.NetCore/Hangfire.AspNetCore, vốn cài AspNetCoreJobActivator.


7. Job đa cảng

UseTWorkContext() một mình là KHÔNG đủ cho tầng dữ liệu multi-tenant. Nó dựng lại ngữ cảnh nghiệp vụ (SiteCode, TenantId, user) nhưng không dựng hồ sơ tenant — chuỗi kết nối và mức cách ly. Hai thứ đó nằm trong TenantInfo, mà TenantConnectionSource đọc từ IMultiTenantContextAccessor; worker chạy trên thread nền và không bao giờ đi qua MultiTenantMiddleware.

Triệu chứng: job chạm database ném "connection string is missing for tenant 'Unknown'". Job không mở kết nối thì tệ hơn — TenantFilterPolicy fail-closed, truy vấn trả rỗng, và job báo thành công sau khi không làm gì cả.

Thêm gói cầu nối, không phải sửa dòng nào trong job:

dotnet add package TCIS.Hangfire.MultiTenancy
builder.Services.AddTMultiTenant<TenantInfo>().WithConfigurationStore();

builder.Services.AddHangfire((sp, config) =>
{
    config.UseSqlServerStorage(connectionString);
    config.UseTWorkContext();             // ngữ cảnh nghiệp vụ
    config.UseTMultiTenantJobContext(sp); // hồ sơ tenant
});

Thứ tự thực thi là điều kiện đúng đắn, không phải thẩm mỹ: OnPerforming của gói này gán toàn bộ WorkContext mới, nên bộ lọc tenant chạy trước sẽ bị xoá sạch phần vừa nạp — hỏng im lặng, chỉ lộ ra khi job đầu tiên chạm database. Bảo đảm bằng Order: bộ lọc tenant đăng ký 100, còn UseTWorkContext() nhận DefaultOrder = -1 của Hangfire. Gõ hai dòng theo thứ tự nào cũng đúng.

Ứng dụng đơn tenant đơn giản là không gọi dòng thứ hai, và không có gì đổi.

Gọi UseTMultiTenantJobContext() mà quên UseTWorkContext() sẽ bị chặn, với mã CONFIG_HANGFIRE_WORKCONTEXT_FILTER_MISSING. Thiếu bộ lọc nền thì không job nào mang wc:siteCode, nên mọi job bị coi là job hệ thống và chạy không có cách ly — im lặng, vì RequireTenant mặc định false. Đó là tổ hợp hỏng kín nhất của cặp gói này.

Job phải quét tất cả cảng (bộ lọc không giúp được, vì job chỉ mang một mã cảng hoặc không mang mã nào) — xem md/14 §5 Cách 2.


8. Những chỗ dễ vấp

# Chỗ vấp Hậu quả
1 Tra cứu xuyên ranh giới job bằng CorrelationId Không khớp — nó được sinh mới mỗi lần chạy (§4)
2 Dùng ngữ cảnh mang theo để phân quyền Nó đóng băng từ lúc enqueue; quyền có thể đã bị thu hồi (§2)
3 Gọi UseTWorkContext() nhiều lần GlobalJobFilters không khử trùng lặp — bộ lọc chạy hai lần, và Activity của lần đầu bị ghi đè nên không được dispose
4 Chờ job đa cảng chạy đúng chỉ với UseTWorkContext() Truy vấn trả rỗng mà job vẫn báo thành công (§7)
5 Trông đợi SubAccountIds có mặt trong job Nó không được truyền — job nhận danh sách rỗng (§3)
6 Trông đợi bộ lọc dùng được service Scoped Bộ lọc chạy ngoài scope của job; lúc đó chưa có scope nào (§6)
7 Bỏ UseTWorkContext() vì "job không cần WorkContext" Không còn gì dọn AsyncLocal — ngữ cảnh job trước dính sang job sau (§6). Nếu có dùng gói multi-tenancy thì job bị chặn với CONFIG_HANGFIRE_WORKCONTEXT_FILTER_MISSING (§7)
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on TCIS.Hangfire:

Package Downloads
TCIS.Hangfire.MultiTenancy

TCIS Core Framework is an application framework for building modular, multi-tenant applications on ASP.NET Core. Multi-tenant context activation for TCIS Hangfire jobs.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 48 10/1/2026
1.0.0-rc.52 42 9/29/2026
1.0.0-rc.51 59 9/23/2026
1.0.0-rc.50 66 9/21/2026
1.0.0-rc.49 62 9/18/2026
1.0.0-rc.47 64 9/17/2026
1.0.0-rc.46 62 9/17/2026
1.0.0-rc.45 69 9/11/2026
1.0.0-rc.44 74 9/10/2026
1.0.0-rc.42 75 9/9/2026
1.0.0-rc.41 65 9/9/2026
1.0.0-rc.40 78 9/8/2026
1.0.0-rc.39 71 9/3/2026
1.0.0-rc.38 67 9/3/2026
1.0.0-rc.37 73 8/27/2026
1.0.0-rc.36 81 8/27/2026
1.0.0-rc.35 65 8/26/2026
1.0.0-rc.34 68 8/26/2026
1.0.0-rc.33 79 8/21/2026
1.0.0-rc.32 71 8/21/2026
Loading failed