Ask Kathleen
Stable Composition in MEF
Learn how to work through debugging challenges posed by stable composition in MEF Preview 7, and get insight into using MEF with Silverlight.
Q: I have a Managed Extensibility Framework (MEF) part that I know is there, but it's not being imported. I can't figure out why. How do you go about debugging missing parts?
A: Stable composition was introduced in MEF Preview 7, and it also appears in beta 2 of .NET 4. For more on MEF and stable composition, see the Ask Kathleen columns "Getting Current on MEF" (October 2009) and "Working with MEF" (April 2009). Stable composition offers stability that keeps applications that are relying on MEF from failing when bugs or deployment errors make a plug-in unusable. However, it also creates significant debugging problems. In your case, the missing part may be due to a number of issues:
- The Import is by name, and the name is incorrect
- The assembly isn't deployed into the expected directory
- The Export attribute is missing, or uses a misspelled name
- The specified contract is an interface existing in different namespaces
- The Export uses a custom metadata attribute and doesn't reset the AllowMultiple attribute usage
- Any of these problems occur in a nested part
- A .NET error occurs when composing a nested part
With stable composition, if a part imports another part that isn't available, no exception is thrown. MEF only exposes valid parts, resulting in a silent failure. Because stable composition causes parts to be missing from the container if there are problems in the nested parts they contain, the first step is to determine where the failure occurs. During development, unit testing is probably the best approach, and to solve deployment problems you can use mefx or a similar tool. I'll cover unit testing first.
Set up a unit test environment that mimics your normal composition (see "Deploying Unit Tests for MEF"). Initialize the container in the ClassInitialize method:
<ClassInitialize()> _
Public Shared Sub MyClassInitialize(
ByVal testContext As TestContext)
Compose()
End Sub
Private Compose()
' Mimic normal composition
Set up non-failing imports for each of the anticipated parts using either ImportMany or Import(AllowDefault:=True):
<ImportMany(Utility.DatabaseNameContract)> _
Private u As IEnumerable(Of String)
<ImportMany()> _
Private y As IEnumerable(Of IPersistenceSync( _
Of IUser, Guid))
<ImportMany()> _
Private z As IEnumerable(Of IUserServices)
Finally, provide tests that assert the anticipated number of parts:
<TestMethod()> _
Public Sub Mef_found_database_name()
Assert.AreEqual(1, u.Count(), _
"DatabaseName not found")
End Sub
...
<TestMethod()> _
Public Sub Mef_found_user_services()
Assert.AreEqual(1, z.Count(), _
"User Services not found")
End Sub
Because the persistence service has an import on the database name, it will fail to appear if the database name isn't exported. Because UserServices has an import of the persistence service, it will fail if the database name isn't exported, the persistence service isn't exported, or the persistence service fails composition. This series of unit tests allow you to determine the level where the failure occurs, and, more importantly, once they're in place they give you confidence that issues are elsewhere in your application, possibly in part deployment.
The mefx tool is available as a sample in Preview 7, also marked as pre-beta 2. It runs from the command line and evaluates a set of parts. You'll find it in the bin/Diagnostics directory of the MEF solution. You can use it to check whether a set of directories will compose as intended. You pass one or more directories along with instructions on the information you'd like to receive. If you're doing custom composition, you may need to pull the relevant parts out of mefx and include them in your application or in a custom tool that matches your custom composition.
There are several reasons a part may fail composition. A .NET error generally causes composition to quietly fail when it occurs in a nested part. This is to keep applications from failing because of a badly behaved part. However, it can make debugging your application difficult. The unit test exposes the underlying problem when it explicitly creates the nested part.
A particularly obtuse issue can occur when custom metadata attributes derive from ExportAttribute:
[MetadataAttribute()]
public class ProcessWrapperAttribute :
ExportAttribute, IProcessWrapperComposition
{
public ProcessWrapperAttribute()
: base(typeof(IProcessWrapper))
{ }
public string Group { get; set; }
}
This custom attribute allows you to hide MEF from implementing programmers. It also allows you to combine the export attribute and the metadata attribute. Implementing programmers merely need to decorate their class with the single descriptive ProcessWrapper attribute:
[ProcessWrapper(Group = "DatabaseScripts")]
This is a very nice enhancement. However, there's some unforeseen quicksand here. ExportAttribute is defined as:
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Field |
AttributeTargets.Property |
AttributeTargets.Method,
AllowMultiple = true, Inherited = false)]
public class ExportAttribute : Attribute
AllowMultiple is set to true so you can export the same class with many contracts-often a useful technique. But when MEF encounters metadata attributes that have AllowMultiple set to true, it anticipates that you may need to store more than one value for a single contract. To accommodate this, MEF stores the metadata as an array, not the declared type. When MEF attempts to match metadata, a type mismatch occurs and the part fails to compose. With stable composition, the part-and any containing parts requiring it-fail to appear.
While that's a rather esoteric reason, the fix is quite simple. Your custom metadata attributes should almost always be decorated with the AttributeUsage attribute resetting AllowMultiple to False:
[MetadataAttribute()]
[AttributeUsage(AttributeTargets.Class,
AllowMultiple = false)]
public class ProcessWrapperAttribute :
ExportAttribute, IProcessWrapperComposition
To save space and illustrate additional ways you can fine-tune the allowed usage of your custom attribute, I've also restricted usage to classes. You can also create an ExportCustomAttribute, which derives from ExportAttribute and resets AllowMultiple to false. The remainder of your custom metadata attribute classes can derive from this ExportCustomAttribute.
Q: I've seen blog posts about MEF that mention PartCreator, PartInitializer and SatisfyImports. What do they do, and why can't I find them in the Visual Studio betas or in MEF Preview 7?
A: You don't see these features because they're only available on Silverlight in the current cycle. These features are part of a new abstraction for managing the container. In an ideal, decoupled world, your code wouldn't only be decoupled from the other parts, it would also be decoupled from the container itself. Using Import and Export attributes, you can remove coupling to the container, except during composition and dynamic part retrieval.
The Silverlight version of Preview 7 introduces a new model, which abstracts the container in both of these scenarios. Instead of directly composing with 20 or so lines code, you simply call the shared-static in C#-PartInitializer.SatisfyImports method. This method passes the class you wish to compose, often the entry class. Imports defined in this class, and other imports and exports accessed during composition, are resolved within the current package. It's really that easy. One line of code instead of 20 is definitely a win.
An example of dynamic part retrieval occurs if you're creating a new invoice page or user control. The GetExportedValue method of the CompositionContainer creates a new instance, assuming the underlying part has a non-shared creation policy. But it explicitly uses the underlying container. The new model introduces a factory class called PartCreator(Of T).
To use this class, create a field for the factory and decorate it with the Import attribute. When you wish to create a new instance, call the CreatePart method of this field:
<Import()> _
Friend userCreator As PartCreator(Of IUser)
Private Sub Create_Click(
ByVal sender As System.Object,
ByVal e As System.Windows.RoutedEventArgs)
Dim userContext = userCreator.CreatePart()
Dim user = userContext.ExportedValue()
This abstraction allows you to create new parts without directly accessing the container. (See the next question for a discussion of MEF scoping rules in Silverlight.)
Importing an instance of PartCreator follows the same rules as Lazy, which means issues created by unfulfilled parts surface when the factory is created. Creation of the actual part is guaranteed not to fail because of unavailable imports.
This is a very nice abstraction. Unfortunately this feature won't be in .NET 4. The release cycles didn't line up to allow it to be included. The MEF team has stated they intend to unify their models, so .NET will eventually support the container abstraction. It just didn't make it into the .NET 4 time frame.
You might need a unified model if you're sharing code between Silverlight and .NET. At this point, if you need shared platform code, you'll have to stick with the direct access to the composition container.
Q: I want to use MEF in Silverlight, but when I do a simple import, I get the following error:
System.FieldAccessException was unhandled
by user code
Do you know what's going on?
A: This is almost certainly because you have an import on a private field. MEF can't import data into a private field because of restrictions Silverlight places on reflection. You can test this by temporarily making imported fields public. If that solves the problem, you'll want a solution that better respects your desired scope.
Silverlight respects the InternalsVisibleTo attribute. Thus, if you give your field assembly wide scope using the Friend scope in VB or internal scope in C#-and you add the InternalsVisibleTo attribute to your AssemblyInfo file-you'll expose the field to MEF:
<Assembly: InternalsVisibleTo( _
"System.ComponentModel.Composition")>
The assembly-wide scope increases the surface area of your class within the assembly and allows other classes to alter the data you intended as private. You can mitigate this by hiding the field from IntelliSense, which can be done by adding an EditorBrowsable attribute to the field:
<Import()> _
<EditorBrowsable(EditorBrowsableState.Never)> _
Friend user As IUser
If you want to ensure that no code outside the class alters the imported field, you can give it private scope and include an importing constructor, but the importing constructor must then be visible to MEF.
Q: Blend 3 opens my project correctly. Visual Studio 2008 doesn't. When I'm debugging, Visual Studio just stops while loading the page. It never displays the Silverlight startup graphic with the circle and the percentage. It's just blank, dead, frozen. What's up?
A: The most likely cause is that Blend is picking a different startup page. Check that the default page is set to the page that contains your Silverlight application, not a blank default page. Visual Studio will also sometimes display a blank screen when the XAML is invalid, but in this case, Blend wouldn't display the application. Sometimes one tool or the other gives better messages pointing you to the solution.
Q: I want to find the core type of a Nullable. How do I do that?
A: There are two scenarios, and it's not clear which situation you're working with. If you have the type available, you can simply check if the return value of Nullable.GetUnderlyingType is null:
private static bool IsNullable(Type type)
{
return Nullable.GetUnderlyingType(type)
!= null;
}
It's a bit trickier if you have a variable and wish to determine whether it's of a generic type. The problem arises because .NET returns information on the underlying type of nullables. For example, IsValueType returns True and the GetType() method returns the underlying type. So, GetUnderlyingType will always return null if you retrieve the type using the GetType method. So, if you call this IsNullable method using GetType, it will always return false:
Console.WriteLine(IsNullable(i.GetType()));
Microsoft MVP Deborah Kurata showed me a solution to this problem, adjusted by Microsoft MVP Joacim Anderson. This approach uses generics to grab the type, rather than using GetType(), and thus sidesteps the issue of using GetType() with nullables:
private static bool IsNullable<T>(T value)
{
var type = typeof(T);
if (type.IsValueType)
{
return (Nullable.GetUnderlyingType(type)
!=null);
}
return false;
}
While GetType() works well in most situations, generic type inference of method parameters offers an alternative approach. In addition to solving this issue, you can use this approach when working with anonymous types.
In Visual Basic, the word GetType is used for both the operator that returns the type (typeof in C#) and the GetType method of the .NET type Type. This can be confusing. The GetType operator is fundamentally different than the method, and it's only the method that returns the underlying type of nullables. The same utility function in VB looks like:
Private Shared Function IsNullable(Of T)( _
ByVal value As T) As Boolean
Dim type = GetType(T)
If (Type.IsValueType) Then
Return (Nullable.GetUnderlyingType(type) _
IsNot Nothing)
End If
Return False
End Function
Q: I'm getting a compiler error when I attempt to convert some C# code containing attributes to Visual Basic. Why doesn't this work?
<AttributeUsage(AttributeTargets.Class, _
AllowMultiple = false)>
Error 1 Name 'AllowMultiple' is not declared.
A: Visual Basic uses := to assign named parameters. The compiler attempts to find a constant named AllowMultiple to compare with the Boolean value False. It can't find a field or property by this name so it produces the error.
Q: I'm getting this error:
Error 67 Argument not specified for parameter
'creationPolicy' of 'Public Sub New(creationPolicy As
System.ComponentModel.Composition.CreationPolicy)'.
The error points to a line of code that obviously has a parameter named creation policy. Even though it's VB, I thought it might be a case problem because the report has a lowercase c and it keeps resetting it to an uppercase C.
<PartCreationPolicy(CreationPolicy:=CreationPolicy.NonShared)> _
A: The problem is there are no named parameters, only positional parameters for this attribute. VB provides a confusing error.