代码之家  ›  专栏  ›  技术社区  ›  mare

如何准备控制器使用会话,但可以测试?

  •  3
  • mare  · 技术社区  · 14 年前

    如何准备ASP.NET MVC控制器使用会话,同时又是可测试的,因此本质上不使用会话,而是使用一些会话抽象?我正在使用ninject,因此您的示例可以基于此。

    问题是,会话对象在控制器(如ctor)中并非始终可用,但我需要在应用程序启动时存储一些内容到会话(global.asax.cs也没有访问会话的权限)。

    4 回复  |  直到 14 年前
        1
  •  2
  •   queen3    14 年前

    如果您希望类是可测试的,那么不要在那里使用不可测试(外部)组件。当你试图取笑他们时,你只是在设计不好的地方工作。改为重新设计控制器。类不应依赖外部/全局对象。这就是为什么使用国际奥委会的原因之一。

    您有两个选择来将实现/基础结构细节与控制器分开:

    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 .

        2
  •  3
  •   bzlm    14 年前

    只是 use a mock framework to create a mock HttpSessionStateBase 并将其注入控制器上下文。用犀牛嘲笑, 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
        );
    
        3
  •  1
  •   briercan    14 年前

    有两种方法——要么使用自定义过滤器属性将会话值注入控制器操作,要么使用可模拟的接口创建会话对象并将其注入控制器的构造函数。

    下面是自定义筛选器的示例。

    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);
    }
    

    您选择哪一个可能取决于您对会话值的依赖程度,但这两种方法都是相对可测试的。

        4
  •  1
  •   Ladislav Mrnka    14 年前

    您不能在应用程序启动时将任何内容存储到会话。会话=客户端启动的交互。在应用程序启动时,您没有针对所有客户机的会话。

    控制器通常不直接与会话交互-它使控制器依赖于会话。相反,控制器方法(操作)接受通过创建自定义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;
      }
    }
    

    您将在应用程序启动(global.asax)中注册活页夹:

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

    你的行为定义如下:

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

    正如您所看到的,控制器绝不依赖于会话,它是完全可测试的。