Skip to main content
All docs
V24.1

Convert an Existing Application into a Multi-Tenant Application

  • 6 minutes to read

Follow the steps below to convert an existing XAF application to a multi-tenant application.

Install Required Dependencies

Install the following NuGet packages:

SolutionName.Module (the shared module project)
EF Core: DevExpress.ExpressApp.MultiTenancy.EFCore.v24.1
XPO: DevExpress.ExpressApp.MultiTenancy.XPO.v24.1
SolutionName.Blazor.Server (the Blazor application project)
EF Core: DevExpress.ExpressApp.MultiTenancy.Blazor.EFCore.v24.1
XPO: DevExpress.ExpressApp.MultiTenancy.Blazor.XPO.v24.1
SolutionName.Win (the WinForms application project)
EF Core: DevExpress.ExpressApp.MultiTenancy.Win.EFCore.v24.1
XPO: DevExpress.ExpressApp.MultiTenancy.Win.XPO.v24.1
SolutionName.WebApi (the Web API Service project)
EF Core: DevExpress.ExpressApp.MultiTenancy.WebApi.EFCore.v24.1
XPO: DevExpress.ExpressApp.MultiTenancy.WebApi.Xpo.v24.1
SolutionName.MiddleTier (the Middle Tier Security project)
EF Core: DevExpress.ExpressApp.MultiTenancy.AspNetCore.EFCore.v24.1
XPO: DevExpress.ExpressApp.MultiTenancy.AspNetCore.Xpo.v24.1

Add a Connection String for the Host Database

Edit the application’s configuration file:

  • appsettings.json in Blazor, Web API Service, Middle Tier Security
  • App.config in WinForms

Add a connection string for the Host Database (named "ConnectionString" throughout this article).

{
  "ConnectionStrings": {
    "ConnectionString": "Integrated Security=SSPI;Pooling=true;MultipleActiveResultSets=true;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=MySolution_Service",
  },

Enable Multi-Tenancy Mode

In the application’s Startup.cs file, add code that enables and configures multi-tenancy mode:

EF Core

File:

  • MySolution.Blazor.Server/Startup.cs
  • MySolution.Win/Startup.cs
  • MySolution.WebApi/Startup.cs
  • MySolution.MiddleTier/Startup.cs
  • MySolution.Win/Startup.cs in Windows Forms applications with Integrated Mode
public class Startup { 
    public void ConfigureServices(IServiceCollection services) {
        services.AddXaf(Configuration, builder => {
            // ...
            builder.AddMultiTenancy() 
                .WithHostDbContext((sp, opt) => {
                    opt.UseSqlServer(Configuration.GetConnectionString("ConnectionString")); 
                    opt.UseLazyLoadingProxies(); 
                    opt.UseChangeTrackingProxies(); 
                })
                .WithTenantResolver<TenantByEmailResolver>(); 
             // ...
        });
    }
} 

XPO

File:

  • MySolution.Blazor.Server/Startup.cs
  • MySolution.Win/Startup.cs
  • MySolution.WebApi/Startup.cs
  • MySolution.MiddleTier/Startup.cs
  • MySolution.Win/Startup.cs in Windows Forms applications with Integrated Mode
public class Startup {
    public void ConfigureServices(IServiceCollection services) {
        services.AddXaf(Configuration, builder => {
            // ...
            builder.AddMultiTenancy()
                .WithHostDatabaseConnectionString(Configuration.GetConnectionString("ConnectionString"))
                .WithTenantResolver<TenantByEmailResolver>();
            // ...
        });
    }
} 

Modify Code that Configures ObjectSpaceProviders

In a multi-tenant application, the connection string for a database that stores tenant data is not static and changes based on the current tenant. To accommodate for this, make the following changes to methods that configure the application’s ObjectSpaceProviders:

  • Use the IConnectionStringProvider service to obtain the connection string instead of assigning the connection string as a static value.
  • In a WinForms application that uses EF Core, specify an additional parameter for created ObjectSpaceProviders: serviceLifeTime = ServiceLifeTime.Transient.

Below is an example of modified code used to configure ObjectSpaceProviders in a multi-tenant application.

File: MySolution.Blazor.Server/Startup.cs (MySolution.Win/Startup.cs)

.AddSecuredEFCore(options => options.PreFetchReferenceProperties())
    .WithDbContext<SolutionEFCoreDbContext>((application, options) => {
        string connectionString = application.ServiceProvider.GetRequiredService<IConnectionStringProvider>().GetConnectionString();
        options.UseSqlServer(connectionString);
        options.UseChangeTrackingProxies();
        options.UseObjectSpaceLinkProxies();
    }, ServiceLifetime.Transient)
.AddNonPersistent();

Modify the Module Updater Code

A multi-tenant application works with multiple databases that have separate purposes and store separate data sets. Consequently, code that updates a database must take into account what tenant is currently active and whether or not the application runs in Host User Interface mode. In most cases, you need to follow the rules below when you modify the Module Updater Code to use it in a multi-tenant application.

  • When you update the Host Database, do not create any business objects used in Tenant Databases.
  • When you update the Host Database, do not create user accounts with roles other than the Administrators role.
  • When you update a Tenant Database, specify user logins in the format from which the format can be determined. For more information, see: Tenant Resolvers.

To determine the configuration in which the application currently runs, you can use the following custom properties in your ModuleUpdater descendant:

File: MySolution.Module/DatabaseUpdate/Updater.cs

public class Updater : ModuleUpdater {
    // Returns the current tenant's unique identifier.
    // If `null`, the application runs in Host User Interface mode.
    Guid? TenantId {
        get {
            return ObjectSpace.ServiceProvider?.GetService<ITenantProvider>()?.TenantId;
        }
    }

    // Returns the current tenant's name.
    // If `null`, the application runs in Host User Interface mode.
    string TenantName {
        get {
            return ObjectSpace.ServiceProvider?.GetService<ITenantProvider>()?.TenantName;
        }
    }
}

The code sample below demonstrates how you can modify the UpdateDatabaseAfterUpdateSchema method implementation to use it in a multi-tenant application:

File: MySolution.Module/DatabaseUpdate/Updater.cs

public class Updater : ModuleUpdater {
    public override void UpdateDatabaseAfterUpdateSchema() {
        base.UpdateDatabaseAfterUpdateSchema();
        if (TenantId != null) {
            // Code that updates Tenant Databases
            if(TenantName == "company1.com") {
                // Code that updates the database for the tenant `company1.com`
            }
            // ...
        } else {
            // Code that updates the Host Database
            // ...
        }
    }
    // ...
}
See Also