Saturday, September 8, 2012

Structuremap scopes and life cycles with example

This article is a detailed version of Basic structuremap scopes. I am trying to keep this article to focus generic DI/IOC container and not just for structuremap. While using any DI/IOC container you must be careful on two things.

  1. Request context
  2. From which context you are requesting object from the structuremap. Basically there are two contexts in a web application you can make request from. The first one is HttpContext and the other is Thread (ThreadContext).

  3. Object's scope
  4. In general the scope defines lifetime of an object. To be accurately with reference to DI/IOC container it is not the lifetime, it is better to say the object's accessiblity. That is, the object created in one scope cannot be referred in another scope. Also the DI/IOC container itself will not dispose the object at the end of the scope. We should explicitly dispose them or GC may take care at later point. For further reading How to dispose objects created by structuremap.


Let's look at the structuremap life cycles. Download the project for this example. I am using windows authentication in this project. Give your machine credentials to login the application. This project UI will not display any result. I am using log4net for results. Results are stored at "_ApplicationLogs\general.txt".

In this example I am using 'RuntimeHelpers.GetHashCode' to get the object hash code for each object. Since we write results in log file for objects comparison, the object hash code for each object helps us to make sure whether they are same object or different object.

  1. Example - transient objects - a.k.a. non-singleton
    • Registry class
    • In registry class, if you don't mention the life cycle, by default the structuremap creates transient object. That is, new object for each structuremap request.
      Code:
      public class ApplicationRegistry : Registry
      {
          public ApplicationRegistry()
          {
              Scan(scanner => 
              { 
                  For<IVehicle>().Use<Car>();                
              });            
          }
      }
      
    • Consumer code
    • In the following code, we are requesting structuremap two times. It returns different object each time. Have a look at the log result
      Code:
      public partial class  _Default : System.Web.UI.Page
      {
          ILog logger = log4net.LogManager.GetLogger("GeneralLog");
          protected void Page_Load(object sender, EventArgs e)
          {
              IVehicle Car = ObjectFactory.GetInstance<IVehicle>();
              logger.Info("First Car :" + Car.GetVehicleID() + " Login user:" + User.Identity.Name);
      
              Car = ObjectFactory.GetInstance<IVehicle>();
              logger.Info("Second Car :" + Car.GetVehicleID() + " Login user:" + User.Identity.Name);
          }
      }
      
    • Log result (log4net log result)
    • 09/08/2012 20:54:08.816 [12] INFO  GeneralLog 
      Message: First Car :3473973 Login user:My-PC\Pandian
      Exception: 
      
      09/08/2012 20:54:08.840 [12] INFO  GeneralLog 
      Message: Second Car :27605492 Login user:My-PC\Pandian
      Exception:
      

  2. Explicit call - transient objects - way 1
  3. The following approach works exactly similar way as explained in example-1.
    • Registry class
    • Code:
      For<IVehicle>().Transient().Use<Car>();
      

  4. Explicit call - transient objects - way 2
  5. This approach too works exactly similar way as explained in example-1 and example-2. The word “request” in the below code doesn't mean HttpRequest. The structuremap returns transient object for each request arrived at it.
    • Registry class
    • Code:
      For<IVehicle>().LifecycleIs(new UniquePerRequestLifecycle()).Use<Car>();
      

  6. Singleton objects
  7. Have a look at the following code and the log, it is very clear that the structuremap creates singleton object across requests. The object hash code 14145203 is same for all the four requests.
    • Registry class
    • Code:
      For<IVehicle>().Singleton().Use<Car>();
      
      For this example I logged in from two different browsers with two different credentials.
    • Log result (log4net log result)
    09/08/2012 22:15:52.277 [5] INFO  GeneralLog 
    Message: First Car :14145203 Login user:My-PC\Pandian
    Exception: 
    
    09/08/2012 22:15:52.313 [5] INFO  GeneralLog 
    Message: Second Car :14145203 Login user:My-PC\Pandian
    Exception: 
    
    09/08/2012 22:16:01.072 [5] INFO  GeneralLog 
    Message: First Car :14145203 Login user:My-PC\TestUser
    Exception: 
    
    09/08/2012 22:16:01.073 [5] INFO  GeneralLog 
    Message: Second Car :14145203 Login user:My-PC\TestUser
    Exception: 
    

  8. Explicit call - Singleton objects
  9. This method also creates singleton object across requests as explained in example 4. The only difference is that you can also pass constructor argument.
    Code:
    For<IVehicle>().Use(new Car());
    

  10. Tricky, it creates singleton object
  11. When we mention the instance in "Use" function it creates singleton object as I said in the 5th example. The "Trainsient()" request is ignored here.
    Code:
    For<IVehicle>().Transient().Use(new Car());
    

  12. Transient objects with constructor arguments.
  13. As opposed to example 5, the following statement in registry creates transient object and it lets you to pass constructor argument. Note, you should modify Car class to accept constructor argument.
    Code:
    For<IVehicle>().Use(context => new Car());
    

  14. Singleton per HttpRequest
  15. The following statement in registry class creates singleton object per request.I am logging in from two different machines. If you look at the log, you can see that it uses only one object per request.
    Code:
    For<IVehicle>().LifecycleIs(new HttpContextLifecycle()).Use<Car>();
    
    In the below log, the first two and the second two results are same. That means it creates new object for every request.
    09/08/2012 22:31:59.870 [19] INFO  GeneralLog 
    Message: First Car :19320046 Login user:My-PC\Pandian
    Exception: 
    
    09/08/2012 22:31:59.888 [19] INFO  GeneralLog 
    Message: Second Car :19320046 Login user:My-PC\Pandian
    Exception: 
    
    09/08/2012 22:32:07.944 [19] INFO  GeneralLog 
    Message: First Car :57289035 Login user:My-PC\TestUser
    Exception: 
    
    09/08/2012 22:32:07.945 [19] INFO  GeneralLog 
    Message: Second Car :57289035 Login user:My-PC\TestUser
    Exception: 
    

  16. Singleton per HttpSession
  17. Very similar to example 8, but it creates singleton object per session.
    Code:
    For<IVehicle>().LifecycleIs(new HttpSessionLifecycle()).Use<Car>();
    

  18. Thread context
  19. The following statement in registry class creates singleton object per thread.
    Code:
    For<IVehicle>().LifecycleIs(new ThreadLocalStorageLifecycle()).Use<Car>();
    
    I have changed a bit in the application's consumer code. There are three threads here. One parent thread and the other two threads are created by me. So there are three threads and three distinct objects.
    Code:
    public partial class  _Default : System.Web.UI.Page
    {
        ILog logger = log4net.LogManager.GetLogger("GeneralLog");
        //CLR runs this method as part of parent thread's execution
        protected void Page_Load(object sender, EventArgs e)
        {
            IVehicle Car = ObjectFactory.GetInstance<IVehicle>();
            logger.Info("First Car :" + Car.GetVehicleID() + "  Main Thread id: " + Thread.CurrentThread.ManagedThreadId);
    
            Car = ObjectFactory.GetInstance<IVehicle>();
            logger.Info("Second Car :" + Car.GetVehicleID() + "  Main Thread id: " + Thread.CurrentThread.ManagedThreadId);
      
            Thread thread1 = new Thread(new ThreadStart(ThreadFunction));
            thread1.Start();
    
            Thread thread2 = new Thread(new ThreadStart(ThreadFunction));
            thread2.Start(); 
        }
        private void ThreadFunction()
        {
            IVehicle Car = ObjectFactory.GetInstance<IVehicle>();
            logger.Info("First Car :" + Car.GetVehicleID() + " Thread id: " + Thread.CurrentThread.ManagedThreadId);
    
            Car = ObjectFactory.GetInstance<IVehicle>();
            logger.Info("Second Car :" + Car.GetVehicleID() + " Thread id: " + Thread.CurrentThread.ManagedThreadId);
        }
    }
    
    In the following log, there are three threads 6, 7 and 11. The three objects created for each thread. This is singleton per thread.
    09/08/2012 22:53:41.562 [6] INFO  GeneralLog 
    Message: First Car :14509978 Main Thread id: 6
    Exception: 
    
    09/08/2012 22:53:41.815 [6] INFO  GeneralLog 
    Message: Second Car :14509978 Main Thread id: 6
    Exception: 
    
    09/08/2012 22:53:41.998 [7] INFO  GeneralLog 
    Message: First Car :15366892 Thread id: 7
    Exception: 
    
    09/08/2012 22:53:41.999 [7] INFO  GeneralLog 
    Message: Second Car :15366892 Thread id: 7
    Exception: 
    
    09/08/2012 22:53:42.000 [11] INFO  GeneralLog 
    Message: First Car :7995840 Thread id: 11
    Exception: 
    
    09/08/2012 22:53:42.047 [11] INFO  GeneralLog 
    Message: Second Car :7995840 Thread id: 11
    Exception: 
    

  20. Hybrid context
  21. I have a special requirement for this example. Considering the above example-10, I need the first object (which is created in the parent thread) should be HttpContext scoped. The other two objects created inside the thread should be ThreadLocal scoped. In hybrid context the structuremap should automatically identify the request scope and it should return the object for the same scope. Generally HttpContext supersedes Thread context. This requirement can be solved as follows.
    Code:
    For<IVehicle>().HybridHttpOrThreadLocalScoped().Use<Car>();
    

    Also it is worth mentioning that, if you look at the HttpContext inside the main thread, it is available; it is not null. But it is not available for child threads; HttpContext is null.

    You can't find any difference in the log result, but the objects created for HttpContext are cached. Objects created for thread local scope are not cached.

1 comment: