Base solution for your next web application
Open Closed

Accessing Tenancy Name Other Than for Resolving Database Connection Strings #11442


User avatar
0
michael.pear created

I have been switching to using multitenancy, and have successfully converted my database connection string resolver to look up a connection string in the app configuration based on the tenant name. I introduced code for tenant atabases (recognized by DbContextConcreteType with my tenant db context) :

        private readonly ITenantCache _tenantCache;
        private readonly ICurrentUnitOfWorkProvider _unitOfWorkProvider;
           ....
             if (args["DbContextConcreteType"] as Type == typeof(TrainingManagerDbContext))
            {
                string tenantName = String.Empty;
                string tenantConnectionStringName = NexusAppsConsts.TrainingManagerSchemaConnectionStringName;
                if (_unitOfWorkProvider.Current.GetTenantId().HasValue)
                {
                    var tenantEntry = _tenantCache.Get(_unitOfWorkProvider.Current.GetTenantId().Value);
                    tenantName = tenantEntry.TenancyName;
                    tenantConnectionStringName = string.IsNullOrEmpty(tenantEntry.TenancyName)? tenantConnectionStringName:tenantConnectionStringName+"."+tenantEntry.TenancyName;
                }
                var tenantDbConnectionString = _appConfiguration.GetConnectionString(tenantConnectionStringName);
                if (string.IsNullOrEmpty(tenantDbConnectionString))
                {
                    throw new UserFriendlyException("NoDatabase", String.Format("No database for Tenant {0}", tenantName));
                }
                return tenantDbConnectionString;
            }
                

This was straightforward to implement, as injecting ITenantCache and ICurrentUnitOfWorkProvider in the ConnectionStringResolver worked without any other changes.

However, I now want to use the tenant name for separating file storage in Azure Storage based on the tenant name by setting up a container with in the storage account that includes the tenancy name. From my initial attempt using the approach above, it may be more complicated because use of ITenantCache and ICurrentUnitOfWorkProvider seem to require modifying the constructors not only of my BlobServicesClient class, but also including injection in the classes using my BlobServicesClient class.

Is there an example of accessing/resolving the tenant name in areas other than for connection string resolution? Currently, BlobServicesClient is just a helper class and doesn't extend a base class (like ApplicaitonService, AbpController, etc.) Would introducing a base class help in resolving the current tenant name? (like through Session to get TenantId and look up name in ITenantCache)

Please advise what direction I should take.

It has been complex in figuring out the multitenancy aspects of AspnetZero, but in the end the amount of change I needed to make to recognize tenant name from sub domain on the Angular client app and resolve to separate tenant database was very little....several days of investigating for ~ 10 lines of code change :-) All of the possible formulations of a multi tenant solution make it difficult to figure out what is applicable to your individual circumstance.


2 Answer(s)
  • User Avatar
    1
    michael.pear created

    I found a solution for finding and using the tenant name in my helper class.

    1. Kept the helper class general by adding a string property that allows changing the container name to include the tenant name.
    2. To resolve the tenant name from either a current session or current unit of work, introduced a static TenantNameHelper class:
    using Abp.Domain.Uow;
    using Abp.MultiTenancy;
    using Abp.Runtime.Session;
    using System;
    
    
    namespace AZProject.Utilities.Helpers
    {
        public static class TenantNameHelper
        {
            public static string GetTenantNameFromSession(IAbpSession session, ITenantCache tenantCache)
            {
               
                if (session.TenantId.HasValue)
                {
                    return tenantCache.Get(session.TenantId.Value).TenancyName;
                } else
                {
                    //If userid has value, then this is host, otherwise not logged in (blank tenanacy)
                    return session.UserId.HasValue ? "host": String.Empty;
                }
    
            }
            public static string GetTenantNameFromUnitOfWork(IActiveUnitOfWork unitOfWork, ITenantCache tenantCache)
            {
                if (unitOfWork.GetTenantId().HasValue)
                {
                    return tenantCache.Get(unitOfWork.GetTenantId().Value).TenancyName;
                }
                else
                {
                    //Assumes host as tenant
                    return "host";
                }
            }
    
        }
    }
    
    1. Added an injection for ITenantCache ineach of the services where the TenantNameHelper is used. Most were an ApplicationService or Controller as base class, so AbpSession was available.

    Haven't fully tested yet, so if you have suggestions on where I might run into problems, let me know.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @michael.pear

    Your approach is fine. Framework itseld also uses a similar approach internally.