Over the past couple weeks, on other projects that also leverage VS-MEF in much the same we we do in NuPattern, we started experiencing serious performance issues. After much spelunking and profiling, I came to the conclusion that there was a reason why
VS-MEF extensibility never made it to PublicAssemblies, why it's not versioned with the same strategy as the other stable parts of VS (using binding redirects and changed assembly file names with their version post-fixed): it is simply not ready for use.
The VS-created composition containers have been basically rewritten down to the core: it's almost as if it's MEF just in the use of the core attributes and abstract base classes. The internal implementation is a completely different beast altogether. And not
the kind of awe-inspiring beasts, but the "run away fast!" kind. I should have listened to my internal warnings that if it was so complicated, it was probably not worth it.
Anyway, in the last few days I completed transitioning Clide (http://github.com/clariuslabs/clide
, our modern VS extensibility API) out of that ugly and undocumented VS-MEF dependency using Autofac,
a long-standing and much loved DI framework I had the pleasure of using in the past. The latest version (3.0) stays true to its simply origins, while offering extensibility and flexibility that makes MEF look laughable in that regard.
I could achieve the same level of "declarative-ness" using attributes for registration and composition, for lifetime annotations (shared vs non-shared components), multi-level scoping of registrations and lifetime, and much more. A real pleasure to
My guess is that our current implementation is the culprit of much of my random VS slowdowns on build, type, debug and many more. A multitude of components and plugins importing (say) SVsServiceProvider multiple times are effectively causing our own composition
containers to also run queries over all registered types (library components, runtime, services, ALL of it) all the time!
So, issue #1 that will be solved, is Performance
Issue #2 is versioning of the Library
MEF is a string-based type-name-based composition container, meaning that there can only be ONE such contract for a type if you expect to be able to resolve a single instance of it (i.e. resolve a command from its type + assembly simple name like we do now).
In addition, we rely on VS to do the assembly loading, which (again) will load the library project a single time, which we currently distribute together with the runtime.
This was fine as a stopgap measure, but if we really want a huge library of reusable components, the library release simply cannot be tied to new NuPattern runtime releases. The bar for contributing to the library currently is way too high: someone has to understand
and is expected to work with a solution containing several projects, a significant time to build, and a release process which is painful for everyone (get NuPattern committers to release a new Runtime, which also typically will mean releasing a new Authoring
drop, and only after both are public, update your toolkit dependency so that you can now use that nice reusable command you needed). . This totally discourages contribution to a pool of reusable components and instead fosters creation of private libraries
of components (even if they are generic) for the toolkits I use and control, just so as not to depend on external releases of NuPattern.
I've talked to Jezz in the past about the need to split the library into a separate project hosted on GitHub, with very low barrier to contribution:
- Just send pull request
- We one-click merge via web
- Components documentation is generated automatically and posted as wiki pages on GitHub
- MyGet.org picks up the merge commit and automatically builds a new nuget package
- At this point contributor can already subscribe to the "CI" nuget package source and start using the command right-away
- After some use (or immediately?) we can one-click publish to nuget.org from myget.org
So all in all, this could be a very fast turnaround for contributions, making it more likely that people contribute their components to the library.
In order for this to work, though, each toolkit must own its own version of the library, and we must resolve references to reusable components by using the toolkit-distributed binary, rather than a runtime-distributed one (maybe as a fallback?). And for this
to work, it's absolutely essential to get rid of the VS-MEF stuff. I've tested this multi-version library approach with Autofac and have a spike that works flawlessly. Each toolkit would gets its own "scope" within Autofac, meaning they can have
their own additional component registrations that add on top of global ones. And there is very good and deterministic disposal of those components too when things are unloaded, so it's safe too.
So, issue #2 of Library Versioning
would be solved with this.
A third problem arises now: in the Authoring experience, we're (ab)using MEF to discover components to show (commands, events, etc.). This needs to go, obviously. We should have never used it in the first place, since now we have that weird model where dependencies
must be marked [Import(AllowDefault = true)] which is completely unintuitive and error-prone.
My proposal for this is that we just expose the actual types the toolkit project can access via references: either project references or library references (such as the reusable library nuget package). And we use our reflection-based provider exclusively, and
drop MEF part of it that we're doing now. This model is more explicit. Users can rely on simply installing a nuget package with components (say someone builds a "TFS Automation Library") and have them ready for use right-away on the authoring environment.
That would increase the likelihood of not having to write code for every single thing you want to automate in VS, eventually.
So, issue 3 is Improving Authoring Type Discovery
Sorry for the very long post, but I had to put it up for consideration.
Note: this could break existing toolkits, since [Export]/[Import] attributes will need to become something else (i.e. [Component]/[Dependency] or the like), but other than that, it should be a mechanical replacement process.
I'm already under way on the library project documentation auto-generation as a wiki. The initial result of that is the
project which allows me to read the XML documentation with reflection augmentation. Next comes markdown generation (likely to be called NuMark ;))