2012-12-21

Asp.net MVC MEF: Per Request Lifetime

,

Like the title says, i want to use MEF in an Asp.Net MVC application and the lifetime of the object has to be exactly the same as the lifetime of the request. In short, i want 1 single unique object that is recreated per request.

In MEF there is a way to control the lifetime of the created object. This is specified the PartCreationPolicy attribute that you can use to decorate the object. In my test project i installed the nuget package i created in this post to incorporate MEF in an Asp.net MVC application. I also created an object called MyCustomObject with an interface IMyCustomObject. This object has only one property called Created that contains the date when the object is created. In the constructor of that object i have but an thread.sleep. This way i am sure that the Created property will be different for every created object.

 public interface IMyCustomObject
{
DateTime Created { get; set; }
}
 [Export(typeof(IMyCustomObject))]    
public class MyCustomObject : IMyCustomObject
{
public DateTime Created { get; set; }

public MyCustomObject()
{
Created =
DateTime.Now;
System.Threading.
Thread.Sleep(1000);
}
}

I also created an object called MyCustomObjectContainer. This object will contain a property of the type IMyCustomObject.

 [Export]   
public class MyCustomObjectContainer
{
[
Import]
public IMyCustomObject MyCustomObject { get; set; }
}


The controller will have 2 properties. The first of type IMyCustomObject aand the other of type MyCustomContainer. In the action method of the controller, i will write the Created value that is found in those 2 properties to the viewbag. That way i will be sure if the application is using the same object.

    [Export]
[
PartCreationPolicy( CreationPolicy.NonShared)]
public class HomeController : Controller
{
[
Import]
public IMyCustomObject MyCustomObject { get; set; }

[
Import]
public MyCustomObjectContainer MyCustomObjectContainer { get; set; }

public ActionResult Index()
{
this.ViewBag.MyCustomtObjectDateCreated = MyCustomObject.Created;
this.ViewBag.MyCustomObjectContainerDateCreated = MyCustomObjectContainer.MyCustomObject.Created;
return View();
}
}

Choosing the right PartCreationPolicy


In MEF there are 3 different type of creation policies that we can choose. More information can be found here.



  • Shared: the part author is telling MEF that at most one instance of the part may exist per container.
  • NonShared: the part author is telling MEF that each request for exports of the part will be served by a new instance of it.
  • Any or not supplied value: the part author allows the part to be used as either “Shared” or “NonShared”.

The Shared Creation policy


We will set Creation policy to Shared and run the application.


image


 


We see that the same instance of the MyCustomOject is used. But when we refresh the page, we will get exactly the same value, over and over again. So this is not the behavior that we want.


The NonShared Creation Policy


We will set the creation policy to NonShared.


image


With every refresh, we will get a different value for the Created property, but everytime that an IMyCustomObject is asked form the MEF container, a new value is created so this is not a solution for my problem.



The solution


We can create a solution for this problem using the Proxy-pattern, HttpContext and ExportFactory<T>.


The ExportFactory<T> class was introduced in MEF 2. It allows us to create new instances and control the lifetime of those instances. This will be  perfect to create the MyCustomObject object since we need to control the lifetime of this object.


The HttpContext contains Http information about an HttpRequest. There is one property that is very interesting  in our case, the Items property. This property is in fact a dictionary where you can insert objects into. This collection is cleared with every request (perfect). We will use this dictionary to add the created MyCustomObject. So every time that this object is requested, we will look in this dictionary. When it is not found, the ExportFactory<T> will create it and add it to the dictionary.


The proxy pattern will be used to make this a transparent as possible.


image


The Implementation


I’ll create an IItemContainer that has one property Container of type IDictionary. Then i’ll create an HttpContextContainer that will implement this interface. This object will map the Container property to the HttpContext.Items property.

public interface IItemContainer
{
IDictionary Container { get; }
}
[Export(typeof(IItemContainer))]
[
PartCreationPolicy(CreationPolicy.Shared)]
public class HttpContextContainer : MEF_Asp.Net_MVC_Per_Request.Models.IItemContainer
{
public IDictionary Container
{
get
{
return HttpContext.Current.Items;
}
}
}

Next I'll create the proxy object (MyCustomObjectPerRequest). This object will contain the IItemContainer and the Exportfactory<T> instances and implement the IMyCustomObject. This object will be served by the MefContainer when something is request of type IMyCustomObject.

[Export(typeof(IMyCustomObject))]
public class MyCustomObjectPerRequest : IMyCustomObject
{
[
Import]
public ExportFactory<MyCustomObject> Factory { get; set; }

[
Import]
public IItemContainer ItemContainer { get; set; }

private IMyCustomObject MyCustomObject
{
get
{
if (null == ItemContainer.Container["MyCustomObject"])
{
ItemContainer.Container[
"MyCustomObject"] = Factory.CreateExport().Value;
}

return (IMyCustomObject)ItemContainer.Container["MyCustomObject"];
}
}

public DateTime Created
{
get
{
return MyCustomObject.Created;
}
set
{
MyCustomObject.Created =
value;
}
}
}

 


We also need to change the ExportAttribute on the MyCustomObject class so that it doesn’t specify a ContractType.

 [Export] 
public class MyCustomObject : IMyCustomObject

The Result


When we know run the application, we will use the same MyCustomObject object per request. When we refresh, we’ll get an other instance.


image





Conclusion


This solution can also be used to use the same instance per Session or to incorporate a caching mechanism.  All that needs to been done is to implement the IItemsContainer and map the Container property. The Visual Studio solution can be downloaded here.

5 comments:

  1. Hi Kenny,

    thx for your amazing blog post. I like your way of using these collections to maintain the lifetime scope of a MEF part.
    Working with HttpContext.Current.Items is no problem. But I would also like to use it with the HttpContext.Current.Session object. The problem I first ran into was that the Session Collection (HttpSessionState) is not compatible with IDictionary.
    I solved this changing the container interface to:
    Object this[String key] { get; set; }

    This works fine. Second problem: MEF accesses the proxy object to early (apparently before HttpApplication.AcquireRequestState Event was fired) in the application lifecycle and therefor HttpContext.Current.Session is null. Any ideas to solve this?

    ReplyDelete
  2. I already have DI, what I want to do is use MEF to provide custom functionality to my clients. At the moment we have a custom view engine that uses the JS, CSS, Views and Master Pages associated with a client and Ninject for DI. At this stage we are planning on using Ninject modules to inject concrete classes based on the user.
    essayswriters.org/dissertation
    Still not a plugin but we have not been able to work out how we can use MEF to do this any better.

    ReplyDelete
  3. Are you looking for home improvement inspiration to strike you? Are you also looking for tips on how to handle restoration work around your home?
    retro4jordanshoes |

    sildenafiledcure |

    storeproductsjp |

    synectic-technology |

    tadalafilblog2013 |

    uktimberland-sale |

    vardenafilinfoblog |

    xanaxanxiety2013 |

    youredmedsinfo |

    zolpidem4sleep |

    ReplyDelete
  4. Have you ever been on your way out the door when you catch a glimpse of your reflection and have the feeling that something is off? Even though your clothes look put together and you like your hair or makeup, something else is necessary.
    youtubev.net |

    perfectwristwatch.com |

    yourfaceis.com |

    yourhautecouture.com |

    renoracepromotion.com |

    crosseyedconsulting.com |

    dehollisterco.com |

    wirelessronin.net |

    connectserviceswest.com |

    kre8ivelance.com |

    3ann-house.com |

    isid.biz |

    servewell.us |

    envisioncentraltexas.com |

    aauw-daw.org |

    camorhino.com |

    apron2apronsisters.com |

    biketowork2013.com |

    midwestmarketingtesting.com |

    experiencetheserenity.com |

    ReplyDelete