Skip to main content
All docs
V23.2

Convert an Existing Application into a Multi-Tenant Application

  • 4 minutes to read

Follow the steps described below to convert an existing XAF WinForms or Blazor (.NET 6+) application into a multi-tenant application. Note that not all application configurations support multi-tenancy. See Limitations for more information.

Install Required Dependencies

Install the following NuGet packages:

Project

Package

The shared module project (SolutionName.Module)

  • DevExpress.ExpressApp.MultiTenancy.EFCore.v23.2 (If you use EF Core)
  • DevExpress.ExpressApp.MultiTenancy.XPO.v23.2 (If you use XPO)

The Blazor application project (SolutionName.Blazor.Server)

  • DevExpress.ExpressApp.MultiTenancy.Blazor.EFCore.v23.2 (If you use EF Core)
  • DevExpress.ExpressApp.MultiTenancy.Blazor.XPO.v23.2 (If you use XPO)

The WinForms application project (SolutionName.Win)

  • DevExpress.ExpressApp.MultiTenancy.Win.EFCore.v23.2 (If you use EF Core)
  • DevExpress.ExpressApp.MultiTenancy.Win.XPO.v23.2 (If you use XPO)

Add a Connection String for the Host Database

In the application’s configuration file (appsettings.json in Blazor and App.config in WinForms), add a connection string for the Host Database (named "ConnectionString" throughout this article).

Enable Multi-Tenancy Mode

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

EF Core

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

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)

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