Mef2P4 & MVC3: part scope

Nov 16, 2011 at 6:22 PM

In the current implementation the application scoped container only has access to exports that are marked as application scope. This prevents an application scoped part from having dependencies on other parts that should be transient. Here's an example:

In my app, we have multiple independent modules, and each module has a class that implements IResourceProvider, which tells the system about any resources that the module makes available. There is a ResourceCollection class that takes a dependency on a collection of IResourceProvider objects, and collects all module resources at startup. The ResourceCollection is ApplicationShared, because this process can be time consuming and should only occur once per app lifetime. However the IResourceProvider implementations are only needed during ResourceCollection initialization, and should be discarded asap. But in order for the IResourceProvider collection to have any items when ResourceCollection is created, the IResourceProvider implementations must be ApplicationShared as well.

I have not debugged deep enough to verify that the ApplicationShared objects are in fact still in memory for the life of the application even though there are no references to them, but I assume this is the case, as it is the point of an object being ApplicationShared (correct?).

Is there any way to allow an ApplicationShared object to access transient objects provided by MEF?

Nov 16, 2011 at 6:56 PM

Thanks for getting in touch. This is a 'by design' restriction, but one we're still very much interested in feedback on.

The reason we don't currently allow dependencies to transient components at the application level is that more often than not it leads to subtle bugs and lifetime/sharing problems.

In your example, if the IResourceProvider implementations could be resolved at the application level they could still live for the life of the application even if the ResourceCollection holds no references. This would occur if any of them implemented IDisposable, or if any disposable component imported them. Any of their disposable dependencies could also live on forever regardless of whether ResourceCollection holds any references or not.

The only way to properly free a component at the application level is with CompositionContainer.ReleaseExport(), which is not available to most components.

This is not to say that there are problems with your scenario or implementation, so long as you are aware of all the issues it would be fine - but even so, a developer innocuously adding IDisposable or changing the composition graph down the track might introduce subtle-but-ugly bugs into your application. Over the years working with MEF and other containers we've seen this category of bug occur again and again.

So, our thinking here was to encourage a "pit of success" by never keeping any component alive for the duration of the app unless it is marked explicitly with [ApplicationShared].

Here are the options available to you given the current setup -

  1. Mark the implementations of IResourceProvider as [ApplicationShared], accepting that they will hang around (so long as they don't consume resources.)
  2. Use custom RegistrationBuilder conventions to mark the resource providers as application shared without using the attribute and thus remaining independent of the MVC binaries (we can help to get you started.)
  3. Make ResourceCollection a per-request component; create another [ApplicationShared] component that it can use as a cache; take lazy dependencies on the IResourceProviders and only access/compose them if the cache has not been populated.

It will be interesting to hear how these alternatives look to you. We can appreciate there is a little more work here, but the results are going to be more robust and trouble-free in the long run. Is this workable for you, and what could we do to further close this functionality gap without opening ourselves up to unexpected sharing/leaks?

Hope this is on the right track!

Nick

Nov 16, 2011 at 7:31 PM

Thanks for the quick response. I implemented something close to #3, it works well.

I guess you cannot import Lazy<IEnumerable<T>>, it has to be IEnumerable<Lazy<T>>?

Nov 16, 2011 at 8:34 PM

Correct – only IEnumerable<Lazy<T>> works.

Thanks again for the feedback.

Nick

From: pwideman [email removed]
Sent: Wednesday, November 16, 2011 12:31 PM
To: Nicholas Blumhardt
Subject: Re: Mef2P4 & MVC3: part scope [MEF:279696]

From: pwideman

Thanks for the quick response. I implemented something close to #3, it works well.

I guess you cannot import Lazy<IEnumerable<T>>, it has to be IEnumerable<Lazy<T>>?