2014-04-18 75 views
0

我最近重構了我的MVC應用程序以使用Unity依賴注入來解決依賴關係,這非常棒。它更易於分解等等。Unity - 使用請求中的信息來解決依賴關係

我現在正在做的是爲多個租戶添加功能來使用它。我使用的方法(以便其餘代碼不必知道租戶多少)正在創建諸如租戶過濾版本的存儲庫接口(這只是另一個存儲庫的代理...)所以它會調用其中一個底層方法,然後檢查記錄是否有合適的租戶並相應地執行)。這讓我基本上可以模擬爲每個租戶設置一個完全獨立的存儲,即使數據在數據不隔離的情況下也是如此,所以客戶代碼的相對較少需要更改。

所有這一切的問題是它如何適應DI方式的做事。我打算做的是,在請求開始時,檢測主機名,然後使用它來確定租戶(每個租戶都將擁有數據庫中的主機名列表)。雖然我正在使用每個請求的生命期來處理Unity正在構建和解決的大多數對象,但我並沒有真正瞭解Unity如何「知道」要使用的租戶,因爲它需要關於請求的數據(我想控制器會有,但我不認爲在我的容器配置方法中可用)和訪問數據庫以瞭解哪個主機(並且幾乎不需要讓我的容器配置進行數據庫調用)。我可以通過只傳遞一個主機名來解決#2,並讓租戶的類去確定哪個租戶被引用,但這對#1沒有幫助。

現在我正在使用「財產注入」(也被稱爲「一個公共財產」在較低的高度falutin'圈子),但我不明白我將如何避免讓我的控制器成爲一個實際提供租戶數據,所以現在我不是真的只有一個組合根控制一切。

有沒有一種方法可以在組合根中做到這一點,或者我應該讓自己屈服於讓控制器完成這項工作?

回答

1

出於某種原因,您似乎忘了注塑工廠。通過向工廠註冊接口/類型,您可以在解析時執行任意複雜的代碼,包括諮詢請求,租戶數據庫等等。

container.RegisterType<IRepository>( 
    new InjectionFactory( 
     c => { 
      // whatever, consult the database 
      // whatever, consult the url 
      return ...; 
     }); 

工廠組成是透明的,所以,每當你需要一個目標,甚至不知道工廠代碼已被執行,而不是從簡單的映射一個類型的實例。

+0

但是我怎樣才能找到該方法的網址?這一點不在範圍內,是嗎? – Casey

+0

我想我可以用這種方式真正簡潔地描述我的問題:做我想做的事情我需要訪問組合根內的請求數據,但據我所知我沒有訪問當我配置容器時的任何當前請求上下文。 – Casey

+0

'HttpContext.Current.Request.Url' –

2

某處需要進行數據庫調用。如果系統需要,最簡單的地方可能在global.ascx中。

private static ConcurrentDictionary<string, string> _tenantCache = new ConcurrentDictionary<string, string>(); 

protected virtual void Application_BeginRequest(object sender, EventArgs e) 
{ 
    HttpApplication app = (HttpApplication)source; 
    var tenantId = _tenantCache.GetOrAdd(app.Context.Request.Url.Host, host => 
      { 
       // Make database call in this class 
       var tenant = new TenantResolver(); 
       return tenant.GetTenantId(host); 
      }) 
    app.Context.Items["TenantID"] = tenantId ; 
} 

您將要緩存結果,因爲Application_BeginRequest被稱爲很多。然後,您可以將Unity配置爲具有子容器。將所有常見/默認映射放在父容器中,然後爲每個租戶創建一個子容器,併爲每個租戶在其自己的子容器中註冊正確的實現。

然後實現IDependencyResolver以返回正確的子容器。

public class TenantDependencyResolver : IDependencyResolver 
{ 
    private static IUnityContainer _parentContainer; 
    private static IDictionary<string, IUnityContainer> _childContainers = new Dictionary<string, IUnityContainer>(); 

    public TenantDependencyResolver() 
    { 
     var fakeTenentID = "localhost"; 
     var fakeTenentContainer = _parentContainer.CreateChildContainer(); 
     // register any specific fakeTenent Interfaces to classes here 

     //Add the child container to the dictionary for use later 
     _childContainers[fakeTenentID] = fakeTenentContainer; 
    } 

    private IUnityContainer GetContainer() 
    { 
     var tenantID = HttpContext.Current.Items["TenantID"].ToString(); 
     if (_childContainers.ContainsKey(tenantID) 
     { 
      return _childContainers[tenantID]; 
     } 
     return _parentContainer; 
    } 

    public object GetService(Type serviceType) 
    { 
     var container = GetContainer(); 
     return container.Resolve(serviceType); 
    } 

    public IEnumerable<object> GetServices(Type serviceType) 
    { 
     var container = GetContainer(); 
     return container.ResolveAll(serviceType); 
    } 
} 

然後將ASP.NET MVC DependecyResolver設置爲TenantDependencyResolver。我沒有運行這個代碼,但它應該讓你知道你需要做什麼。如果您的實現已設置,那麼您可以在TenantDependecyResolver的靜態構造函數中完成此操作。

+0

非常有趣。我不確定我會這麼想。 – Casey

+0

@CharlesNRice:這是不是每個租戶有額外的兒童容器矯枉過正?如果爲每個租戶註冊不同的實現,這將是有意義的。另一方面,他似乎需要更簡單的東西。 –

+0

@CharlesNRice:另外,放在'BeginRequest'中的代碼需要一個重構器,你可以把它放在一個單獨的類中,幷包裝在一個訪問內部的'HttpContext.Current.Items'的屬性中,以便它只被執行在需要的時候,而不是在每次請求時,無條件地。 –