- Request context
- Object's scope
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).
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.
- 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.
- 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
- Log result (log4net log result)
- Explicit call - transient objects - way 1 The following approach works exactly similar way as explained in example-1.
- Registry class
- Explicit call - transient objects - way 2 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
- Singleton objects 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
- Log result (log4net log result)
- Explicit call - Singleton objects This method also creates singleton object across requests as explained in example 4. The only difference is that you can also pass constructor argument.
- Tricky, it creates singleton object 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.
- Transient objects with constructor arguments. 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.
- Singleton per HttpRequest 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.
- Singleton per HttpSession Very similar to example 8, but it creates singleton object per session.
- Thread context The following statement in registry class creates singleton object per thread.
- Hybrid context 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.
public class ApplicationRegistry : Registry { public ApplicationRegistry() { Scan(scanner => { For<IVehicle>().Use<Car>(); }); } }
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); } }
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:
For<IVehicle>().Transient().Use<Car>();
For<IVehicle>().LifecycleIs(new UniquePerRequestLifecycle()).Use<Car>();
For<IVehicle>().Singleton().Use<Car>();
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:
For<IVehicle>().Use(new Car());
For<IVehicle>().Transient().Use(new Car());
For<IVehicle>().Use(context => new Car());
For<IVehicle>().LifecycleIs(new HttpContextLifecycle()).Use<Car>();
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:
For<IVehicle>().LifecycleIs(new HttpSessionLifecycle()).Use<Car>();
For<IVehicle>().LifecycleIs(new ThreadLocalStorageLifecycle()).Use<Car>();
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); } }
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:
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.