2013-11-03 33 views
1

我正在構建一個多租戶MVC應用程序,其中有一個應用程序池和單個數據庫。我有一個租戶表,我的每個模型都有一個TenantId標識。使服務層可訪問的對象不作爲參數在MVC4中傳遞應用程序

每個租戶都有一個字符串「Url」,用於標識用於訪問該租戶數據的完整URL。在一個點,我需要向租客傳遞到服務層來執行業務邏輯

HttpRequest request = HttpContext.Current.Request; 
Uri requestUrl = request.Url; 
_tenant = _tenantService.GetTenantByUrl(requestUrl); 

現在,我:

我可以從我的BaseController具有以下(粗略估計)訪問此。我可以做到這一點的一種方法是跨所有服務(〜200個方法)遍歷每一種方法並添加一個Tenant參數。我不得不觸摸每個呼叫到服務層,並且每個服務層方法。這會起作用,但它很繁瑣,並且混淆了代碼。

例如,我的一個方法之前:

public void DeleteUserById(int userId) 
    { 
     using (var db = CreateContext()) 
     { 
      var user = db.Users.FirstOrDefault(u => u.UserId.Equals(userId)); 
      InternalDeleteUser(db, user); 
     } 
    } 

後(如果我通過在租戶):

public void DeleteUserById(Tenant tenant, int userId) 
    { 
     using (var db = CreateContext()) 
     { 
      var user = tenant.Users.FirstOrDefault(u => u.UserId.Equals(userId)); 
      InternalDeleteUser(db, user); 
     } 
    } 

我試圖實現(通過設置租戶我BaseController,上升一層):

public void DeleteUserById(int userId) 
    { 
     using (var db = CreateContext()) 
     { 
      var user = _tenant.Users.FirstOrDefault(u => u.UserId.Equals(userId)); 
      InternalDeleteUser(db, user); 
     } 
    } 

有什麼辦法,我可以用我的BaseService(所有其他服務繼承來回回m this)還是其他一些模式來定義控制器中的租戶,並讓服務方法撿起它,而不將它作爲參數傳遞給每個?這樣我只需要觸摸基本控制器(或甚至可能是global.asax),而不需要其他任何東西。

簡單地說:如何通過從MVC控制器定義一個對象,而無需將其直接傳遞給服務,讓所有服務都可以訪問對象?

+0

你進行某種形式的用戶認證的?我覺得將TenantId與當前用戶聯繫起來很自然。請參閱[此堆棧溢出問題](http://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal)以瞭解如何將自定義字段添加到當前身份/主體目的。然後你可以像服務層代碼那樣訪問TenantId:'((MyCustomPrincipal)HttpContext.Current.User).TenantId'。 –

+0

@EiríkurFannarTorfason我有自定義成員資格設置和用戶目前正與租戶配對,但是並不是所有的查詢涉及用戶,所以我不認爲這種做法是可行的。 – SB2055

+0

好吧,總是有'Session'對象。 –

回答

3

我想你說的有關基礎服務的意見(請參閱Layer Supertype)是合理的。該基類將依賴於在同一個服務層中定義的接口(例如IUserSession,IContext或其他),並且該接口將有一個方法或屬性返回您的租戶。

該接口的實現將駐留在您的Web應用程序中,它將按照您所描述的執行操作,從HttpContext獲取數據。

如果你有一個後臺進程,控制檯應用程序或任何不上的網絡環境中運行,你將有不同的實現,這將創建一個基於所需的任何其他條件的租戶。

所以總結一下,你會在你的服務層:

abstract class BaseService 
{  
    protected IContext Context {get; private set;} 

    public BaseService(IContext context) 
    { 
      Context = context; 
    } 
} 

public interface IContext 
{ 
    Tenant GetTenant(); 
} 

然後在你的web層you'll有:

public IWebContext : IContext 
{ 
    public Tenant GetTenant() 
    { 
     //your code to return create the tenant based on the url. 
    } 
} 

希望這有助於。

+0

非常感謝! – SB2055

0

自從我構建多租戶應用程序以來,我也有同樣的問題。但是,我解決這個問題很簡單,IMO:每個存儲庫/服務都定義了一個TenantId屬性,在使用該服務時必須設置該屬性。 TenantId是一個值對象,如果爲null則會拋出。

現在,重點是可以在請求之外使用任何repos/services,例如在後臺線程或應用程序中。我使用的是消息驅動的方法,因此任何所需的信息(如tenant id)都是消息的一部分,因此可用於服務的消費者(消息處理程序)。另一個好處是可測試性。

我反對將您的服務耦合到特定於請求的對象(如HttpContext,Session或Cache)的建議。

+0

謝謝邁克。我也在我的BaseService上實現了TenantId屬性。你如何從你的控制器那裏獲得TenantId?由於租戶應該由URL確定,您是否會在BaseController中提取它? – SB2055

+0

我提取了一個httpmodule中的租戶id,然後將其緩存在HttpContext.Items中。然後是一種擴展方法,使其更容易訪問它。 – MikeSW

+0

真棒 - 看起來像我有更多的學習要做:)。感謝您的幫助 – SB2055

相關問題