Sharing boundaries in Windows 8 application

Sep 1, 2012 at 12:55 AM

I'm working on a Windows 8 application which uses MEF and I'm trying to think of the best way to handle part lifetime.

The application requires you to log in before you can do anything and there's things that I'd like destroyed when the user logs out. I think the sharing boundaries in MEF would be ideal for that, on login you create a bunch of parts, log out destroys them and if you choose to log back in the parts all get created again.

Is this possible in MEF? I've read the documentation on part boundaries but it's not overly clear how you would programmatically start and end boundaries.

Sep 2, 2012 at 8:45 PM

G'day Aaron,

Yep, you certainly can implement this using sharing boundaries. You will still need to trigger the creation of all the parts related to the session, e.g. by using GetExport<...>() somewhere, let me know if this bit's unclear.

First, give the user session a name, let's say "LoginSession".

We'll also create a composition root to represent the session, and share it on a per-session basis:

[Export, Shared("LoginSession")]
public class LoginSession
{
    // TBC...
}

Then, we'll assume that somewhere you have a part controlling login that persists for the life of the application. This may be a main window or something similar, I'll just call it LoginController here as a placeholder:

[Export, Shared]
public class LoginController
{
    [ImportingConstructor]
    public LoginController(
        [SharingBoundary("LoginSession")]
        ExportFactory<LoginSession> sessionFactory)
    {
        // _sessionFactory = sessionFactory;
    }

Now the LoginController.Login() method:

public LoginSession Login()
{
    return _sessionFactory().CreateExport().Value;
}

Notice we return LoginSession rather than the Export<LoginSession> returned from CreateExport(): if you want to have MEF dispose the parts in a LoginSession when it completes, you'll need to keep track of the Export<LoginSession> instead, and Dispose() it when the user logs out. In the code here the GC will pick up the parts instead, without disposing them. I'd usually dispose the Export<LoginSession> explicitly, but it is a personal preference :)

So now the only matter is how parts associated with the session get created.

The simplest and most recommended way is that they're dependencies of LoginSession, e.g.:

[ImportingConstructor]
public LoginSession(
    SomeDependency d,
    SomeOtherDependency e)
{
}

 

The d and e dependencies here, as well as any of their dependencies, will be created within the sharing boundary so long as they're either NonShared (every importer of the dependency within the session gets its own copy) or Shared("LoginSession") in which case all importers within the boundary will get a single copy.

I'd then go and add something like a LoginSession.Show() method to kick the actual session off.

The last possible piece of the puzzle for you may be how to go and use GetExport<T>() to look up other parts within a particular boundary. The simplest way to do this is to have LoginSession accept a dependency of type CompositionContext:

[ImportingConstructor]
public LoginSession(
    CompositionContext cc,
    // Other dependencies...

 

This will be injected with a context specific to the session, so calling GetExport() on it will create or retrieve parts within the boundary. Make sure you don't create a leak by doing this repeatedly for NonShared parts - if you need to create and release an unbounded number of instances of a part within the session, check out http://mef.codeplex.com/wikipage?title=Avoiding%20reference%20%28memory%29%20leaks%20in%20MEF%20for%20Metro%20style%20apps&referringTitle=Documentation

Hope this helps, and good luck!

Nick

Sep 3, 2012 at 4:30 AM

Sounds pretty feasible, so just to make sure I'm tracking appropriately say we step through to the next screen, we'll call it User Details:

public class UserDetailsController {
    [ImportingConstructor]
    public UserDetailsController(
        [Shared("LoginSession")] LoginSession loginSession) {
            //do stuff
        }
}

Would this receive the LoginSession that was created in the previous call to GetExport()?

Sep 3, 2012 at 8:24 PM
Sharing's specified on the export, not the import, so you can omit [Shared(...)] in this case.

Whether this works hinges on how you 'step through to the next screen' - where do you get UserDetailsController from? To work, it needs to be either an import of LoginSession (or a part itself imported by LoginSession), or, retrieved from a CompositionContext imported by LoginSession as shown in the last example in my previous reply.

Just calling GetExport() on the container itself won't get you access to the parts within a login session, as the container has no way to figure out just what login session you're asking for. The trick here is reachability - anything you want to be associated with the login session needs to be reachable via imports from whatever is returned from that initial ExportFactory<T>.CreateExport() call.

HTH! :)

On 3 September 2012 14:30, slace <notifications@codeplex.com> wrote:

From: slace

Sounds pretty feasible, so just to make sure I'm tracking appropriately say we step through to the next screen, we'll call it User Details:

public class UserDetailsController {
    [ImportingConstructor]
    public UserDetailsController(
        [Shared("LoginSession")] LoginSession loginSession) {
            //do stuff
        }
}

Would this receive the LoginSession that was created in the previous call to GetExport()?

Read the full discussion online.

To add a post to this discussion, reply to this email (MEF@discussions.codeplex.com)

To start a new discussion for this project, email MEF@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Sep 3, 2012 at 10:49 PM

Oh right-o. I might make a sample project to see how it would work first ;)