Model View Presenter (MVP) – Tips from trenches (TFT) – Working with HttpContext (Part 2)

 

Intro

This is part 2 of blog post series where I am presenting solutions I came up with to some common problems developers are facing implementing the MVP pattern in real world applications.

Previously on tips from trenches:

Today’s hurdle: Using HttpContext without System.Web

(Source code used in today’s example can be downloaded from here)

One of the principles I really feel strong about can be formulated like this:”Presenter is not allowed to reference System.Web assembly at all”.

Main reason why I feel so strong about that, is based on the fact that once referenced System.Web enables developers in your team to use types from that assembly (HttpContext, Session, Query, Cache etc) and we all know what PITA those types can be when testing Presenters.

Here’s very simple example I’ll use in today’s post:

Imagine you have a presenter which based on value of some query parameter is performing some redirecting or updating UI elements.

The easiest way (referencing the System.Web into presenter) will enable very easy implementation of required functionality but testing that code will require things like creating fake web context which IMHO is good sign of of important web test smell.

Luckily, there’s a solution 🙂

Model View Presenter pattern combined with Adapter design pattern offers easy and effective way of tackling this problem and this post is showing how to do that.

Adapter design pattern

This pattern is one of patterns I recently rediscovered as maybe one of the most useful patterns in every day programing. The pattern itself is not very sexy, because it is very simple. It’s purpose is to handle the cases Adapter_realexamplewhenever we need to adapt the interface of one component to the interface we need.

Canonical example mostly used in explaining the pattern is the case of US and European power adapters. I am sure I am not the only one European who found himself shocked by the fact that power outlets in USA are totally different then the European one. In other words, European power sockets are wider and round while US are narrow and flat. Luckily, there’s Ebay where one can buy himself an adapter compatible with USA power outlets with entry holes compatible to European standards. That’s how you charge your laptop using original cable adapted to custom power outlet.

Adapter design pattern is exactly the same thing.

As we can see on above diagram, when client calls a Target object he would get instance of Adapter inheriting from Target which will delegate to SpecificRequest method of of Adaptee object all of the calls for Request() method (Adaptee object is containing instance of Adapter).
In other words, Adapter “implements via delegation” to Adaptee.

Building the TDD friendly HttpContext

 

Implementation of TDD friendly HttpContext will be based on couple of steps:

  • building System.Web agnostic interface which would:
    • perform the role of Facade by hiding most of the the members not important for MVP
    • perform a role of a service contract which would be used by IoC container where custom HttpContext service will be need
  • building special collection type which would enable us transparent intercepting interface members access
  • building HttpContext service adapter which would implement the interface by delegating all of the calls to interface members to System.Web.HttpContext
  • registering service into IoC container

Defining IHttpContextService

As previously mentioned, first step in implementing the TDD friendly http context is defining the feature list I am expecting to use in my Presenters. Based on some of my current experiences, that interface might look like this:

image

You can see from the interface definition to the left that (IMHO) important http context members are:

  • Redirect(url) method – When presenter andor controller are responsible for driving navigation (more about controller in upcoming post)
  • QueryString property – read only name-value collection property which contains collection of string pairs representing url parameter name and value
  • User property  – IPrincipal type doesn’t require usage of System.Web and defines a lot of useful things for implementing presenters
  • ContextItems  – read only property of custom name value collection type (more about that in second) which contains collection of string pairs representing name of httpcontext item and value that item carries
  • Session – read only property of custom collection type containing string pairs representing application session variable entries

Key point here is that none of the members is not returning types from System.Web and that there are no Response, Request etc members. Only a small subset of really needed things defined in “easy to mock” way.

One thing Web Client Software Factory failed

While we speak about interface definition I would like to mention here a thing I’ve been disappointed seeing how it is been implemented there.

WCSF has IHttpContextLocatorService implemented like this (taken from reflector)

