首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

ASP.NET MVC 動態二級域名實現:路由捕獲 + DNS 泛解析 + 多租戶架構

     編輯:關於ASP.NET

SaaS 產品常需為每個租戶分配專屬二級域名:tenant1.app.com、tenant2.app.com。本文展示基於 ASP.NET Routing 捕獲子域、結合 DNS 泛解析、配合租戶解析管線的完整方案。

DNS 配置:泛解析

; 域名服務商控制面板新增記錄
*   A   192.168.1.100      ; 所有 *.app.com 指向同一伺服器 IP
; 或
*   CNAME   app.com.       ; 指向主域別名

驗證:nslookup random.app.com 解析至目標 IP。

IIS 綁定:通配符主機名

  • IIS 站點綁定:Host Name 留空 (接收所有主機頭) 或明確添加 *.app.com (需 IIS 10+/Windows Server 2016+ 支援通配符)。
  • 舊版 IIS 需建立多個站點綁定各子域,或使用 URL Rewrite 重寫 Host Header。

路由捕獲子域:自定義 RouteBase

public class SubdomainRoute : RouteBase {
    public override RouteData GetRouteData(HttpContextBase httpContext) {
        var host = httpContext.Request.Headers["Host"]; // tenant.app.com:80
        if (string.IsNullOrEmpty(host)) return null;
        var parts = host.Split('.');
        if (parts.Length < 3) return null; // 非二級域名 (如 www.app.com)

        var tenant = parts[0]; // tenant
        // 可選:驗證租戶是否存在/啟用
        if (!TenantService.Exists(tenant)) return null;

        var routeData = new RouteData(this, new MvcRouteHandler());
        routeData.Values["controller"] = "Home";
        routeData.Values["action"] = "Index";
        routeData.Values["tenant"] = tenant; // 關鍵:將租戶標識注入路由值
        routeData.DataTokens["namespaces"] = new[] { "MyApp.Controllers" };
        return routeData;
    }
    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 
        => null; // 不支援反向生成子域 URL,需自行實作
}

// Global.asax 註冊 (優先於預設路由)
public static void RegisterRoutes(RouteCollection routes) {
    routes.Add("Subdomain", new SubdomainRoute());
    routes.MapRoute("Default", "{controller}/{action}/{id}", 
        new { controller="Home", action="Index", id=UrlParameter.Optional });
}

Controller 基類:自動解析租戶上下文

public abstract class TenantControllerBase : Controller {
    protected TenantContext Tenant { get; private set; }

    protected override void Initialize(RequestContext requestContext) {
        base.Initialize(requestContext);
        var tenantSlug = requestContext.RouteData.Values["tenant"] as string 
            ?? requestContext.HttpContext.Request.Headers["X-Tenant"]; // 支援 Header 傳遞
        Tenant = TenantService.GetContext(tenantSlug); // 包含 DB 連接串、配置、品牌化資源
        if (Tenant == null) throw new HttpException(404, "租戶不存在");
        // 設定當前執行緒文化、連接字串、View 路徑等
    }
}

// 使用
public class HomeController : TenantControllerBase {
    public ActionResult Index() {
        ViewBag.TenantName = Tenant.Name;
        return View(); // 可根據 Tenant.Theme 選擇不同視圖
    }
}

資料隔離策略

策略實現優點缺點
共享資料庫 + 租戶欄位所有表加 TenantId,查詢自動過濾維護簡單、成本低資料洩露風險、效能干擾、Schema 變更影響所有
共享資料庫 + 獨立 SchemaPostgreSQL Schema / SQL Server 命名空間隔離性較好、備份靈活跨 Schema 查詢複雜、遷移工具支援有限
獨立資料庫每租戶一庫,連接字串動態構建最高隔離、可定制版本、合規友善維護成本高、連接池碎片化、部署複雜

進階:租戶識別多來源支援

public class TenantResolver {
    public string Resolve(HttpRequestBase request) {
        // 1. 子域名
        var host = request.Headers["Host"];
        if (host != null) {
            var parts = host.Split('.');
            if (parts.Length >= 3 && !IsWww(parts[0])) return parts[0];
        }
        // 2. 路由參數 (Area/Route)
        var routeTenant = request.RequestContext.RouteData.Values["tenant"] as string;
        if (!string.IsNullOrEmpty(routeTenant)) return routeTenant;
        // 3. Header (API 客戶端)
        var headerTenant = request.Headers["X-Tenant-Id"];
        if (!string.IsNullOrEmpty(headerTenant)) return headerTenant;
        // 4. QueryString (測試/臨時)
        var queryTenant = request.QueryString["tenant"];
        if (!string.IsNullOrEmpty(queryTenant)) return queryTenant;
        return null; // 預設/公共租戶
    }
}

避坑指南

  • Cookie Domain:設定 .app.com 共享登入態,參考《跨子域 Session 共享》。
  • SSL 憑證:需通配符憑證 *.app.com 或 Let's Encrypt 泛域名憑證 (DNS-01 驗證)。
  • 靜態資源:CDN 域名區別 (cdn.app.com),避免 Cookie 污染、啟用 CORS。
  • 健康檢查:負載均衡器探針需攜帶有效 Host Header 或走專用路徑 /health (排除租戶解析)。
  • 測試策略:本地 hosts 映射 tenant1.local.app.com → 127.0.0.1,或使用 lvh.me (解析至 127.0.0.1)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved