Out of the box, MEF provides you with limited options for defining the lifetime of exported parts. At its most basic you can define either transient or singleton lifetimes (CreationPolicy.NonShared and CreationPolicy.Shared respectively). We have to remember that although MEF can be used as an IoC container, it has been primarily designed to focus on managing composition rather than compile time dependencies. This goes part way to explaining why it doesn’t have broader lifetime management features baked in like other IoC container implementations. In short, out of the box, MEF has a much coarser level of granularity when it comes to to the management of dependencies.

When used in web-projects (either web-forms or MVC based), we will sometimes have the requirement to scope exported parts based on either the lifetime of a HTTP request, or an associated session. For example, we would like to be able to write code along the lines of this:


[Export(typeof(IFoo))]
[WebPartCreationPolicy(WebCreationPolicy.Session)]
public class SessionFoo : IFoo
{ }

Obviously the [WebPartCreationPolicy] attribute is something new, and in this example we’re aiming for one SessionFoo object to exist for the lifetime of the HTTP session. Likewise, we could use the value WebCreationPolicy.Request to indicate that an export exists for the lifetime of the current HTTP request. The good news is that MEF is highly extensible, and our web-scoped lifetime requirements can be easily implemented as a new catalog which simply transforms the part definitions found in another catalog through composition.

The code for this post can be found on my private fork of the MefContrib project (found at GitHub). In this fork I’ve introduced a new project called MefContrib.Web to support these (and other) web-specifc features.

Implementing a new WebScopedCatalog

Once the exports have been marked up with a [WebPartCreationPolicy] attribute, we need to do three things:

  1. Use an existing catalog to include the exported parts in the eventual CompositionContainer. For example, we could use a DirectoryCatalog or an AssemblyCatalog
  2. Pass the catalog into a newly created WebScopedCatalog
  3. Pass the WebScopedCatalog into our CompositionContainer

In web projects, we typically setup our CompositionContainer in the Global.asax. In the following example, we’ll use MVC:


public class MvcApplication : HttpApplication
{
    protected void Application_Start()
    {
       AreaRegistration.RegisterAllAreas();

        RegisterRoutes(RouteTable.Routes);

        var catalog = new WebScopedCatalog(new DirectoryCatalog(Server.MapPath("~\\bin")));
        var container = new CompositionContainer(catalog);

        ControllerBuilder.Current.SetControllerFactory(new CompositionControllerFactory(container));
    }
}

It’s important to note that you can also pass an AggregateCatalog into a new WebScopedCatalog allowing exports from a collection of underlying catalogs to be adorned with web-scoped lifetime semantics. In the above example we then give the CompositionContainer object to an MVC controller factory that knows how to create composed controllers. The WebScopedCatalog queries the parts of the underlying catalog and for any part definition which has a WebCreationPolicy metadata value, creates a new WebScopedComposablePartDefinition object in its place. It then stores a list of these parts unioned with those that didn’t so that it can return them when queried by MEF. Basically it transforms the underlying list of composable part definitions so that where one has a web-creation policy, the ComposablePartDefinition is replaced with the new WebScopedComposablePartDefinition object instead:


public class WebScopedCatalog : ComposablePartCatalog
{
    private readonly IQueryable<ComposablePartDefinition> _parts;

    public WebScopedCatalog(ComposablePartCatalog partCatalog)
    {
        if (partCatalog == null)
            throw new ArgumentNullException("partCatalog");

        var webScopedParts
            = from part in partCatalog.Parts
                let exportDef = part.ExportDefinitions.First()
              where exportDef.Metadata.ContainsKey("WebCreationPolicy")
              select part;

        var nonScopedParts = partCatalog.Parts.Except(webScopedParts);

        _parts
            = webScopedParts.Select(p => new WebScopedComposablePartDefinition(p))
                .Union(nonScopedParts)
                .AsQueryable();
    }

    public override IQueryable<ComposablePartDefinition> Parts
    {
        get { return _parts; }
    }
}

When MEF requires access to the part, the WebScopedComposablePartDefinition object simply inspects the value of the WebCreationPolicy metadata value, and then returns either a WebRequestScopedComposablePart or a WebSessionScopedComposablePart object based on that value:


public class WebScopedComposablePartDefinition : ComposablePartDefinition
{
    private readonly ComposablePartDefinition _partDefinition;

    internal WebScopedComposablePartDefinition(ComposablePartDefinition partDefinition)
    {
        _partDefinition = partDefinition;
    }

    public override ComposablePart CreatePart()
    {
        var policy = (WebCreationPolicy)_partDefinition.ExportDefinitions.First().Metadata["WebCreationPolicy"];

        if (policy == WebCreationPolicy.Request)
            return new WebRequestScopedComposablePart(_partDefinition.CreatePart());
        else
            return new WebSessionScopedComposablePart(_partDefinition.CreatePart());
    }

    ...
}

The implementation of these web-scoped aware composable parts look in either the HTTPContext object’s session or current items dictionaries for a created object and returns the object if one is found. If one isn’t found, it delegates to the underlying ComposablePart to create the object before storing it in the associated dictionary thereby caching the exported object at either the request or session boundary. For example, the WebSessionScopedComposablePart is defined as follows:


public class WebSessionScopedComposablePart : WebScopedComposablePart
{
    internal WebSessionScopedComposablePart(ComposablePart composablePart)
        : base(composablePart)
    { }

    public override object GetExportedValue(ExportDefinition definition)
    {
        string sessionKey = string.Format("__Session_{0}", Key);

        var obj = CurrentHttpContext.Session[sessionKey];

        if (obj != null)
            return obj;

        obj = CreatePart();

        CurrentHttpContext.Session[sessionKey] = obj;

        return obj;
    }
}

Tagged with:
 

29 Responses to Defining Web-scoped parts with MEF

  1. John says:

    Hi! Thanks for the greate acrticle!

    But in similar code _service2 is always null.


    [Export]
    [WebPartCreationPolicy(MefContrib.Web.WebCreationPolicy.Session)]
    Service1
    { [Import(typeof(Service2)]
    Service2 _service2 { get; set; }
    }

    [WebPartCreationPolicy(MefContrib.Web.WebCreationPolicy.Session)]
    Service2
    {

    }

    If I make Service1 nonshared, then it works – _service2 is assigned.


    [Export]
    [PartCreationPolicy( CreationPolicy.NonShared )]
    Service1
    { [Import(typeof(Service2)]
    Service2 _service2 { get; set; }
    }

    [WebPartCreationPolicy(MefContrib.Web.WebCreationPolicy.Session)]
    Service2
    {

    }

    • Tim Roberts says:

      Hi John –

      I think you need to combine both your examples. In your first example, where you say it isn’t working, you appear to be missing the [Export] attribute on Service2. You’ve included the [Export] attribute on Service2 in your second example, which is why I think it’s working (although you’ve then switched to the standard part creation policy instead of my web scoped ones). What you need to do is combine both examples like this:


      [Export]
      [WebPartCreationPolicy(WebCreationPolicy.Session)]
      public class Service1
      {
      [Import]
      public Service2 _service2 { get; set; }
      }

      [Export]
      [WebPartCreationPolicy(WebCreationPolicy.Session)]
      public class Service2
      {
      }

      Hope that fixes it.

  2. John says:

    Sorry for my mistake. The code i’ve provided is not real, it’s similar to the code i’m using in real application. But it reflects the problem. When two services are marked sessioned and one of the services uses another, then the used service is not assigned.

    • Tim Roberts says:

      Hi John –

      Yes, you’re right, there is a bug, and I’ve hopefully fixed it now. I’ve pushed a new commit into my GitHub fork of MefContrib.

      Basically I wasn’t allowing MEF to satisfy the imports of the web-exported parts. I didn’t override two methods correctly, but I’ve just tried out your scenario with two session-scoped exports and it works. Let me know if it works for you.

  3. John says:

    It works for me. Thank you!

  4. Ivan says:

    Hi Tim! Thanks for the great article.

    I’m trying to do dependency injection into my WCF service. I’m having trouble with bootstrapper as it’s regular ASP.NET application. Do you care check this StackOverflow post to see why it doesn’t work?

    http://stackoverflow.com/questions/7085780/trying-to-inject-dependency-into-iis-hosted-wcf-service-using-mef

    Post illustrates a problem, but I guess I understand WHY it happens. Nobody tells my webservice to SatisfyImports and I’m not sure how to do it properly. Ideally I just want MyTestClass being magically instatiated when service class created..

    • Tim Roberts says:

      Hi Ivan –

      As @Daniel said in his Stackoverflow answer, MEF can only satisfy imports on objects that it knows about (either through the process of composition where exports are matched with imports when it creates the object, or when you ask it to via one of the SatisfyXxx() methods on the container). Regardless of all this though, the web-scoped parts that I wrote won’t help you with WCF services however, since the web-scoping works with web-requests.

      All is not lost however, you can make WCF services composable, and I’ve done this many times. You need to create a custom ServiceHost (and if you host your service via a .svc file in IIS, you’ll also need to create a custom ServiceHostFactory). The custom ServiceHost will need to change the InstanceProvider to a custom one that is aware of MEF and your CompositionContainer, and creates the service instance that way rather than the WCF default way. If you keep an eye on my blog, I’ll make a new post that will demonstrate this, and I’ll also look at updating my fork of the MefContrib project on GitHub.

      • Ivan says:

        I’m hosting services in IIS but I don’t use .svc files. I create routes on ASP.NET site like this:

        RouteTable.Routes.Add(
        new ServiceRoute(
        account.AccountId + “/mobile”, new WebServiceHostFactory(), typeof(MobileService)));

        As you see – I’m adding custom URL routes for different accounts – my app is multi-tenant with different DB per customer and I brake it down starting with URL

        Do you have any links on where I can find examples of customer Service Hosts?

      • Ivan says:

        Tim,

        Your advice on WCF stuff really helped me. I’m moving forward and have stuff working already. But I have problem with the same “request scope” feature. It doesn’t work and my problem described here (I’m not using your code at all right now)

        http://stackoverflow.com/questions/7102699/how-to-tell-mef-to-re-create-object

        I think you know exactly what I’m missing and I will appreciate if you can comment on this.

        Thank you,
        Ivan

        • Tim Roberts says:

          I’ve posted an answer. By default MEF satisfies imports using shared instances, so you’ll need to add the standard part creation policy attribute to your exports:


          [PartCreationPolicy(CreationPolicy.NonShared)]

          My original web-exported parts just extend this model with creation policies that reflect web-related lifetimes such as request and session. Good to hear that you’re making progress with the WCF/MEF integration side of things though. I’ll still do a post though – it’ll be an excuse to extend the blog if nothing else!

  5. […] I’m not sure I even bootrstrap it properly.. SECOND, I’m using http://www.timjroberts.com/2011/02/web-scoped-mef-parts/ as a guidance for web-scoped parts. I need that because some injected objects supposed to live […]

  6. John says:

    Hi, Tim, some observations. Before the changes were made, the place where an object was created was
    protected object CreatePart()
    {
    return _composablePart.GetExportedValue(_exportDef);
    }
    and session object was created once in session.
    But now I’m watching the object is always created at this point:
    public override void Activate()
    {
    _composablePart.Activate();
    }
    then we decide either we are going to use the new object or the old one which was created before in the session.

    • Tim Roberts says:

      The WebScopedComposablePart class will always be used when MEF is satisfying imports. The important thing is that the CreatePart() method is only invoked once per session or request. I’m not sure why you’ve highlighted the Activate() method. This method is called by MEF once it has fullfilled the imports that your part requires. This is why it wasn’t working in your original comment (where you had one export importing another).

      If you put a breakpoint on the CreatePart() method in the WebScopedComposablePart base class, it should only be called once per request (for a WebRequestScopedComposablePart), and once per session (for a WebSessionScopedComposablePart).

    • Johnette says:

      Always refreshing to hear a rational anwser.

  7. John says:

    My part requires many times, but I’d like to make it shared for a session. And I’m seeing that part is created many times but used only one instance as we’d like to.

  8. John says:

    sorry, ‘many times’ means for several another parts.

  9. computers says:

    Whoa, this is really fascinating content material. I’m astonished using your creating capabilities. The post is great reading through material. Appreciate expressing your experience.

  10. Patrick says:

    It’s actually quite easy to make this work for WCF. There are two ways to do this:

    1. Enable aspNetCompatibilityMode in your WCF service. There are disadvantages to doing this so be aware of the implications.
    2. If you prefer not to revert to compatibility mode, you can make Tim’s fantastic code here work with WCF sessions fairly easily.

    In WebSessionScopedComposablePart.cs:

    Add field:

    private static ConcurrentDictionary CachedSessionInstances = new ConcurrentDictionary();

    Change GetExportedValue to be:

    public override object GetExportedValue(ExportDefinition definition)
    {
    string sessionKey = string.Format(“__Session_{0}”, Key);

    // HttpContext is only available in ASP.NET-hosted services.
    if (CurrentHttpContext == null)
    {
    return CachedSessionInstances.GetOrAdd(System.ServiceModel.OperationContext.Current.SessionId, (s) => { return CreatePart(); });
    }

    var obj = CurrentHttpContext.Session[sessionKey];

    if (obj != null)
    return obj;

    obj = CreatePart();

    CurrentHttpContext.Session[sessionKey] = obj;

    return obj;
    }

    You’ll want to do the same thing in WebRequestScopedComposablePart.cs

    The one issue I note isn’t addressed here is garbage collection. The reference will continue to exist in the dictionary even when the request/session goes out of scope. This isn’t an issue in the ASP.NET case because the HttpContext.Session object will get collected as well; I’m not sure how to properly dispose of these, short of forcing the service instances to implement IDisposable.

  11. Patrick says:

    I got curious and looked up how to solve this problem of garbage collection. A minute or two of thought and I realized the solution is a WeakReference to the service instance. A quick web search for such a collection turned up this solution:

    http://blogs.msdn.com/b/jaredpar/archive/2009/03/03/building-a-weakreference-hashtable.aspx

    This won’t be thread safe (it may be by accident, but it won’t be explicitly) but that is easily fixed by switching out the Dictionary implementation for a ConcurrentDictionary and adding a couple of the wrappers for ConcurrentDictionary’s convenience methods (GetOrAdd, AddOrUpdate, etc) which will just be simple wrappers around the TryGetValue implementation.

  12. Sindy says:

    Good post. Its realy nice. More information help me.

  13. Hi there, I discovered your blog by way of Google even as looking for a comparable matter, your site came up, it seems great. I have bookmarked to favourites|added to bookmarks.

  14. I appreciate, cause I found just what I was looking for. You’ve ended my 4 day long hunt! God Bless you man. Have a great day. Bye

  15. I appreciate, cause I found exactly what I was looking for. You have ended my 4 day long hunt! God Bless you man. Have a great day. Bye

  16. Awesome blog! Where did you get this design?

  17. I think you have mentioned some very interesting points , regards for the post.

  18. I think you had to be part of ASP.NET’s development team. Man, how did you find out all these things? I mean, diving deep into very lower parts of MEF till you create parts, define new policies, and doing extra stuff, requires more than document reading ;).

    This was of great help to me. Thanks a lot!

Leave a Reply

Your email address will not be published. Required fields are marked *