public interface IHttpContextLocatorService
{
    // Methods
    [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
    IHttpContext GetCurrentContext();
}

and that IHttpContext interface look like this (part of)

public interface IHttpContext
{
    // Methods
    HttpApplicationState Application { get; }
    HttpApplication ApplicationInstance { get; set; }
    Cache Cache { get; }
    HttpContext Context { get; }
    ProfileBase Profile { get; }
    HttpRequest Request { get; }
    HttpResponse Response { get; }
}

As you can see by the fact that interface has members of types defined in System.Web, in order to use IHttpContextLocatorService I am still required to use System.Web. In other words, to get url query parameter I would still have to use System.Web.HttpRequest.

I might be missing something pretty big here, but IMHO there is no sense at all in how P&P team implemented this service. Testing pain I felt using WCSF related to this made me thinking about better solutions and this post is result of that 🙂

HookableNameValueCollection

Now when you saw interface definition and heard where from inspiration for this post came, let’s talk about the custom collection I used in interface while defining type of Session and ContextItems properties.

Design goal behind the decision for creating this collection is to provide the same simple feeling working with http context adapter as the one we are used to it while using the real HttpContext.

Here are some simple examples of  what behavior I wanted to get:

  • var x=httpContextService.Session[“SOME_KEY”]    
  • httpContextService.Session[“SOME_KEY”] =”123”; 
  • httpContextService.Session.Add(“SOME_KEY”,123); 
  • httpContextService.Session.Remove(“SOME_KEY”); 

And here is what I was trying to avoid:

  • var x= httpContextService.GetSessionValue(“SOME_KEY”)
  • httpContextService.UpdateSessionItemValue(“SOME_KEY”,”123”)
  • httpContextService.RemoveSessionItem(“SOME_KEY”)

One way to do that is to play again with Adapter pattern and transform HttpSessionState to IDictionary based adapter class and use that adapter type, but that would be solution specific only to this problem and (if there is an option) I always try to solve problem with reusable solutions. The way I have picked for this blog post is simply to create custom name value collection type where I am going to override Add/Remove/Set methods and raise appropriate events informing listeners that certain activity occurred.

Something like this:

image

Here’s the same diagram given through plain old c# code  

namespace Vuscode.Framework.Abstractions
{
    using System;
    using System.Collections.Specialized;

    public class HookableNameValueCollection : NameValueCollection
    {
        public EventHandler<HookItemChangedEventArgs> ItemAdded;
        public EventHandler<HookItemChangedEventArgs> ItemSet;
        public EventHandler<HookItemRemovedEventArgs> ItemRemoved;

        public override void Add(string name, string value)
        {
            base.Add(name, value);
            if (ItemAdded != null)
                ItemAdded(this, new HookItemChangedEventArgs(name, value));
        }

        public override void Set(string name, string value)
        {
            base.Set(name, value);
            if (ItemSet != null)
                ItemSet(this, new HookItemChangedEventArgs(name, value));
        }

        public override void Remove(string name)
        {
            base.Remove(name);
            if (ItemRemoved != null)
                ItemRemoved(this, new HookItemRemovedEventArgs(name));
        }
    }
}

 

 

Building HttpContext service adapter

Once we have IHttpContextService and HookableNameValueCollection implemented, implementing httpcontext adapter is very simple implementation of  the context interface.

Due to the fact that we would have to reference System.Web in order to adapt certain properties, I will create new component called Framework.Adapters and put the adapter class implementation there.

Implementation in general would look like this:

  • Redirect method, QueryString collection property and User property would just delegate their calls to HttpContext.Current instance
  • HttpContextServiceAdapter class subscribe to ContextItems and Session add/update/set events and performs appropriate calls to HttpContext.Current.Session and HttpContext.Current.Items

Let’s take a peek at how this implementation looks like in the C# code …

#region

using System.Collections.Specialized;
using System.Security.Principal;
using System.Web;
using Vuscode.Framework.Abstractions;
using Vuscode.Framework.Abstractions.Web;

#endregion

namespace Framework.Adapters.Web
{
    public class HttpContextServiceAdapter : IHttpContextService
    {
        private readonly HookableNameValueCollection _context
            = new HookableNameValueCollection();

        private readonly HookableNameValueCollection _session
            = new HookableNameValueCollection();

        public HttpContextServiceAdapter()
        {
            InitSessionVariables();
            InitContextVariables();
        }

        #region IHttpContextService Members

        public NameValueCollection QueryString
        {
            get { return HttpContext.Current.Request.QueryString; }
        }

        public void Redirect(string url)
        {
            HttpContext.Current.Response.Redirect(url);
        }

