Debugging a MEF GetExport/GetExports Issue
I’ve gotten into what feels like a gnarly debugging problem in a MEF application. Debugging MEF can be challenging so I’ll describe the process I went through. Obviously you are unlikely to have an identical problem, but it might help to see the specific questions I asked as I walked through the debugging process.
NOTE: This describes a problem with the import process; problems in the composition process have very good error messages. With composition problems, you’ll often get “no export” errors. This is a little more complex way to drop into the same debugging process.
I had a working application that I needed to overlay a few new concepts on. This included adding my own defaulting system. This meant adding strongly typed metatdata attributes that corresponded to each of my MEF part interfaces. That background explains why I’ve been all over the code and made hundreds of changes since it last worked. And this would have been a pretty tough refactoring to do in bits and pieces.
The symptom of my broken app is that I have an empty ExportCollection on a field defined with an Import attribute. This is nearly identical to getting a no matching part error for a single value with an Import attribute.
Did MEF evaluate the Import?
If MEF hadn’t tried to fill that collection, it would be null (Nothing in VB). Since I have an empty collection, I’m confident that MEF is evaluating the class. If it was a single value, the value would be the default value rather than receiving the error.
Is the assembly in the catalog as expected?
This question more or less corresponds to whether the assembly is being built in the correct location. I happen to be exporting my export container for other reasons, but you can also check this stepping through the code that builds your export container. In my case, both check out so I’m still tracking down the problem.
Does the Export attribute contain exactly what I’m looking for?
MEF contracts are strings. This offers enormous flexibility, but it also means the match between export and import needs to be exact. When you use a type for a contract, it’s a shortcut for adding the fully qualified name. That shortcut is exceedingly valuable because it makes your code strongly typed and avoids typos. However, if you add an Export attribute without including a parameter, the type name is exported. The vast majority of cases the export should be an interface implemented by the class, not the class itself. Thus, I check that my export attribute contains the interface type and that I’m looking for the same interface in the Import and that they both use the type (GetType in VB, typeof in C#).
If you are using a metadata view in your Import…
The export collection is:
Private loopExports As ExportCollection( _
Of ILoopValues, ILoopValuesComposition)
Does the collection contain expected values if you remove the metadata view request?
I stripped out the second type parameter to see if the collection remains empty. This required commenting out a bunch of code because I’m depending on that metadata view. I could also have created a new class for the test.
The result of this check is that the collection is empty only when the metadata view is in place. So at least now I know the problem is in the metadata view.
Does the strongly typed attribute have the MetadataAttributeAttribute?
I’ve made this mistake a few times before and MEF has several similarly named attributes so I double check…. All’s good
Do the property names and types match exactly on the metadata view and attribute?
If you don’t understand how strongly metadata views work, a brief summary of what happens under the hood may help. The attribute supplies property to a dictionary of metadata on the export. The result is identical whether you use multiple MetadataAttributes and add the values with string keys, or you use the same keys as the property names in your strongly typed attribute. When matching exports, the MEF container checks whether the dictionary on an export can provide the entire data for the requested interface.
So, I triple check spelling and other obvious issues that mismatch between the custom attribute and interface. I minimize the possibility for this by using an interface. The primary reason I use an interface is to force future changes in the interface to be made in the attribute. However explicit interface implementations do not need to have the same name as the interface and it is the properties of the attribute, not the interface, used in the dictionary. So a mismatch can still occur:
Public Interface ILoopValuesComposition
ReadOnly Property LoopTypeNames() As String
Public Class LoopValuesCompositionAttribute
Dim mTypeNames As String
Public ReadOnly Property LoopTypeNames() _
As String _
There is no direct relationship between the metadata view used at runtime and the attribute – the relationship is indirect and through the metadata dictionary of the export object. While it is not necessary for correct runtime behavior, I find it very helpful to implement the metadata interface on the attribute. All looks good.
A red herring….
I’m wondering about the inheritance within the interface. My concern is partly due to the fact I know there are some issues with inheritance in MEF Preview 4. I’ll pull that out and anything that depends on it, and see what happens. Voila’ my collection is no longer empty.
I know I’m going to see Glenn Bock this weekend at Alt.NET, so I’ll talk to him about it. However, it doesn’t make much sense for MEF to skip the base class data in the attribute.
Glenn is not aware of any problems around my scenario when I explain it to him. He thinks it should work. And he’s sick and involved in Alt.NET so I don’t sit down to pair with him on Friday night.
What is actually in the dictionary?
I’m a bit embarrassed to admit that I woke up in the middle of the night realizing this was an important question to ask to establish that inheritance was the problem. Then inheritance would be “proven” as the problem, and since Glenn thought it should work it would be a bug and I could work on a redesign until the next build.
Because I can get a list of exports when I remove the strongly typed metadata view from the Import request, I have an opportunity to look directly at the metadata dictionary for each export. I’m confident in what it contains, but the process of debugging is around challenging and double checking these kinds of assumptions.
I put a break point within the class with the failing import. Stopping at this line, I look at the metadata view on the first export. It should have three items, two from the ExportCompositionBase and the LoopTypeNames from the code above. I check the count and it has one: just LoopTypeNames.
OK, now I know why the import request is failing. MEF determines whether the dictionary can fulfill the entire metadata view contract/interface. It can’t. It doesn’t have all the values available. It’s missing exactly the values in the base class, which is actually consistent with an inheritance problem, but happily my brain is stuck on the inheritance of the interface, not the inheritance of the attribute, so I retake ownership of the problem.
In my mind, the question is no longer why MEF doesn’t find the appropriate exports based on metadata view, but why doesn’t my class have the correct metadata view? I know I put on the attribute. Here’s the code:
Public Class BizModelMetadata
See, there’s a LoopValuesComposition attribute!
Some place in meditating on this code, which has my attention as one of the involved places that contains changes since the last time the code worked, I realize “doh!” MEF does not support two strongly typed metadata attributes. In this case BizModelMetadataComposition should derive from LoopValuesComposition and be the only strongly typed metadata attribute. Due to an error in my code, BizModelMetadataComposition contains a different LoopTypeNames property rather than inheriting from LoopValueComposition.
That fixes the problem. But there’s one more thing I need to do (other than writing this blog post). Is this an issue that could be addressed with a change in the tool? In this case I think it is. If it was in a released product, I’d use Connect (get there by Help/Report a Bug in VS 2008). Since it’s a CodePlex project, I use the Issue Tracker feature of CodePlex. I ask the MEF team to consider one of two changes – either merge the data from multiple strongly typed attributes or report a warning/error if you apply two.
If you’re debugging MEF failures, you’re going to have to understand a bit about how MEF works so you can step through its processes. MEF’s strength is the simplicity of just slapping on Export/Import attributes. However, I think it’s naïve to think that’s all you’ll have to understand to use MEF because you simply can’t debug without understanding a bit of MEF plumbing.
MEF debugging takes slightly different thinking than normal debugging. And I find it significantly harder to do. A patient step wise process is a great help. If you step outside this sequence … What’s the next question you need to ask and how are you going to ask it?
I need to return to the red-herring of the inheritance issue, but I’ll do that in another post.