开发者

How to prepare the controllers to use Session but be testable?

开发者 https://www.devze.com 2023-01-13 01:13 出处:网络
How to prepare the ASP.NET MVC Controllers to use Session and at the same time be testable, so in essence not use Session but rather use some Session abstraction? I am using Ninject so your examples c

How to prepare the ASP.NET MVC Controllers to use Session and at the same time be testable, so in essence not use Session but rather use some Session abstraction? I am using Ninject so your examples could be based on that.

The problem is that the Session object is not available at all times in the controllers (like in ctor's) but I need to store something to the Session at application start (the global.asax.cs also does开发者_JAVA百科 not have access to the Session).


Just use a mock framework to create a mock HttpSessionStateBase and inject it into the controller context. With Rhino Mocks, this would be created using MockRepository.PartialMock<HttpSessionStateBase>() (see below). The controller, during the test, will then operate on the mock Session.

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


If you want your class to be testable, don't use non-testable (external) components there. When you try to mock them, you just work around the bad design. Re-design you controllers instead. A class shouldn't rely on external/global objects. That's one of the reasons why IoC is used.

You have two choices to separate implementation/infrastructure details from your controllers:

  1. Minor abstraction

    public interface ISession
    {
       string GetValue(string name);
       void SetValue(string name, string value);
    }
    
  2. Domain abstraction.

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

In the latter case, the interface adds semantics over session - strongly typed, well named property. This is just as using NHibernate domain entities instead of sqlreader["DB_COLUMN_NAME"].

Then, of course, inject HTTP implementation of the interface (e.g. using HttpContext.Current) into controllers.

Model binders are also a good way to go, just as action filters are. They're not just for form data.


There are a couple of ways - either use a custom filter attribute to inject a session value into a controller action, or create a session object with an interface that can be mocked and inject it into the constructor of the controller.

Below is an example of the custom filter.

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

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

        base.OnActionExecuting(filterContext);
    }
}`

and a way to use it in a controller:

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

Which one you choose probably depends on how much you rely on session values, but either way is relatively testable.


You can't store anything to Session at application start. Session = client initiated interaction. You don't have Sessions for all clients at application start.

Controller usually do not interact with session directly - it makes controllerd dependent on session. Instead controller methods (actions) accepts parameters which are automatically filled from the session by creating custom ModelBinder. Simple example:

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

You will than register your binder in Application_Start (Global.asax):

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

And you define your action like:

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

As you can see the controller is in no way dependent on Session and it is fully testable.

0

精彩评论

暂无评论...
验证码 换一张
取 消