        public IPrincipal User
        {
            get { return HttpContext.Current.User; }
            set { HttpContext.Current.User = value; }
        }

        public HookableNameValueCollection Session
        {
            get { return _session; }
        }

        public HookableNameValueCollection ContextItems
        {
            get { return _context; }
        }

        #endregion

        private void InitContextVariables()
        {
            if (HttpContext.Current.Items != null)
                foreach (string itemsKey in HttpContext.Current.Items.Keys)
                {
                    _context.Add(itemsKey,
				 HttpContext.Current.Items[itemsKey].ToString());
                }

            _context.ItemAdded += ((sender, e)
			    => HttpContext.Current.Items.Add(e.Name, e.Value));
            _context.ItemSet += ((sender, e)
			    => HttpContext.Current.Items[e.Name] = e.Value);
            _context.ItemRemoved += ((sender, e)
			    => HttpContext.Current.Items.Remove(e.Name));
        }

        private void InitSessionVariables()
        {
            if (HttpContext.Current.Session != null)
                foreach (string sessionKey in HttpContext.Current.Session.Keys)
                {
                    _session.Add(sessionKey,
				 HttpContext.Current.Session[sessionKey].ToString());
                }

            _session.ItemAdded += ((sender, e)
			    => HttpContext.Current.Session.Add(e.Name, e.Value));
            _session.ItemSet += ((sender, e)
			    => HttpContext.Current.Session[e.Name] = e.Value);
            _session.ItemRemoved += ((sender, e)
			    => HttpContext.Current.Session.Remove(e.Name));
        }
    }
}

Key points:

  • Constructor calls two methods:
    • InitContextVariables – which iterates through all of the  current Httpcontext.Current.Items and populate with their values custom context collection. After that, there’s code which implements event handlers for events published from context custom collection by using the lambda expressions. That’s how when ItemAdded event would be raised it would result with _httpContext.Items.Add method execution
    • InitSessionVariables – same story here. Code first populate custom session collection with the entries already stored in session and then hooks up custom events with appropriate HttpContext Session methods
  • Redirect method just delegates to _httpContext.Response.Redirect() method
  • User property delegates implementation to _httpContext.User
  • QueryString delegates implementation to _httpContext.Request.QueryString

IoC container context service registration

Now when we have service contract (IHttpContextService) and service (HttpContextServiceAdapter), the only thing left to be done is registering them into IoC container so presenters can use them in decoupled manner.

Doing that is very simple… First we add a reference to Framework.Adapters to web application project in order to get access to HttpContextServiceAdapter class. Then we create a simple class called BootStrapper.cs into web application root folder looking something like this

using Framework.Adapters.Web;
using Vuscode.Framework;
using Vuscode.Framework.Abstractions.Web;

namespace Vuscode.Web.WebSite
{
    public class Bootstrapper
    {
        public static void Init()
        {
            ServiceLocator.Register
		       <IHttpContextService, HttpContextServiceAdapter>();
        }
    }
}

At the end we just add call to Bootstrapper Init method into Application_Start event handler of global.asx so our IoC container would be populated at the beginning of web application life cycle.  

Solution in action

Now when infrastructure is in place, implementing HttpContext related logic in presenter  is very trivial

using Vuscode.Framework;
using Vuscode.Framework.Abstractions.Web;
using Vuscode.Framework.ModelViewPresenter;

namespace Presenters
{
    public class DefaultViewPresenter  : Presenter
    {
        private readonly IHttpContextService _httpContextService;

        #region .ctors
        public DefaultViewPresenter()
            : this(ServiceLocator.Retrieve<IHttpContextservice>()){ }

        internal DefaultViewPresenter(IHttpContextService httpContextService)
        {
            _httpContextService = httpContextService;
        }
        #endregion

