Base solution for your next web application
Open Closed

Tenant Subdomain Resolver with Fallback #12506


User avatar
0
[email protected] created

Hi,

We’re using the subdomain tenant resolver and need to configure a single “master” entry point while still allowing tenant-specific subdomains. Below is our current behavior, edge case, and questions in one:


  1. Desired routing behavior

    • Host Dashboard & tenant registration: – Entry point: app.mydomain.io
    • Tenant access: – Existing tenants via tenant1.mydomain.io, tenant2.mydomain.io, etc.
  2. Current issue

    • Any unknown subdomain (e.g. dkfk.mydomain.io) also falls back to the Host Dashboard, even when the subdomain doesn’t match an existing tenant.
  3. Edge case: tenant named “app”

    • If a tenant chooses “app” as their short name, app.mydomain.io would resolve to that tenant instead of the Host Dashboard.
  4. Questions

    1. Is there built-in ASP.NET Zero support for reserving certain subdomains (e.g. “app”, “www”, “admin”) so they always route to the host context?

    2. If not, can you share a recommended approach—configuration settings or example code (e.g. a custom ITenantResolveContributor)—to:

      • Force only app.mydomain.io to resolve to the Host Dashboard, and
      • Prevent tenants from registering the reserved name “app.”

Thanks in advance for any guidance or sample snippets!

Best regards Fabian

Markdown is supported
Copy & paste or drag & drop images (max 30 MB per image)

1 Answer(s)
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hi Fabian,

    That's an excellent question and a very common requirement for customizing multi-tenant applications.

    While ASP.NET Zero doesn't have a simple, single configuration setting for reserving subdomains, its framework is perfectly designed to handle this with a custom, code-based solution. The approach involves two main parts: a custom tenant resolver for routing and a custom tenant manager for validation.

    Here is the recommended approach with code examples to achieve your desired behavior.

    Forcing app.mydomain.com to the Host Dashboard

    To control how subdomains are resolved, you'll replace the default tenant resolver with a custom one. This class will contain your specific logic: always treat "app" as the host, and for everything else, try to find a tenant.

    Create the Custom Tenant Resolver Class

    In your *.Core project, create a new class. A good location would be in a MultiTenancy folder.

    ReservedSubdomainTenantResolveContributor.cs

    namespace YourProjectName.MultiTenancy;
        
    public class ReservedSubdomainTenantResolveContributor : ITenantResolveContributor, ITransientDependency
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IConfigurationRoot _appConfiguration;
        private readonly ITenantStore _tenantStore;
    
        public ReservedSubdomainTenantResolveContributor(
            IHttpContextAccessor httpContextAccessor,
            IAppConfigurationAccessor configurationAccessor,
            ITenantStore tenantStore)
        {
            _httpContextAccessor = httpContextAccessor;
            _appConfiguration = configurationAccessor.Configuration;
            _tenantStore = tenantStore;
        }
    
        public int? ResolveTenantId()
        {
            var httpContext = _httpContextAccessor.HttpContext;
            if (httpContext == null)
            {
                // Can't resolve tenant in a non-HTTP context.
                return null;
            }
    
            var siteRootAddress = _appConfiguration["App:ServerRootAddress"];
            if (string.IsNullOrEmpty(siteRootAddress))
            {
                return null;
            }
    
            var hostName = httpContext.Request.Host.Host.RemovePreFix("www.");
            var siteHost = new Uri(siteRootAddress).Host.RemovePreFix("www.");
    
            if (hostName.Equals(siteHost, StringComparison.OrdinalIgnoreCase))
            {
                return null;
            }
            
            var subdomain = hostName.Replace($".{siteHost}", "", StringComparison.OrdinalIgnoreCase);
    
            if (subdomain.Equals("app", StringComparison.OrdinalIgnoreCase))
            {
                return null;
            }
    
            var tenant = _tenantStore.Find(subdomain);
            if (tenant != null)
            {
                return tenant.Id;
            }
    
            return null;
        }
    }
    

    Register the Custom Resolver

    Now, you need to tell the application to use your new class. In your *CoreModule.cs file, modify the PreInitialize method to clear the default resolvers and add your own.

    YourProjectNameCoreModule.cs

    using YourProjectName.MultiTenancy;
    
    public class YourProjectNameCoreModule : AbpModule
    {
        public override void PreInitialize()
        {
            // ... other configurations
    
            // It's crucial to Clear() existing resolvers to prevent conflicts and ensure only your logic runs.
            Configuration.MultiTenancy.Resolvers.Clear();
            Configuration.MultiTenancy.Resolvers.Add<ReservedSubdomainTenantResolveContributor>();
    
            // ... other configurations
        }
    
        // ...
    }
    

    To prevent tenants from registering names like "app", "www", etc., you'll extend the TenantManager and add your validation logic.

    Create a Custom Tenant Manager

    In the same *.Core/MultiTenancy folder, create the following class.

    CustomTenantManager.cs

    
    namespace YourProjectName.MultiTenancy;
    
    public class CustomTenantManager : TenantManager
    {
        private readonly List<string> _reservedTenancyNames = new List<string>
        {
            "app",
            "www",
            "admin",
            "host",
            "support",
            "mail"
        };
    
        //.. Constructor
    
        public override async Task CreateAsync(Tenant tenant)
        {
            if (_reservedTenancyNames.Any(name => name.Equals(tenant.TenancyName, System.StringComparison.InvariantCultureIgnoreCase)))
            {
                throw new UserFriendlyException("The tenancy name '" + tenant.TenancyName + "' is reserved and cannot be used.");
            }
            
            await base.CreateAsync(tenant);
        }
    }
    
    

    Register the Custom Tenant Manager

    Finally, replace the default TenantManager with your custom one using the dependency injection system. Add the following line to the PreInitialize method in your *CoreModule.cs.

    YourProjectNameCoreModule.cs

    public override void PreInitialize()
    {
        // ...
        Configuration.ReplaceService<ITenantManager, CustomTenantManager>(Abp.Dependency.DependencyLifeStyle.Transient);
        // ...
    }
    

    By implementing this two-part solution, you gain full control over your application's subdomain routing and tenant registration logic.

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)