Question

I am writing a WPF MEF application. In the past I have written WPF IoC based applications and structures modules of code using PRISM like so:

  • Shell - Main executable
  • BusinessArea.Module.Interface - Contains all interfaces for Services, ViewModels and Views for the BusinessArea module.
  • BusinessArea.Module - Contains the implementation of PRISM IModule and implements the interfaces in the BusinessArea.Module.Interface project.
  • OtherBusinessArea.Module.Interface - Another module
  • OtherBusinessArea.Module - Another module interface

In the IoC world, each module registers its components with the IoC container when loaded. If I wanted my two modules to reuse components from each other they would simply need to reference one of my interface projects and have the component injected.

However in MEF, I can't really find any good practices or guidelines for separating parts into modules and for this type of inter-module reuse of components. I have five questions:

  1. Should I continue to create interfaces for all my parts.
  2. Should I have one shared container across the application or one per module (Each module is kind of a separate app launched from a Windows 8 style start menu).
  3. If one module wants to use a part in another module how do I maintain the separation.
  4. If one module wants to use a part in another container how do I maintain the separation.
  5. How best to keep performance fast in an application with lots of modules.
Was it helpful?

Solution

  1. Yes, you should. If you export the modules as their own implementation type instead of the interface, your consuming module which imports the module needs to reference the library which contains the module implementation. And one of the main reasons to use IoC is to avoid just this.

  2. Yes, you should have one container. If your module holds the container, you will not be able to export/import this module with it, because you need to have an instance of the module before the container exists. There is no real MEF specific issue here, it is just the same as with Unity or anything else. For your PRISM app, the idea is to seperate the concern of instantiating and wiring up modules in one place, which is the container. The container is created before anything else in the Bootstrapper and then creates the shell, the modules, the services and whatever you need. It can make sense to have other IoC containers in your application which manage the instantiation and referenciation of objects in a completely different context, let's say not for your UI, but for wiring together a complex business object. Also, it could make sense to assemble your modules themselves on construction with MEF in an inner (private) container which the main container doesn't know. Than you have a composite UI with modules which are themselves composite UIs. Think of it thoroughly if you really need that. You easily run into issues with this kind of stuff, for example loading assemblies twice, etc., etc..

  3. Just as before. Module B references the interface project of ModuleA and then imports fields or parameters of type IModuleA. The container will resolve the dependency to inject ModuleA.

  4. As said before, you should really have your architecture straight. If you want to inject dependencies between to modules, they should be in the same container. That's the idea of IoC.

  5. I am working on a complex application with multiple IoC containers. I use MEF for the UI, that's the shell and a couple of UI modules. For the more business logic related stuff, I use AutoFac IoC containers. Mainly, because Autofac is a 'real' IoC container and MEF isn't, but also because it's much faster. Autofac can do anything that MEF can, next time I would use Autofac instead of MEF for the UI as well.

Lot's of questionsfor a question....

And here is the answer to a similar questions I gave a while ago, which hopefully helps you too:

I can only explain the system in principle here, but it might point you in the right direction. There are always numerous approaches to everything, but this is what I understood to be best practice and what I have made very good experiences with:

Bootstrapping

As with Prism and Unity, it all starts with the Bootstrapper, which is derived from MefBootstrapper in Microsoft.Practices.Prism.MefExtensions. The bootstrapper sets up the MEF container and thus imports all types, including services, views, ViewModels and models.

Exporting Views (modules)

This is the part MatthiasG is referring to. My practice is the following structure for the GUI modules:

  • The model exports itself as its concrete type (can be an interface too, see MatthiasG), using [Export(typeof(MyModel)] attribute. Mark with [PartCreationPolicy(CreationPolicy.Shared)] to indicate, that only one instance is created (singleton behavior).

  • The ViewModel exports itself as its concrete type just like the model and imports the Model via constructor injection:

    [ImportingConstructor] public class MyViewModel(MyModel model) { _model = model; }

  • The View imports the ViewModel via constructor injection, the same way the ViewModel imports the Model

  • And now, this is important: The View exports itself with a specific attribute, which is derived from the 'standard' [Export] attribute. Here is an example:

    [ViewExport(RegionName = RegionNames.DataStorageRegion)] public partial class DataStorageView { [ImportingConstructor] public DataStorageView(DataStorageViewModel viewModel) { InitializeComponent(); DataContext = viewModel; } }

The [ViewExport] attribute

The [ViewExport] attribute does two things: Because it derives from [Export] attribute, it tells the MEF container to import the View. As what? This is hidden in it's defintion: The constructor signature looks like this:

public ViewExportAttribute() : base(typeof(UserControl)) {}

By calling the constructor of [Export] with type of UserControl, every view gets registered as UserControl in the MEF container.

Secondly, it defines a property RegionName which will later be used to decide in which Region of your Shell UI the view should be plugged. The RegionName property is the only member of the interface IViewRegionRegistration. The attribute class:

/// <summary>
/// Marks a UserControl for exporting it to a region with a specified name
/// </summary>
[Export(typeof(IViewRegionRegistration))]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[MetadataAttribute]
public sealed class ViewExportAttribute : ExportAttribute, IViewRegionRegistration
{
    public ViewExportAttribute() : base(typeof(UserControl)) {}

    /// <summary>
    /// Name of the region to export the View to
    /// </summary>
    public string RegionName { get; set; }
}

Importing the Views

Now, the last crucial part of the system is a behavior, which you attach to the regions of your shell: AutoPopulateExportedViews behavior. This imports all of your module from the MEF container with this line:

[ImportMany] 
private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;

This imports all types registered as UserControl from the container, if they have a metadata attribute, which implements IViewRegionRegistration. Because your [ViewExport] attribute does, this means that you import every type marked with [ViewExport(...)].

The last step is to plug the Views into the regions, which the bahvior does in it's OnAttach() property:

/// <summary>
/// A behavior to add Views to specified regions, if the View has been exported (MEF) and provides metadata
/// of the type IViewRegionRegistration.
/// </summary>
[Export(typeof(AutoPopulateExportedViewsBehavior))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class AutoPopulateExportedViewsBehavior : RegionBehavior, IPartImportsSatisfiedNotification
{
    protected override void OnAttach()
    {
        AddRegisteredViews();
    }

    public void OnImportsSatisfied()
    {
        AddRegisteredViews();
    }

    /// <summary>
    /// Add View to region if requirements are met
    /// </summary>
    private void AddRegisteredViews()
    {
        if (Region == null) return;

        foreach (var view in _registeredViews
            .Where(v => v.Metadata.RegionName == Region.Name)
            .Select(v => v.Value)
            .Where(v => !Region.Views.Contains(v)))
            Region.Add(view);

    }

    [ImportMany()] 
    private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;
}

Notice .Where(v => v.Metadata.RegionName == Region.Name). This uses the RegionName property of the attribute to get only those Views that are exported for the specific region, you are attaching the behavior to.

The behavior gets attached to the regions of your shell in the bootstrapper:

protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors() { ViewModelInjectionBehavior.RegionsToAttachTo.Add(RegionNames.ElementViewRegion);

var behaviorFactory = base.ConfigureDefaultRegionBehaviors();
behaviorFactory.AddIfMissing("AutoPopulateExportedViewsBehavior", typeof(AutoPopulateExportedViewsBehavior));

}

We've come full circle, I hope, this gets you an idea of how the things fall into place with MEF and PRISM.

And, if you're still not bored: This is perfect:

Mike Taulty's screencast

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top