Use parts of the ASP.NET runtime as providers for services the business layer might need – level 300

We all know to use the Strategy pattern to let the domain assembly know where to get and persist data.  Our domain assembly has no references to any other custom assembly, but all assemblies reference the domain.  We use the Dependency-Inversion Principle to accomplish this for data, but what about other services?

For instance, what if different domain object would benefit from knowledge of some state that is not AppDomain-specific, and not user-specific, but request-specific?  The ASP.NET runtime offers a great statebag for request-specific information.  HttpContext.Items.  This collection of key/value pairs can be used in an ASP.NET context.  Of course, we don’t want our model to depend on the ASP.NET runtime.  In fact, our model doesn’t care.  It will not reference any of our assemblies and especially not System.Web.  How then can we make a domain object interact with this Items collection? 

Simple.  Use a Strategy that allows injecting an adapter class into the domain object.  The adapter class can live at the ASP.NET runtime layer and server as a proxy for interaction.  First, in the domain layer, we must define the appropriate interface required:

    5 public interface IUserContextCacheable
    6 {
    7     void SaveUserState(object key, object state);
    8     object GetUserState(object key);
    9 }

Any class implementing this can serve as our state or cache provider.  For this example, I have two domain object that will consume this.  There are many ways to inject a dependency, so use your imagination.  I’m presenting the simplest way.

    5 public class Customer
    6 {
    7     public void DoSomething() {
    8         // Do something and save the state in cache.
    9         UserContextCache.Instance.SaveUserState(“MyState”, “I went to the store at ” + DateTime.Now.ToString(“hh:mm:ss”) + “.”);
   10     }
   11 }

    5 public class CustomerTracker
    6 {
    7     public string GetWhatWasDone() {
    8         // Check to see if something was done.
    9         object o = UserContextCache.Instance.GetUserState(“MyState”);
   10         if(o != null) return o.ToString();
   11         else return null;
   12     }
   13 }

Customer will save a piece of state, and later, CustomerTracker will benefit from it being there.  In our domain layer, we also have a singleton to hold an instance of IUserContextCacheable.  This singleton will be set by the ASP.NET layer at startup.

    3 public class UserContextCache
    4 {
    5     private static IUserContextCacheable _singleton;
    7     public static IUserContextCacheable Instance {
    8         get { return _singleton; }
    9         set { _singleton = value; }
   10     }
   12     private UserContextCache()
   13     {}
   14 }

Now, in our ASP.NET layer, we have this class:

    6 public class RequestCache : Model.IUserContextCacheable
    7 {
    8     public void SaveUserState(object key, object state) {
    9         HttpContext.Current.Items.Add(key, state);
   10     }
   12     public object GetUserState(object key) {
   13         return HttpContext.Current.Items[key];
   14     }
   15 }

It implements the required interface, and our controller will use an instance of this when constructing the domain objects.  An instance of this is set to the singleton in the domain layer using our Global.asax (you could use a number of other methods as well).

   24 protected void Application_Start(Object sender, EventArgs e)
   25 {
   26     Model.UserContextCache.Instance = new RequestCache();
   27 }

Here’s a snippet of code that, for demonstration purposes can be placed in any page or user control (or any control for that matter):

   22 this.TraceEnabled = true;
   24 Trace.Warn(“To prove that no previous state exists, I’ll try to get ‘MyState'”);
   25 Trace.Warn(“MyState = ” + this.Context.Items[“MyState”]);
   27 Trace.Warn(“Invoking business object, Customer, to do something.”);
   28 Customer f = new Customer();
   29 f.DoSomething();
   31 Trace.Warn(“Invoking CustomerTracker, another business object that benefits from this knowledge.”);
   32 CustomerTracker b = new CustomerTracker();
   33 string state = b.GetWhatWasDone();
   35 Trace.Warn(“Bar used the following state: “” + state + “””);

When you run this page, you’ll see Trace enabled and the following outputted to the screen:

Trace Information

Category Message From First(s) From Last(s)
To prove that no previous state exists, I’ll try to get ‘MyState’
MyState = 0.000013 0.000013
Invoking business object, Customer, to do something. 0.000032 0.000019
Invoking CustomerTracker, another business object that benefits from this knowledge. 0.000734 0.000701
Bar used the following state: “I went to the store at 07:17:18.” 0.001141 0.000407 Begin PreRender 0.001162 0.000021 End PreRender 0.001181 0.000019 Begin SaveViewState 0.001252 0.000072 End SaveViewState 0.001374 0.000122 Begin Render 0.001390 0.000016 End Render 0.073195 0.071805

This proves that our ASP.NET runtime adapter class works as intended and allows our domain layer to store things in the ASP.NET runtime.  This prevents us from having to implement our own caching mechanism that has the correct scope.

Using the Strategy pattern, we can hook up pretty much any outside service with an adapter class (adapter is my word). 

I’ve zipped up this sample solution, and it is available on my downloads page.