Sofa and MEF !


What is AvalonDock ?
AvalonDock is a WPF controls library which can be used to create a docking layout system like that is present in Visual Studio.
It supports fly-out panes, floating windows, multiple docking manager in same window, styles and themes and it can host WinForms controls.

See more at the AvalonDock web site,

What is Sofa?
Sofa wraps AvalonDock and provides:
  • Easier programming
  • Enhanced features
  • Additional features
One of those features are built-in functionalities that make Sofa MEF-compatible.
See more at the Sofa web site,

What problems does MEF solve?
MEF presents a simple solution for the runtime extensibility problem.
MEF provides a standard way for the host application to expose itself and consume external extensions.
MEF offers a set of discovery approaches for your application to locate and load available extensions.
MEF allows tagging extensions with additonal metadata which facilitates rich querying and filtering.

See more at the MEF web site.

There is a symmetry between the docking layout of Sofa/Avalon and the MEF component architecture : MEF will allow integration of external components that will easily be represented as tabbed or splited windows.

The MEF XFileExplorer example
The example below is based on the MEF FileExplorer which can be found in the MEF preview download (MEF\MEF2_Preview2\Samples\CS\XFileExplorer) and has been "translated" into a set of Sofa components.
It is a basic file explorer with its treeview, its contents pane and 3 additional functionalities, a size pane showing a graphical representation of file sizes, a preview pane and favorites.
It is made of a main component named Shell and one "sub-component" for each functionality.
MEF Explorer_small.jpg

Once the application is translated into Sofa components and runs in the Sofa container its appearance is not that different.
You can find it as a Component of the SofaExplorer 1.1 example on the download page of the Sofa web site.
Sofa Explorer_small.jpg

But the user can now customize it:
Sofa Explorer2_small.jpg

Another additional feature is the XFileExplorer is now a set of Sofa components and it becomes easy to manage many instances of each set using Sofa perspectives (top left menu). A default filepath is associated to each perspective and allows managing filepathes favorites.

The main goal of this documentation is however not to demonstrate hosting an application as a Sofa component can easily provide additional functionalities: It is to show how MEF concepts and Sofa/AvalonDock concepts fits to each other and are easy to implement.

Micro MEF tutorial
The way MEF works is very simple: MEF components, called Composable Parts, declare they are composable parts:
[Export("Microsoft.Samples.XFileExplorer.FileExplorerViewContract", typeof(UserControl))]
public partial class PreviewView : UserControl
{
    ...
Then the host application registers composable parts using the CompositionContainer.ComposePart() method: In this example all Composable Parts that are in the executing assembly will be registered:
private void Compose()
{
    var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
} 
The XFileExplorer example uses a different object, the CompositionBatch, but basically it is the same work.

At last the hosting application can retrieve composable parts: For instance in the XFileExplorer the 2 following lines will make the _views object automatically loaded by MEF at run time.
[ImportMany("Microsoft.Samples.XFileExplorer.FileExplorerViewContract", AllowRecomposition=true)]
private Lazy<UserControl, IFileExplorerViewMetadata>[] _views = null;

Sofa and MEF
When the MEF hosting application becomes a Sofa component it is no longer an application but a UserControl. And it looses its App.xaml.cs file where the Compose method is usually stored. This piece of code has to be moved to the main component. It is Shell.xaml.cs in our example.

Sofa has a central repository where all component running in the container are stored. This allows retrieving components registered in the Sofa container instead of components of the executing assembly:
private bool Compose()
{
    var catalog = new AggregateCatalog();                                                        

    //Use Sofa component list
    foreach (CmpModel cmp in Container.SofaContainer.CmpModelList)
    {
        catalog.Catalogs.Add(new AssemblyCatalog(cmp.ControlDllName));            
        ...
Then Sofa components (ie MEF composable parts) must be loaded in AvalonDock.

The usual way of loading a Sofa component is to use the SofaContainer.OpenComponent method. This creates a new instance of the component and loads it into an AvalonDock window. The return value is an object storing information about the component.
cmpLoaded = Container.SofaContainer.OpenComponent("ComponentName")

When using MEF instances of components are already created. The AddComponent method replaces the OpenComponent method: It uses an already instanciated object.

A cmpModel object is created to store parameters defining the graphical behaviour of the component.
cmpLoaded = Container.SofaContainer.AddComponent(ComponentInstance, cmpModel);

For instance in Shell.xaml.cs:
private void ReloadAllViews()
{
    …

    //_views stores MEF objects          
    foreach (var view in _views.OrderBy(i => i.Metadata.DockId))
    {
        //Get the instance of current view
        var childPane = view.Value;
        …

        //Prepare a CmModel object as parameter for the AddComponent method
        CmpModel cmpModel = new CmpModel();
        …

        switch (view.Metadata.Docking)
        {
            case Dock.Top:
                …
                cmpModel.AvalonPaneGroup = "TopSide";
                cmpLoaded = Container.SofaContainer.AddComponent(childPane, cmpModel); 
And that's it. All the rest of the code of the original example is not impacted when running as a Sofa component. Some lines of code were added to handle Sofa multi-instance and the default filepath associated to each instance but everything needed to convert a MEF application into a Sofa component using MEF is here.

Sofa not only allows creating MEF enabled applications. It adds an interesting feature to MEF applications: Components are deployed separately.
In the original sample all MEF composable parts must be built in the same directory so that the MEF Compose() method can find them. If a new component is added or modified all the application needs to be re-packaged and re-deployed. And all components of the package are deployed without the user can choose which one he wants.

On the contrary, when using Sofa, MEF components are deployed as Sofa components, separately from each other’s. This gives flexibility in application development and deployment. This may also allow users selecting in a toolset the tool or the version of the tool they want to use.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架