        public void SomeMethod()
        {
            string urlParam = _httpContextService.QueryString[“param”];
            if (!string.IsNullOrEmpty(urlParam))
                View.Parameter =urlParam;
            if (urlParam == “nikola”)
                _httpContextService.Redirect(“vuscode.com”);
        }
    }
}

At the beginning of the class we have standard poor man dependency injection combined with service locator thing.
(In case you are not familiar with what I just said go and check one of my Design For Testability (DFT) posts)

After we would inject the http context service it’s usage is exactly the same as it would be usage based on HttpContext.Current (which was one of the design goal) so everyone in your team should be able to start using it immediately 

Unit testing HttpContext related presenter code

The test og SomeMethod given above can be now written before the SomeMethod implementation and it doesn’t require  any Web reference.
Here is the fragment of the test illustrating how simple now is to test the HttpContext related things in Presenters (complete test fixture code can be found in code sample archive attached at the beginning of this post)

        [TestMethod]
        public void SomeMethod_InCaseOfNikolaParamValue_WouldRedirectToVuscode()
        {
            using (_mockRepository.Record())
            {
                // setting up the stage
                SetupResult.For(_mockedHttpContextService.QueryString)
                    .Return(new NameValueCollection() { { “param”, “nikola” } });

                // defining expected behavior
                _mockedHttpContextService.Expect(x => x.Redirect(“vuscode.com”));
            }
            using (_mockRepository.Playback())
            {
                var presenter = new DefaultViewPresenter
				   (_mockedHttpContextService) {View = _mockedView};
                presenter.SomeMethod();
            }
        }

As you can see it from code above, to get url needed for test set up all one need to do is to set QueryString property of mocked IHttpContextService to contain desired URL parameters.

Then I define expected behavior using new Rhino Mock 3.5 syntax for void methods where I said that I expect that redirection to www.vuscode.com would occur.

Once the recording is over, in playback part I inject the mocked http context service and mocked view and call SomeMethod.

Ain’ that piece of cake? 🙂

What’s next?

Today I showed how to implement TDD friendly http context and introduced new infrastructure component Framework.Adapters.
My next blog post would introduce the role of Application controller in MVP (as a ported solution from WCSF)

‘till then…

Posted on July 30, 2008, in Uncategorized. Bookmark the permalink. 7 Comments.

  1. Liking the series alot so far and looking forward to reading the next installment!
    Keep up the good work.

  2. This is a nice peice of code. Thanks. However the HookableNameValueCollection fails when I try to add any entity object in the Session, as it seem to currently take in string. I tried to make a generic NameValueCollection, but that has its problems of a different kind. Could you suggest any ways arounds.?

  3. Dheeman,
    I deliberately used the NVC to discourage storing of non string values  (a thing related to limitation InProc session has with web farms) which solution I would present in my next blog post – application controller
    With current code of this post  I would modify the solution to utilize JSON serializedeserialize. You can also try replacing NVC with generic dictionary (I would provide the source code for this once I’ll be back from vacation – in 10 days).
    Off course, you can wait for my next MVP application controller blog  where I would present systematic solution for object context preservation in no-session environments      

  4. Good article,
    I agree with trying not to reference the System.Web assembly in the presenter.
    I created a seperate project called webutility that is for anything that needs System.Web, and can be acessed via an interface and dependancy injection.
    The latest addition was to hide the Membership API behind an interface so I can keep System.Web seperate, and test the presenter.
    Funny how other people trying to hide the implementation behind an interface but still keep the dependancy to the system.web assembly.
    Happens also with IMembershipService in an MVC example I saw where the MembershipCreateStatus from system.web was still referenced.

  5. It seems this implementation allows sharing of the session across browser instances.  Have you seen this?  If so, are you aware of a work around?

  6. As Sean said, the session gets shared! Not only amongst browsers on the same machine, but ANY client accessing!
    If I've got this right, on Application StartUp, the Bootstrapper creates the HttpContextServiceAdapter object for the 1st person accessing the application, and then this is shared with ANY client that subsequently follows!
    I extended the HttpContextServiceAdapter object so that I could deal with a shopping basket cookie, and discovered the above-mentioned behaviour when all clients accessing it were sharing the same basket.
    I have since changed the way that this works, and have moved the responsibility for creation of the HttpContextServiceAdapter object to the ViewBasePage<TPresenter> class.
    Please ammend your sample code so that others don't go down the same route!

  7. DinoSean,
    this is just a dummy sample code made to ilustrate the idea (code I am using in production is not quite the same) but that is the code I am no tallowed to paste here.
    Anyhow, I’ll look now at this code in order not to have session issues you guys describe…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: