TCIS.Hangfire.MultiTenancy 1.0.0

There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package TCIS.Hangfire.MultiTenancy --version 1.0.0
                    
NuGet\Install-Package TCIS.Hangfire.MultiTenancy -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.MultiTenancy" 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.MultiTenancy" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="TCIS.Hangfire.MultiTenancy" />
                    
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.MultiTenancy --version 1.0.0
                    
#r "nuget: TCIS.Hangfire.MultiTenancy, 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.MultiTenancy@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.MultiTenancy&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=TCIS.Hangfire.MultiTenancy&version=1.0.0
                    
Install as a Cake Tool

TCIS.Hangfire.MultiTenancy

Cho worker Hangfire tự nạp hồ sơ tenant từ tham số job wc:siteCode, để job chạy được trên tầng dữ liệu đa cảng của TCIS.Pluggable.

Gói cầu nối: nó biết cả hai bên, để không bên nào phải biết bên kia. TCIS.Hangfire giữ nguyên purity — chỉ tham chiếu TCIS.Core.

Mục lục

  1. Vấn đề nó giải
  2. Cài đặt
  3. Hành vi
  4. Bộ lọc chạy NGOÀI scope DI của job
  5. Job quét tất cả các cảng
  6. Những chỗ dễ vấp

1. Vấn đề nó giải

TWorkContextJobFilter khôi phục WorkContext — đủ để định tuyến nghiệp vụ theo cảng, nhưng không đủ để mở kết nối.

Tầng dữ liệu đọc một nguồn khác: TenantConnectionSource và TenantFilterPolicy đều lấy TenantInfo từ IMultiTenantContextAccessor — nơi chứa chuỗi kết nối và mức cách ly. Worker chạy trên thread nền, không đi qua MultiTenantMiddleware, nên không thành phần nào nạp nó.

Host đa cảng KHÔNG cài gói này
Job chạm database ném "connection string is missing for tenant 'Unknown'"
Job không mở kết nối TenantFilterPolicy fail-closed ⇒ truy vấn trả rỗng, job báo thành công, không ai biết

Vế thứ hai nguy hiểm hơn: hệ thống báo thành công sau khi không làm gì cả.


2. Cài đặt

dotnet add package TCIS.Hangfire.MultiTenancy
// 1. Tenant Store — nguồn sự thật.
//    KHÔNG cần strategy: mã cảng đã có sẵn trong tham số job.
builder.Services.AddTMultiTenant<TenantInfo>()
                .WithConfigurationStore();

// 2. Overload (sp, config) — bộ lọc cần IServiceProvider.
builder.Services.AddHangfire((sp, config) =>
{
    config.UseSqlServerStorage(connectionString);
    config.UseTWorkContext();             // ngữ cảnh nghiệp vụ  ← BẮT BUỘC
    config.UseTMultiTenantJobContext(sp); // hồ sơ tenant
});

builder.Services.AddHangfireServer();

Host đơn tenant không gọi dòng thứ hai, và không có gì đổi.

Quên UseTWorkContext() sẽ bị chặn với mã CONFIG_HANGFIRE_WORKCONTEXT_FILTER_MISSING.

Thiếu nó thì OnCreating không chạy, nên không job nào mang wc:siteCode. Bộ lọc tenant khi đó coi mọi job là job hệ thống — và vì RequireTenant mặc định false, nó không kêu một tiếng nào. Toàn bộ job đa cảng chạy không có cách ly. Đây là tổ hợp hỏng kín nhất của cặp gói này, nên nó được chặn tường minh.

Thứ tự gõ hai dòng không quan trọng. Thứ tự thực thi mới quan trọng, và nó được bảo đảm bằng Order: bộ lọc này đăng ký 100, UseTWorkContext() nhận DefaultOrder = -1 của Hangfire. Điều đó cần thiết vì TWorkContextJobFilter.OnPerforming gán toàn bộ WorkContext mới — chạy nó sau thì phần Tenant vừa nạp bị xoá sạch.

Gọi UseTMultiTenantJobContext nhiều lần là an toàn: lần sau thắng, không tạo bộ lọc trùng.


3. Hành vi

Job Kết quả
Không có wc:siteCode Job hệ thống — chạy bình thường
Có mã cảng, Store biết Nạp TenantInfo + phần Tenant của WorkContext
Có mã cảng, Store không biết Luôn từ chối (SEC_TENANT_NOT_FOUND), bất kể RequireTenant
Hồ sơ tenant khai thiếu IsolationLevel Từ chối (CONFIG_TENANT_ISOLATION_TIER_MISSING)
// Worker CHỈ phục vụ job của cảng — chặn luôn job không mang mã cảng:
config.UseTMultiTenantJobContext(sp, o => o.RequireTenant = true);

RequireTenant mặc định false — ngược với TCIS.EventBus.MultiTenancy.

Mặc định Vì sao
Hangfire false Job hệ thống (recurring lúc khởi động, job dọn dẹp) chạy với TenantId = null — đó là chuyện bình thường
EventBus true Mọi message đều xuất phát từ nghiệp vụ của một cảng; message không có mã cảng là bất thường

Nhưng "có mã cảng mà Store không biết" thì LUÔN bị từ chối ở cả hai bên. Bất đối xứng đó có chủ đích: không có mã cảng là trạng thái hợp lệ; mã cảng sai là lỗi cấu hình.

AutomaticRetryAttribute của Hangfire vẫn áp dụng cho ngoại lệ ném từ bộ lọc. Một mã cảng sai trong cấu hình sẽ bị thử lại theo chính sách retry của job — mà lỗi cấu hình thì không tự khỏi. Với job phụ thuộc tenant, cân nhắc [AutomaticRetry(Attempts = 0)].

Đây là điểm EventBus hơn: bên đó IsPermanentFailure nhận ra tiền tố SEC_/CONFIG_ và không thử lại. Hangfire giữ cơ chế retry ở ngoài tầm với của bộ lọc.


4. Bộ lọc chạy NGOÀI scope DI của job

Đ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)
Hệ quả
Bộ lọc không dùng được service Scoped Lúc nó chạy chưa có scope. Vì thế nó nhận root provider — hợp lệ, vì ResolveTenantAsync/ApplyTenant chỉ cần Singleton + AsyncLocal
Ngữ cảnh ghi trước khi scope tồn tại DbContext dựng trong scope đọc được chuỗi kết nối theo tenant
Dọn ngữ cảnh chạy sau khi scope dispose Tenant còn nguyên lúc DbContext đóng kết nối — an toàn hơn EventBus, nơi Cleanup() chạy trước lúc scope dispose

Cách ly giữa hai job dựa vào OnPerformed, KHÔNG dựa vào scope. Đây là khác biệt kiến trúc so với EventBus, nơi SubscribeInvoker tự mở một scope mỗi message và scope chính là ranh giới.

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 — không đăng ký bộ lọc thì không có gì dọn.


5. Job quét tất cả các cảng

Bộ lọc không giúp được ở đây: một job chỉ mang một mã cảng, hoặc không mang mã nào. Dùng trực tiếp cặp hàm của TCIS.MultiTenancy:

[AutomaticRetry(Attempts = 0)]
public async Task ExecuteAsync(CancellationToken ct)
{
    foreach (TenantInfo tenant in await tenantStore.GetAllAsync())
    {
        TenantInfo? profile = await serviceProvider.ResolveTenantAsync(tenant.Identifier, ct);

        // ĐỒNG BỘ. false ⇒ không phân giải được ⇒ bỏ qua cảng này, KHÔNG chạy với ngữ cảnh thiếu.
        if (!serviceProvider.ApplyTenant(profile)) continue;

        try     { await billingProcessor.ProcessDailyInvoiceAsync(ct); }
        catch   { /* log cho riêng cảng này, chạy tiếp cảng khác */ }
        finally { serviceProvider.ClearTenant(); }
    }
}

ApplyTenant phải đồng bộ: ghi AsyncLocal sau một await đã nhường luồng thì thay đổi không lan ra ngoài, và nó hỏng không tất định.


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

# Chỗ vấp Hậu quả
1 Cài TCIS.Pluggable nhưng quên gói này Truy vấn trả rỗng mà job vẫn báo thành công (§1)
2 Gọi UseTMultiTenantJobContext() mà quên UseTWorkContext() Bị chặn với CONFIG_HANGFIRE_WORKCONTEXT_FILTER_MISSING (§2)
3 Dùng overload AddHangfire(config => …) không có sp Không có IServiceProvider để truyền — đổi sang AddHangfire((sp, config) => …)
4 Trông đợi bộ lọc dùng được service Scoped Nó chạy ngoài scope của job (§4)
5 Để AutomaticRetry mặc định cho job phụ thuộc tenant Lỗi cấu hình bị thử lại 10 lần mà không tự khỏi (§3)
6 Mong bộ lọc lo được job quét mọi cảng Job chỉ mang một mã cảng — dùng mẫu ở §5

Chi tiết vận hành: md/14 §6. Bộ test đối chứng: TCIS.Hangfire.MultiTenancy.Tests (16 test, chạy qua BackgroundJobPerformer thật, không cần Docker).

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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1-rc.1 0 10/5/2026
1.0.0 62 10/1/2026
1.0.0-rc.52 40 9/29/2026
1.0.0-rc.51 59 9/23/2026
1.0.0-rc.50 54 9/21/2026
1.0.0-rc.49 61 9/18/2026
1.0.0-rc.47 63 9/17/2026
1.0.0-rc.46 58 9/17/2026
1.0.0-rc.45 69 9/11/2026
1.0.0-rc.44 65 9/10/2026
1.0.0-rc.43 67 9/10/2026
1.0.0-rc.42 71 9/9/2026
1.0.0-rc.41 65 9/9/2026
1.0.0-rc.40 64 9/8/2026
1.0.0-rc.39 67 9/3/2026
1.0.0-rc.38 62 9/3/2026
1.0.0-rc.37 77 8/27/2026
1.0.0-rc.36 91 8/27/2026
1.0.0-rc.35 65 8/26/2026
1.0.0-rc.34 70 8/26/2026
Loading failed