2010-08-26 54 views
3

如何準備ASP.NET MVC控制器使用Session並且同時是可測試的,所以實質上不使用Session而是使用一些Session抽象?我正在使用Ninject,因此您的示例可以基於此。如何準備控制器以使用會話但可測試?

問題是Session對象在控制器中一直不可用(比如ctor的),但我需要在應用程序啓動時將某些東西存儲到Session中(global.asax.cs也無法訪問會議)。

+0

昨天我問了一個相關的問題。它沒有答案,所以你可能想要保持關注。 http://stackoverflow.com/questions/3570513/what-is-the-most-complete-mocking-framework-for-httpcontext – kbrimington 2010-08-26 18:33:44

+0

可能的重複[如何使用MOQ模擬ASP.NET MVC中的HttpContext?]( http://stackoverflow.com/questions/1452418/how-do-i-mock-the-httpcontext-in-asp-net-mvc-using-moq) – bzlm 2010-08-26 21:55:54

回答

2

如果您希望您的類可以測試,請勿在其中使用不可測試(外部)組件。當你試圖嘲笑他們時,你只是在糟糕的設計中工作。相反,重新設計你的控制器。一個類不應該依賴外部/全局對象。這是使用IoC的原因之一。

你有兩個選擇從您的控制器分離實現/基礎設施的細節:

  1. 小抽象

    public interface ISession 
    { 
        string GetValue(string name); 
        void SetValue(string name, string value); 
    } 
    
  2. 域抽象。

    public interface IStateData 
    { 
        bool IsPresent { get; } 
        int MyDomainMeaningfulVariable { get; set; } 
    } 
    

在後一種情況下,接口增加了語義過會 - 強類型,以及命名屬性。這與使用NHibernate域實體而不是sqlreader [「DB_COLUMN_NAME」]一樣。

然後,當然,將接口的HTTP實現(例如使用HttpContext.Current)注入到控制器中。

與動作過濾器一樣,模型活頁夾也是一個好方法。他們是not just for form data

+0

第二個選項實際上看起來非常好。就像我將在控制器構造函數中使用存儲庫接口一樣,我可以將其添加爲參數,然後設置Ninject規則以提供具體的實現。 – mare 2010-08-27 10:33:48

1

有幾種方法 - 使用自定義過濾器屬性將會話值注入到控制器操作中,或者使用可模擬的接口創建會話對象並將其注入到控制器的構造函數中。

下面是自定義過濾器的示例。

public class ProfileAttribute : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    { 

     filterContext.ActionParameters["profileUsername"] = "some session value"; 

     base.OnActionExecuting(filterContext); 
    } 
}` 

,並在控制器中使用它的方式:

[ProfileAttribute] 
public ActionResult Index(string profileUsername) 
{ 
    return View(profileUsername); 
} 

你選擇哪一種可能取決於你有多少依靠會話值,但無論哪種方式比較測試。

+0

該解決方案也遭受與@Ladislav之一相同的問題。你必須修飾控制器的屬性才能工作。我寧願有一個開發人員入門的,「集中管理」的解決方案來訪問Session。所以看起來,使用模擬Session對象和控制器注入的解決方案似乎是正確的選擇。 – mare 2010-08-26 19:12:46

1

在應用程序啓動時,您無法將任何內容存儲到會話中。會話=客戶端發起的交互。在應用程序啓動時,您沒有針對所有客戶端的會話。

控制器通常不直接與會話交互 - 它使控制器依賴於會話。相反,控制器方法(動作)接受通過創建自定義ModelBinder從會話中自動填充的參數。簡單的例子:

public class MyDataModelBinder : IModelBinder 
{ 
    private const string _key = "MyData"; 

    public object BindModel(ControllerContext context, ModelBindingContext bindingContext) 
    { 
    MyData data = (MyData)context.HttpContext.Session[_key]; 

    if (data == null) 
    { 
     data = new MyData(); 
     context.HttpContext.Session[_key] = data; 
    } 

    return data; 
    } 
} 

你會比登記的Application_Start(Global.asax中)您粘結劑:

ModelBinders.Binders.Add(typeof(Mydata), new MyDataModelBinder()); 

,並且定義了你的行動,如:

public ActionResult MyAction(MyData data) 
{ ... } 

正如你所看到的控制器決不依賴於Session,並且它是完全可測試的。

+0

我想我可以「創造」一個類,它可以存儲我想要存儲在Session中的數據併爲其創建一個自定義Model Binder,但這需要我將此類作爲參數添加到所需的所有操作方法中使用Session。即使它是可測試的,但它看起來並不正確,實際上它很麻煩,開發人員需要知道他必須將SessionData(我剛拿出這個名字)添加到他想要的控制器動作中使用會話,如果他不知道,他最終會直接使用會話編寫一個方法,這是錯誤的。 – mare 2010-08-26 19:10:29

+0

我認爲我的方法比較乾淨,因爲它只向需要它們的動作提供強類型的必需數據。但是,除非您正在進行代碼審查,否則無法控制開發者代碼。我可以想象爲Code Analysis編寫一些自定義規則,當在Controller中直接訪問Session以支持我的方法時,它將觸發。 – 2010-08-26 20:12:45

+0

如果我將參數添加到所有控制器派生的基礎控制器的構造函數中,您認爲它會起作用嗎? – mare 2010-08-26 20:17:33

3

只需use a mock framework to create a mock HttpSessionStateBase並將其注入到控制器上下文中。隨着Rhino Mocks,this would be created使用MockRepository.PartialMock<HttpSessionStateBase>()(見下文)。測試期間,控制器將在模擬會話中進行操作。

var mockRepository = new MockRepository(); 
var controller = new MyController(); 
var mockHttpContext = mockRepository.PartialMock<HttpContextBase>(); 
var mockSessionState = mockRepository.PartialMock<HttpSessionStateBase>(); 
SetupResult.For(mockHttpContext.Session).Return(mockSessionState); 
// Initialize other parts of the mock HTTP context, request context etc 
controller.ControllerContext = 
    new ControllerContext(
     new RequestContext(
      mockHttpContext, 
      new RouteData() 
     ), 
     controller 
    );