I wanted to remove my controllers’ dependencies on HttpContext. I don’t know about anyone else, but having to set a fake context in all my unit tests was driving me nuts. Yeah, sure, Stephen Walther’s Fake Contexts are great, but they’re just another piece of friction in the unit test. What I currently need to access the HttpContext for is to get the authenticated user, as I was talking about in my last post. I ended up with my SessionController
looking like this:
public class SessionController : Controller { public ViewResult Authentication() { Authentication auth = new Authentication(ControllerContext.HttpContext.User); return View(auth); } }
Okay, time to split that dependency out of there. First off, I create a simple User
class to represent a user and altered the Authentication class to take an instance of that rather than IPrincipal
to further divorce my app logic from the underlying framework. I can now define an interface to represent the session:
public interface ISession { User AuthenticatedUser(); }
This is currently all I need to know about the session, so I’ve followed the YAGNI principle and left the interface at that for now. I’ve got two implementations for this interface, first up is FakeSession
for testing purposes:
class FakeSession : ISession { private User mAuthenticatedUser; public void Unauthorise() { mAuthenticatedUser = null; } public void AuthoriseAs(User authedUser) { mAuthenticatedUser = authedUser; } #region ISession Members public JkEditingHub.Models.User AuthenticatedUser() { return mAuthenticatedUser; } #endregion }
This is pretty simple and allows me to set who is logged in with ease.
The other implementation simply wraps the HttpContextBase
that the controller will know about:
public class AspSession : ISession { public AspSession(HttpContextBase context) { mContext = context; } private HttpContextBase mContext; public User AuthenticatedUser() { if (!mContext.User.Identity.IsAuthenticated) { return null; } return new User(mContext.User.Identity.Name); } }
Great, now all I need to do is inject the ISession
into any controllers that need it. I’m using constructor injection, so SessionController
now looks like this:
public class SessionController : Controller { public SessionController(ISession session) { mSession = session; } private ISession mSession; public ViewResult Authentication() { Authentication auth = new Authentication(mSession.AuthenticatedUser()); return View(auth); } }
Okay, now all I have to do is get StructureMap to use AspSession
as its default for ISession
. In my bootstrapping code I can do this:
ObjectFactory.Initialize(x => { // Other registrations... x.ForRequestedType<ISession>().TheDefaultIsConcreteType<AspSession>(); });
Great, no problem here. But how can I tell StructureMap which object to use for the HttpContextBase
argument in the AspSession
constructor, when that is going to be a specific instance that is different each time and not available when I’m bootstrapping? Fortunately, this is very very easy and doesn’t require going much beyond the very basic use I’ve so far made of StructureMap. In my ControllerFactory
I am using StructureMap to create the instances of controllers:
protected override IController GetControllerInstance(Type controllerType) { return (IController)ObjectFactory.GetInstance(controllerType); }
The ControllerFactory
has a handle to the RequestContext
which contains the HttpContext
for the request. All we need to do is tell StructureMap that if it requires an HttpContextBase
, then use this object by using the With<T>(T arg)
method. As I said, not at all advanced usage by any measure:
protected override IController GetControllerInstance(Type controllerType) { return (IController)ObjectFactory .With<HttpContextBase>(this.RequestContext.HttpContext) .GetInstance(controllerType); }