Roshan
Roshan

Reputation: 3344

TenantInfo returns null in finbuckle.multitenant with .NET Core

I am trying to configure multiple tenants in ASP.NET Core 8 MVC project.

I am using

My program.cs is shown here:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

// 2. Configure multi-tenancy.
builder.Services.AddMultiTenant<AppTenantInfo>()
    .WithEFCoreStore<TenantDbContext, AppTenantInfo>()
    .WithHostStrategy();

// 1. Register TenantDbContext.
builder.Services.AddDbContext<TenantDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("MultiTenantConnection")));


builder.Services.TryAddScoped<ITenantInfo>(sp =>
    sp.GetRequiredService<IMultiTenantContext<AppTenantInfo>>().TenantInfo);

// Optionally, add a fallback registration for ITenantInfo.
builder.Services.AddScoped<ITenantInfo>(sp =>
{
    // Try to resolve the multi-tenant context.
    var multiTenantContext = sp.GetService<IMultiTenantContext<AppTenantInfo>>();
    if (multiTenantContext?.TenantInfo != null)
    {
        return multiTenantContext.TenantInfo;
    }
    // Fallback: return a default tenant info(adjust as needed for your scenario).
    return new AppTenantInfo
    {
        Id = "default",
        Identifier = "default",
        Name = "Default Tenant",
        ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    };
});

//builder.Services.TryAddScoped<IMultiTenantContext<AppTenantInfo>, MultiTenantContext<AppTenantInfo>>();


// Configure the main application DbContext using the tenant-specific connection string.
builder.Services.AddDbContext<CliniqueDbContext>((serviceProvider, options) =>
{
    // Resolve the current tenant information.
    var tenantInfo = serviceProvider.GetRequiredService<ITenantInfo>() as AppTenantInfo;
    // Use the tenant's connection string (ensure it’s available in production scenarios).
    options.UseNpgsql(tenantInfo?.ConnectionString);
});

// 4. Register the Identity DbContext for authentication
builder.Services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
    var tenantInfo = serviceProvider.GetRequiredService<ITenantInfo>() as AppTenantInfo;
    // Here we use the tenant's connection string to store Identity data.
    options.UseNpgsql(tenantInfo?.ConnectionString);
});

// ...

var app = builder.Build();

// Enable multi-tenancy middleware.
app.UseMultiTenant();

I have 3 database contexts

TenantDbContext:
Running the initial migration for this context will create the database that stores tenant configuration (including connection strings).

CliniqueDbContext:
Migrations here will update your business data database (patients, appointments, etc.) for the current tenant.

ApplicationDbContext:
Migrations for this context will set up the Identity database for user authentication and roles.

I have configured host file and launchsetting.json to test multi-tenancy in development environment.

Host file:

127.0.0.1   cli1.clinique360.com
127.0.0.1   cli2.clinique360.com

lauchsettings.json:

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:46541",
      "sslPort": 44367
    }
  },
    "profiles": {
        "http": {
            "commandName": "Project",
            "dotnetRunMessages": true,
            "launchBrowser": true,
            "applicationUrl": "http://*:5146",
            "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            }
        },
        "https": {
            "commandName": "Project",
            "dotnetRunMessages": true,
            "launchBrowser": true,
            "applicationUrl": "https://localhost:7292;http://localhost:5146",
            "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            }
        },
        "IIS Express": {
            "commandName": "IISExpress",
            "launchBrowser": true,
            "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            }
        },
        "Clinique360.Web": {
            "commandName": "Project",
            "launchBrowser": true,
            "applicationUrl": "http://*:5000",
            "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            }
        }

    }
}

But when I run the application it returns null for multiTenantContext:

builder.Services.AddScoped<ITenantInfo>(sp =>
{
    // Try to resolve the multi-tenant context.
    var multiTenantContext = sp.GetService<IMultiTenantContext<AppTenantInfo>>();
    if (multiTenantContext?.TenantInfo != null)
    {
        return multiTenantContext.TenantInfo;
    }

    // Fallback: return a default tenant info(adjust as needed for your scenario).
    return new AppTenantInfo
    {
        Id = "default",
        Identifier = "default",
        Name = "Default Tenant",
        ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    };
});

What can be the issue?

Upvotes: 1

Views: 47

Answers (1)

SoftwareDveloper
SoftwareDveloper

Reputation: 742

From my understanding, ITenantInfo is not available for DI from version 7 onwards. As per finbuckle's version history page, (I)MultiTenantContext and (I)TenantInfo are no longer available via dependency injection. Use IMultiTenantContextAccessor instead. MultiTenantDbContext and MultiTenantIdentityDbContext will require a new constructor that injects IMultiTenantContextAccessor or IMultiTenantContext. Please see this link for more info: https://www.finbuckle.com/MultiTenant/Docs/v7.0.2/History?redirectedToLatest

Instead, try this:

    builder.Services.AddDbContext<TenantDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("MultiTenantConnection")));
    
    //when you register your appdbcontext, you don't have to specify connection strings or it's database provider; 
    
    builder.Services.AddDbContext<CliniqueDbContext>();
    builder.Services.AddDbContext<ApplicationDbContext>();
    
    builder.Services.AddMultiTenant<AppTenantInfo>()
        .WithEFCoreStore<TenantDbContext, AppTenantInfo>()
        .WithHostStrategy();
        //.WithStaticStrategy("default_tenant");
    
    var app = builder.Build();
    
    app.UseMultiTenant();
    app.Run();



In your CliniqueDbContext and AppDbContext, inject IMultiTenantContextAccessor like this to access TenantInfo and set the default:

        public class AppDbContext : DbContext
    {
        private readonly ITenantInfo? _tenant;
        private readonly IWebHostEnvironment _env;
        private readonly IConfiguration _config;
    
        public AppDbContext (
            DbContextOptions<AppDbContext> options,
            IWebHostEnvironment env,
            IMultiTenantContextAccessor multiTenantContextAccessor,
            IConfiguration config)
            : base(options)
        {
            _tenant = multiTenantContextAccessor.MultiTenantContext?.TenantInfo;
            _env = env;
            _config = config;
        }
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (_tenant is null)
            {
            _tenant = new AppTenantInfo(){
                Id = "default",
                    Identifier = "default",
                    Name = "Default Tenant",
                    ConnectionString = _config.GetConnectionString("DefaultConnection")
            };
            }        
            optionsBuilder.UseNpgsql(_tenant.ConnectionString);
    
            base.OnConfiguring(optionsBuilder);
        }
    }

Upvotes: 0

Related Questions