Chapter 30

Managed Extensibility Framework

WHAT’S IN THIS CHAPTER?

  • Architecture of the Managed Extensibility Framework
  • MEF using Attributes
  • Convention-based Registration
  • Contracts
  • Exports and imports of parts
  • Containers used by hosting applications
  • Catalogs for finding parts

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

The wrox.com code downloads for this chapter are found at http://www.wrox.com/remtitle.cgi?isbn=1118314425 on the Download Code tab. The code for this chapter is divided into the following major examples:

  • Attribute-Based Sample
  • Convention-Based Sample
  • WPFCalculator

INTRODUCTION

Add-ins (or plugins) enable you to add functionality to an existing application. You can create a hosting application that gains more and more functionality over time—such functionality might be written by your team of developers, but different vendors can also extend your application by creating add-ins.

Today, add-ins are used with many different applications, such as Internet Explorer and Visual Studio. Internet Explorer is a hosting application that offers an add-in framework that is used by many companies to provide extensions when viewing web pages. The Shockwave Flash Object enables you to view web pages with Flash content. The Google toolbar offers specific Google features that can be accessed quickly from Internet Explorer. Visual Studio also has an add-in model that enables you to extend Visual Studio with different levels of extensions.

For your custom applications, it has always been possible to create an add-in model to dynamically load and use functionality from assemblies. However, all the issues associated with finding and using add-ins need to be resolved. You can accomplish that automatically using the Managed Extensibility Framework (MEF). The MEF can also be used on a smaller scale. For creating boundaries, MEF helps remove dependencies between parts and the clients or callers that make use of the parts. Such dependencies can also be removed just by using interfaces or delegates. However, MEF also helps in finding the parts by using catalogs, and connecting callers and parts in turn.

The major namespace covered in this chapter is System.ComponentModel.Composition.

MEF ARCHITECTURE

The .NET 4.5 Framework offers two technologies for writing flexible applications that load add-ins dynamically. One is the Managed Extensibility Framework (MEF), which is covered in this chapter. Another technology that has been available since .NET 3.5 is the Managed Add-in Framework (MAF). MAF uses a pipeline for communication between the add-in and the host application that makes the development process more complex but offers separation of add-ins via app-domains or even different processes. In that regard, MEF is the simpler of these two technologies. MAF and MEF can be combined to get the advantage of each, but it doubles the work.

MEF is built with parts and containers, as shown in Figure 30-1. A container finds parts from a catalog; and the catalog finds parts within an assembly or a directory. The container connects imports to exports, thereby making parts available to the hosting application.

Here’s the full picture of how parts are loaded. As mentioned, parts are found within a catalog. The catalog uses exports to find its parts. An export provider accesses the catalog to offer the exports from the catalog. Multiple export providers can be connected in chains for customizing exports—for example, with a custom export provider to only allow parts for specific users or roles. The container uses export providers to connect imports to exports and is itself an export provider.

MEF consists of three large categories: classes for hosting, primitives, and classes for the attribute-based mechanism. Hosting classes include catalogs and containers. Primitive classes can be used as base classes to extend the MEF architecture to use other techniques to connect exports and imports. Of course, the classes that make up the implementation of the attribute-based mechanism with reflection, such as the Export and Import attributes, and classes that offer extension methods that make it easier to work with attribute-based parts are also part of MEF.


NOTE The MEF implementation is based on attributes that specify what parts should be exported and then map these to the imports. However, the technology is flexible and allows for other mechanisms to be implemented by using the abstract base class ComposablePart and extension methods with reflection-based mechanisms from the class ReflectionModelServices.

MEF Using Attributes

Let’s start with a simple example to demonstrate the MEF architecture. The hosting application can dynamically load add-ins. With MEF, an add-in is referred to as a part. Parts are defined as exports and are loaded into a container that imports parts. The container finds parts by using a catalog; and the catalog lists parts.

In this example, a simple console application is created to host calculator add-ins from a library. To create independence from the host and the calculator add-in, three assemblies are required. One assembly, CalculatorContract, holds the contracts that are used by both the add-in assembly and the hosting executable. The add-in assembly SimpleCalculator implements the contract defined by the contract assembly. The host uses the contract assembly to invoke the add-in.

The contracts in the assembly CalculatorContract are defined by two interfaces, ICalculator and IOperation. The ICalculator interface defines the methods GetOperations and Operate. The GetOperations method returns a list of all operations that the add-in calculator supports, and with the Operate method an operation is invoked. This interface is flexible in that the calculator can support different operations. If the interface defined Add and Subtract methods instead of the flexible Operate method, a new version of the interface would be required to support Divide and Multiply methods. With the ICalculator interface as it is defined in this example, however, the calculator can offer any number of operations with any number of operands (code file AttributeBasedSample/CalculatorContract/ICalculator.cs):

using System.Collections.Generic;
namespace Wrox.ProCSharp.MEF
{
  public interface ICalculator
  {
    IList<IOperation> GetOperations();
    double Operate(IOperation operation, double[] operands);
  }
}

The ICalculator interface uses the IOperation interface to return the list of operations and to invoke an operation. The IOperation interface defines the read-only properties Name and NumberOperands (code file AttributeBasedSample/CalculatorContract/IOperation.cs):

namespace Wrox.ProCSharp.MEF
{
  public interface IOperation
  {
    string Name { get; }
    int NumberOperands { get; }
  }
}

The CalculatorContract assembly doesn’t require any reference to MEF assemblies. Only simple .NET interfaces are contained within it.

The add-in assembly SimpleCalculator contains classes that implement the interfaces defined by the contracts. The class Operation implements the interface IOperation. This class contains just two properties as defined by the interface. The interface defines get accessors of the properties; internal set accessors are used to set the properties from within the assembly (code file AttributeBasedSample/SimpleCalculator/Operation.cs):

namespace Wrox.ProCSharp.MEF
{
  public class Operation : IOperation
  {
    public string Name { get; internal set; }
    public int NumberOperands { get; internal set; }
  }
}

The Calculator class provides the functionality of this add-in by implementing the ICalculator interface. The Calculator class is exported as a part as defined by the Export attribute. This attribute is defined in the System.ComponentModel.Composition namespace in the System.ComponentModel.Composition assembly (code file AttributeBasedSample/SimpleCalculator/Calculator.cs):

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
namespace Wrox.ProCSharp.MEF
{
  [Export(typeof(ICalculator))]
  public class Calculator : ICalculator
  {
    public IList<IOperation> GetOperations()
    {
      return new List<IOperation>()
      {
        new Operation { Name="+", NumberOperands=2},
        new Operation { Name="-", NumberOperands=2},
        new Operation { Name="/", NumberOperands=2},
        new Operation { Name="*", NumberOperands=2}
      }; 
    }
    public double Operate(IOperation operation, double[] operands)
    {
      double result = 0;
      switch (operation.Name)
      {
        case "+":
          result = operands[0] + operands[1];
          break;
        case "-":
          result = operands[0] - operands[1];
          break;
        case "/":
          result = operands[0] / operands[1];
          break;
        case "*":
          result = operands[0] * operands[1];
          break;
        default:
          throw new InvalidOperationException(String.Format(
              "invalid operation {0}", operation.Name));
      }
      return result;
    }
  }
}

The hosting application is a simple console application. The add-in uses an Export attribute to define what is exported; with the hosting application, the Import attribute defines what is used. Here, the Import attribute annotates the Calculator property that sets and gets an object implementing ICalculator. Therefore, any calculator add-in that implements this interface can be used here (code file AttributeBasedSample/SimpleHost/Program.cs):

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using Wrox.ProCSharp.MEF.Properties;
namespace Wrox.ProCSharp.MEF
{
  class Program
  {
    [Import]
    public ICalculator Calculator { get; set; }

In the entry method Main of the console application, a new instance of the Program class is created, and then the Run method invoked. In the Run method, a DirectoryCatalog is created that is initialized with the AddInDirectory, which is configured in the application configuration file. Settings.Default.AddInDirectory makes use of the project property Settings to use a strongly typed class to access a custom configuration.

The CompositionContainer class is a repository of parts. This container is initialized with the DirectoryCatalog to get the parts from the directory that is served by this catalog. ComposeParts is an extension method that extends the class CompositionContainer and is defined with the class AttributedModelServices. This method requires parts with an Import attribute passed with the arguments. Because the Program class has an Import attribute with the property Calculator, the instance of the Program class can be passed to this method. With the implementation for the imports, exports are searched and mapped. After a successful call of this method, exports mapped to the imports can be used. If not all imports can be mapped to exports, an exception of type ChangeRejectedException is thrown, which is caught to write the error message and to exit from the Run method:

    static void Main()
    {
      var p = new Program();
      p.Run();
    }
    public void Run()
    {
      var catalog = new DirectoryCatalog(Settings.Default.AddInDirectory);
      var container = new CompositionContainer(catalog);
      try
      {
        container.ComposeParts(this);
      }
      catch (ChangeRejectedException ex)
      {
        Console.WriteLine(ex.Message);
        return;
      }

With the Calculator property, the methods from the interface ICalculator can be used. GetOperations invokes the methods of the previously created add-in, which returns four operations. After asking the user what operation should be invoked and requesting the operand values, the add-in method Operate is called:

      var operations = Calculator.GetOperations();
      var operationsDict = new SortedList<string, IOperation>();
      foreach (var item in operations)
      {
        Console.WriteLine("Name: {0}, number operands: {1}", item.Name, 
            item.NumberOperands);
        operationsDict.Add(item.Name, item);
      }
      Console.WriteLine();
      string selectedOp = null;
      do
      {
        try
        {
          Console.Write("Operation? ");
          selectedOp = Console.ReadLine();
          if (selectedOp.ToLower() == "exit" || !operationsDict.ContainsKey(selectedOp))
            continue;
          var operation = operationsDict[selectedOp];
          double[] operands = new double[operation.NumberOperands];
          for (int i = 0; i < operation.NumberOperands; i++)
          {
            Console.Write("	 operand {0}? ", i + 1);
            string selectedOperand = Console.ReadLine();
            operands[i] = double.Parse(selectedOperand);
          }
          Console.WriteLine("calling calculator");
          double result = Calculator.Operate(operation, operands);
          Console.WriteLine("result: {0}", result);
        }
        catch (FormatException ex)
        {
          Console.WriteLine(ex.Message);
          Console.WriteLine();
          continue;
        }
      } while (selectedOp != "exit");       
    }
  }
}

The output of one sample run of the application is shown here:

Name: +, number operands: 2
Name: -, number operands: 2
Name: /, number operands: 2
Name: *, number operands: 2
Operation? +
         operand 1? 3
         operand 2? 5
calling calculator
result: 8
Operation? -
         operand 1? 7
         operand 2? 2
calling calculator
result: 5
Operation? exit

Without recompiling the host application, it is possible to use a completely different add-in library. The assembly AdvCalculator defines a different implementation for the Calculator class to offer more operations. This calculator can be used in place of the other one by copying the assembly to the directory that is specified by the DirectoryCatalog in the hosting application (code file AttributeBasedSample/SimpleCalculator/Calculator.cs):

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
namespace Wrox.ProCSharp.MEF
{
  [Export(typeof(ICalculator))]
  public class Calculator : ICalculator
  {
    public IList<IOperation> GetOperations()
    {
      return new List<IOperation>()
      {
        new Operation { Name="+", NumberOperands=2},
        new Operation { Name="-", NumberOperands=2},
        new Operation { Name="/", NumberOperands=2},
        new Operation { Name="*", NumberOperands=2},
        new Operation { Name="%", NumberOperands=2},
        new Operation { Name="++", NumberOperands=1},
        new Operation { Name="--", NumberOperands=1}
      };
    }
        public double Operate(IOperation operation, double[] operands)
        {
            double result = 0;
            switch (operation.Name)
            {
                case "+":
                    result = operands[0] + operands[1];
                    break;
                case "-":
                    result = operands[0] - operands[1];
                    break;
                case "/":
                    result = operands[0] / operands[1];
                    break;
                case "*":
                    result = operands[0] * operands[1];
                    break;
                case "%":
                    result = operands[0] % operands[1];
                    break;
                case "++":
                    result = ++operands[0];
                    break;
                case "--":
                    result = --operands[0];
                    break;
                default:
                    throw new InvalidOperationException(
                        String.Format("invalid operation {0}", operation.Name));
            }
            return result;
        }
    }
}

Now you’ve seen imports, exports, and catalogs from the MEF architecture. The next section takes a look at a new feature of .NET 4.5: convention-based part registration.

Convention-Based Part Registration

A new feature of MEF with .NET 4.5 is convention-based part registration. It’s no longer required to use attributes with the exported parts. One scenario in which this is useful is when you don’t have access to the source code of classes that should be used as parts to add attributes. Another scenario is when you want to remove the need for a user of your library to deal with attributes for imports. ASP.NET MVC 4 makes use of MEF with convention-based registration. This technology is based on the Model View Controller (MVC) pattern, and uses controller classes with the suffix Controller, which is a naming convention to find controllers.


NOTE ASP.NET MVC is discussed in Chapter 41, “ASP.NET MVC.”


NOTE Convention-based part registration requires an additional reference to the assembly System.ComponentModel.Composition.Registration.

This introduction to convention-based part registration builds the same example code shown previously using attributes, but attributes are no longer needed; therefore, the same code is not repeated here. The same contract interfaces ICalculator and IOperation are implemented, and nearly the same part with the class Calculator. The difference with the Calculator class is that it doesn’t have the Export attribute applied to it.

Creating the host application, all this becomes more interesting. Similar to before, a property of type ICalculator is created as shown in the following code snippet—it just doesn’t have an Import attribute applied to it. In the Main method, a new instance of Program is created because the Calculator property is an instance property of the Program class (code file ConventionBasedSample/SimpleHost/Program.cs):

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
 
namespace Wrox.ProCSharp.MEF
{
  public class Program
  {
    public ICalculator Calculator { get; set; }
 
    static void Main()
    {
      var p = new Program();
      p.Run();
    }

Within the Run method, a new RegistrationBuilder instance is created. This type is needed to define conventions for export and import. With the sample code, an export is defined for all the types that derive from the interface type ICalculator, and the export is of type ICalculator by calling the method conventions.ForTypesDerivedFrom<ICalculator>.Export<ICalculator>. This is similar to applying the attribute Export[typeof(ICalculator)] to all types that implement the interface ICalculator. For mapping the exported type to the Calculator property, ForType<Program> specifies a convention for the type Program, and the ImportProperty<ICalculator> method maps an import to the specified property. Similar to the previous sample, a DirectoryCatalog is created to find the parts in a specific directory. The constructor of the DirectoryCatalog allows passing the RegistrationBuilder to provide information about the conventions. The search in the directory starts by invoking the SatisfyImportOnce method of the CompositionSerivce. With this method invocation, the property Calculator is assigned, and now the methods of the Calculator can be invoked:

    public void Run()
    {
     var conventions = new RegistrationBuilder();
     conventions.ForTypesDerivedFrom<ICalculator>().Export<ICalculator>();
     conventions.ForType<Program>().ImportProperty<ICalculator>(p => p.Calculator);
 
     var catalog = new DirectoryCatalog(
         Properties.Settings.Default.AddInDirectory, conventions);
 
     using (CompositionService service = catalog.CreateCompositionService())
     {
       service.SatisfyImportsOnce(this, conventions);
     }
 
     CalculatorLoop();
    }

As you’ve seen, the RegistrationBuilder is the heart of convention-based part registration and MEF. It uses a fluent API and offers all the flexibility you’ll see with attributes as well. Conventions can be applied to a specific type with ForType; or for types that derive from a base class or implement an interface, ForTypesDerivedFrom. ForTypesMatching enables specifying a flexible predicate. For example, ForTypesMatching(t => t.Name.EndsWith("Controller")) applies a convention to all types that end with the name Controller.

The methods to select the type return a PartBuilder. With the PartBuilder, exports and imports can be defined, as well as metadata applied. The PartBuilder offers several methods to define exports: Export to export a specific type, ExportInterfaces to export a list of interfaces, and ExportProperties to export properties. Using the export methods to export multiple interfaces or properties, a predicate can be applied to further define a selection. The same applies to importing properties or constructors with ImportProperty, ImportProperties, and SelectConstructors.

Having briefly looked at the two ways of using MEF with attributes and conventions, the next section digs into the details by using a WPF application to host add-ins.

DEFINING CONTRACTS

The following sample application extends the first one. The hosting application is a WPF application that loads calculator add-ins for calculation functionality, and other add-ins that bring their own user interface into the host.


NOTE For more information about writing WPF applications, see Chapter 35, “Core WPF,” and Chapter 36, “Business Applications with WPF.”

For the calculation, the same contracts that were defined earlier are used: ICalculator and IOperation. Added to this example is another contract, ICalculatorExtension. This interface defines the UI property that can be used by the hosting application. The get accessor of this property returns a FrameworkElement. This enables the add-in to return any WPF element that derives from FrameworkElement to be shown as the user interface within the host application (code file WPFCalculator/CalculatorContract/ICalculator.cs):

using System.Windows;
namespace Wrox.ProCSharp.MEF
{
  public interface ICalculatorExtension
  {
    FrameworkElement UI { get; }
  }
}

.NET interfaces are used to remove the dependency between one that implements the interface and one that uses it. This way, a .NET interface is also a good contract for MEF to remove a dependency between the hosting application and the add-in. If the interface is defined in a separate assembly, as with the CalculatorContract assembly, the hosting application and the add-in don’t have a direct dependency. Instead, the hosting application and the add-in just reference the contract assembly.

From a MEF standpoint, an interface contract is not required at all. The contract can be a simple string. To avoid conflicts with other contracts, the name of the string should contain a namespace name—for example, Wrox.ProCSharp.MEF.SampleContract, as shown in the following code snippet. Here, the class Foo is exported by using the Export attribute, and a string passed to the attribute instead of the interface:

  [Export("Wrox.ProCSharp.MEF.SampleContract")]
  public class Foo
  {
    public string Bar()
    {
      return "Foo.Bar";
    }
  }

The problem with using a contract as a string is that the methods, properties, and events provided by the type are not strongly defined. Either the caller needs a reference to the type Foo to use it, or .NET reflection can be used to access its members. The C# 4 dynamic keyword makes reflection easier to use and can be very helpful in such scenarios.

The hosting application can use the dynamic type to import a contract with the name Wrox.ProCSharp.MEF.SampleContract:

    [Import("Wrox.ProCSharp.MEF.SampleContract")]
    public dynamic Foo { get; set; }

With the dynamic keyword, the Foo property can now be used to access the Bar method directly. The call to this method is resolved during runtime:

        string s = Foo.Bar();

Contract names and interfaces can also be used in conjunction to define that the contract is used only if both the interface and the contract name are the same. This way, you can use the same interface for different contracts.


NOTE The dynamic type is explained in Chapter 12, “Dynamic Language Extensions.”

EXPORTING PARTS

The previous example showed the part SimpleCalculator, which exports the type Calculator with all its methods and properties. The following example contains the SimpleCalculator as well, with the same implementation that was shown previously; and two more parts, TemperatureConversion and FuelEconomy, are exported. These parts offer a UI for the hosting application.

Creating Parts

The WPF User Control library named TemperatureConversion defines a user interface as shown in Figure 30-2. This control provides conversion between Celsius, Fahrenheit, and Kelvin scales. With the first and second combo box, the conversion source and target can be selected. The Calculate button starts the calculation to do the conversion.

The user control has a simple implementation for temperature conversion. The enumeration TempConversionType defines the different conversions that are possible with that control. The enumeration values are shown in the two combo boxes by setting the DataContext property of the user control in the constructor. The method ToCelsiusFrom converts the argument t from its original value to Celsius. The temperature source type is defined with the second argument, TempConversionType. The method FromCelsiusTo converts a Celsius value to the selected temperature scale. The method OnCalculate is the handler of the Button.Click event and invokes the ToCelsiusFrom and FromCelsiusTo methods to do the conversion according to the user’s selected conversion type (code file WPFCalculator/TemperatureConversion/TemperatureConversion.xaml.cs):

using System;
using System.Windows;
using System.Windows.Controls;
namespace Wrox.ProCSharp.MEF
{
  public enum TempConversionType
  {
    Celsius,
    Fahrenheit,
    Kelvin
  }
  public partial class TemperatureConversion : UserControl
  {
    public TemperatureConversion()
    {
      InitializeComponent();
      this.DataContext = Enum.GetNames(typeof(TempConversionType));
    }
    private double ToCelsiusFrom(double t, TempConversionType conv)
    {
      switch (conv)
      {
        case TempConversionType.Celsius:
          return t;
        case TempConversionType.Fahrenheit:
          return (t - 32) / 1.8;
        case TempConversionType.Kelvin:
          return (t - 273.15);
        default:
          throw new ArgumentException("invalid enumeration value");
      }
    }
    private double FromCelsiusTo(double t, TempConversionType conv)
    {
      switch (conv)
      {
        case TempConversionType.Celsius:
          return t;
        case TempConversionType.Fahrenheit:
          return (t * 1.8) + 32;
        case TempConversionType.Kelvin:
          return t + 273.15;
        default:
          throw new ArgumentException("invalid enumeration value");
      }
    }
    private void OnCalculate(object sender, System.Windows.RoutedEventArgs e)
    {
      try
      {
        TempConversionType from;
        TempConversionType to;
        if (Enum.TryParse<TempConversionType>(
          (string)comboFrom.SelectedValue, out from) &&
               Enum.TryParse<TempConversionType>(
                   (string)comboTo.SelectedValue, out to))
        {
          double result = FromCelsiusTo(
              ToCelsiusFrom(double.Parse(textInput.Text), from), to);
          textOutput.Text = result.ToString();
        }
      }
      catch (FormatException ex)
      {
        MessageBox.Show(ex.Message);
      }
    }
  }
}

So far, this control is just a simple WPF user control. To create a MEF part, the class TemperatureCalculatorExtension is exported by using the Export attribute. The class implements the interface ICalculatorExtension to return the user control TemperatureConversion from the UI property (code file WPFCalculator/TemperatureConversion/TemperatureCalculatorExtension.cs):

using System.ComponentModel.Composition;
using System.Windows;
namespace Wrox.ProCSharp.MEF
{
  [Export(typeof(ICalculatorExtension))]
  public class TemperatureCalculatorExtension : ICalculatorExtension
  {
    private TemperatureConversion control;
    public FrameworkElement UI
    {
      get
      {
        return control ?? (control = new TemperatureConversion());
      }
    }  
  }
}

The second user control that implements the interface ICalculatorExtension is FuelEconomy. With this control, either miles per gallon or liters per 100 km can be calculated. The user interface is shown in Figure 30-3.

The next code snippet shows the class FuelEconomyViewModel, which defines several properties that are bound from the user interface, such as a list of FuelEcoTypes that enables the user to select between miles and kilometers, and the Fuel and Distance properties, which are filled by the user:

using System.Collections.Generic;
 
namespace Wrox.ProCSharp.MEF
{
  public class FuelEconomyViewModel : BindableBase
  {
    public FuelEconomyViewModel()
    {
      InitializeFuelEcoTypes();
    }
 
    private List<FuelEconomyType> fuelEcoTypes;
    public List<FuelEconomyType> FuelEcoTypes
    {
      get
      {
        return fuelEcoTypes;
      }
    }
 
    private void InitializeFuelEcoTypes()
    {
      var t1 = new FuelEconomyType
      {
        Id = "lpk",
        Text = "L/100 km",
        DistanceText = "Distance (kilometers)",
        FuelText = "Fuel used (liters)"
      };
      var t2 = new FuelEconomyType
      {
        Id = "mpg",
        Text = "Miles per gallon",
        DistanceText = "Distance (miles)",
        FuelText = "Fuel used (gallons)"
      };
      fuelEcoTypes = new List<FuelEconomyType>() { t1, t2 };
    }
 
    private FuelEconomyType selectedFuelEcoType;
 
    public FuelEconomyType SelectedFuelEcoType
    {
      get { return selectedFuelEcoType; }
      set { SetProperty(ref selectedFuelEcoType, value); }
    }
 
    private string fuel;
    public string Fuel
    {
      get { return fuel; }
      set { SetProperty(ref fuel, value); }
    }
 
    private string distance;
    public string Distance
    {
      get { return distance; }
      set { SetProperty(ref distance, value); }
    }
 
    private string result;
    public string Result
    {
      get { return result; }
      set { SetProperty(ref result, value); }
    }    
  } 
}

NOTE The base class BindableBase that is used with the sample code just offers an implementation of the interface INotifyPropertyChanged. This class is found in the CalculatorUtils assembly.

The calculation is within the OnCalculate method. OnCalculate is the handler for the Click event of the Calculate button (code file WPFCalculator/FuelEconomy/FuelEconomyUC.xaml.cs):

    private void OnCalculate(object sender, RoutedEventArgs e)
    {
      double fuel = double.Parse(viewModel.Fuel);
      double distance = double.Parse(viewModel.Distance);
      FuelEconomyType ecoType = viewModel.SelectedFuelEcoType;
      double result = 0;
      switch (ecoType.Id)
      {
        case "lpk":
          result = fuel / (distance / 100);
          break;
        case "mpg":
          result = distance / fuel;
          break;
        default:
          break;
      }
      viewModel.Result = result.ToString();
    }

Again, the interface ICalculatorExtension is implemented and exported with the Export attribute (code file WPFCalculator/FuelEconomy/FuelCalculatorExtension.cs):

using System.ComponentModel.Composition;
using System.Windows;
 
namespace Wrox.ProCSharp.MEF
{
  [Export(typeof(ICalculatorExtension))]
  public class FuelCalculatorExtension : ICalculatorExtension
  {
    private FrameworkElement control;
    public FrameworkElement UI
    {
      get
      {
        return control ?? (control = new FuelEconomyUC());
      }
    }
  }
}

Before continuing the WPF calculator example to import the user controls, let’s take a look at what other options you have with exports. With exports, you can export not only complete types, but also properties and methods, and you can add metadata information to the exports.

Exporting Properties and Methods

Instead of exporting complete classes with properties, methods, and events, it is possible to export just properties or methods. Exporting properties makes it possible to use classes where you can’t change the source code by adding the Export attribute to them (for example, classes from the .NET Framework or third-party libraries). For this, you just have to define a property of the specific type and export the property.

Exporting methods provides a finer degree of control than using types. The caller doesn’t need to know about the type. Methods are exported with the help of delegates. The following code snippet defines the Add and Subtract methods with exports. The type of the export is the delegate Func<double, double, double>, which is a delegate that accepts two double parameters and a double return type. For methods without return types, the Action<T> delegate can be used (code file WPFCalculator/Operations/Operations.cs):

using System;
using System.ComponentModel.Composition;
namespace Wrox.ProCSharp.MEF
{
  public class Operations
  {
    [Export("Add", typeof(Func<double, double, double>))]
    public double Add(double x, double y)
    {
      return x + y;
    }
    [Export("Subtract", typeof(Func<double, double, double>))]
    public double Subtract(double x, double y)
    {
      return x - y;
    }
  }
}

NOTE You can read about the Func<T> and Action<T> delegates in Chapter 8, “Delegates, Lambdas, and Events.”

The exported methods are imported from the SimpleCalculator add-in. A part itself can use other parts. To use the exported methods, delegates are declared with the attribute Import. This attribute contains the same name and delegate type that was declared with the export (code file WPFCalculator/SimpleCalculator/Calculator.cs):


NOTE SimpleCalculator itself is a part that exports the ICalculator interface and consists of parts that are imported.

  [Export(typeof(ICalculator))]
  public class Calculator : ICalculator
  {
    [Import("Add", typeof(Func<double, double, double>))]
    public Func<double, double, double> Add { get; set; }
 
    [Import("Subtract", typeof(Func<double, double, double>))]
    public Func<double, double, double> Subtract { get; set; }

The imported methods that are represented by the Add and Subtract delegates are invoked via these delegates in the Operate method:

    public double Operate(IOperation operation, double[] operands)
    {
      double result = 0;
      switch (operation.Name)
      {
        case "+":
          result = Add(operands[0], operands[1]);
          break;
        case "-":
          result = Subtract(operands[0], operands[1]);
          break;
        case "/":
          result = operands[0] / operands[1];
          break;
        case "*":
          result = operands[0] * operands[1];
          break;
        default:
          throw new InvalidOperationException(
              String.Format("invalid operation {0}", operation.Name));
      }
      return result;
    }

Exporting Metadata

With exports, you can also attach metadata information. Metadata enables you to provide information in addition to a name and a type. This can be used to add capability information and to determine, on the import side, which of the exports should be used.

The exported Add method is now changed to add speed capabilities with the attribute ExportMetadata (code file WPFCalculator/Operations/Operation.cs):

    [Export("Add", typeof(Func<double, double, double>))]
    [ExportMetadata("speed", "fast")]
    public double Add(double x, double y)
    {
      return x + y;
    }

To have the option to choose from another implementation of the Add method, another method with different speed capabilities but the same delegate type and name is implemented (code file WPFCalculator/Operations/Operation2.cs):

  public class Operations2
  {
    [Export("Add", typeof(Func<double, double, double>))]
    [ExportMetadata("speed", "slow")]
    public double Add(double x, double y)
    {
      Thread.Sleep(3000);
      return x + y;
    }
  }

Because more than one exported Add method is available, the import definition must be changed. The attribute ImportMany is used if more than one export of the same name and type is available. This attribute is applied to an array or IEnumeration<T> interface. ImportMany is explained with more detail in the next section. For accessing metadata, an array of Lazy<T, TMetadata> can be used. The class Lazy<T> is used to support lazy initialization of types on first use. Lazy<T, TMetadata> derives from Lazy<T> and supports, in addition to the base class, access to metadata information with the Metadata property. In the example, the method is referenced by the delegate Func<double, double, double>, which is the first generic parameter of Lazy<T, TMetadata>. The second generic parameter is IDictionary<string, object> for the metadata collection. The ExportMetadata attribute can be used multiple times to add more than one capability, and it always consists of a key of type string and a value of type object (code file WPFCalculator/SimpleCalculator/Calculator.cs):

    [ImportMany("Add", typeof(Func<double, double, double>))]
    public Lazy<Func<double, double, double>, IDictionary<string, object>>[] 
        AddMethods { get; set; }
    //[Import("Add", typeof(Func<double, double, double>))]
    //public Func<double, double, double> Add { get; set; }

The call to the Add method is now changed to iterate through the collection of Lazy<Func<double, double, double>, IDictionary<string, object>> elements. With the Metadata property, the key for the capability is checked; if the speed capability has the value fast, the operation is invoked by using the Value property of Lazy<T> to get to the delegate:

      case "+":
        // result = Add(operands[0], operands[1]);
        foreach (var addMethod in AddMethods)
        {
          if (addMethod.Metadata.ContainsKey("speed") && 
              (string)addMethod.Metadata["speed"] == "fast")
            result = addMethod.Value(operands[0], operands[1]);   
        }
        // result = operands[0] + operands[1];
        break;

Instead of using the attribute ExportMetadata, you can create a custom export attribute class that derives from ExportAttribute. The class SpeedExportAttribute defines an additional Speed property that is of type Speed (code file WPFCalculator/CalculatorUtils/SpeedExportAttribute.cs):

using System;
using System.ComponentModel.Composition;
namespace Wrox.ProCSharp.MEF
{
  public enum Speed
  {
    Fast,
    Slow
  }
  [MetadataAttribute]
  [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
  public class SpeedExportAttribute : ExportAttribute
  {
    public SpeedExportAttribute(string contractName, Type contractType)
        : base(contractName, contractType) { }
    public Speed Speed { get; set; }
  }
}

NOTE For more information about how to create custom attributes, read Chapter 15, “Reflection.”

With the exported Add method, now the SpeedExport attribute can be used instead of the Export and ExportMetadata attributes (code file WPFCalculator/Operations/Operations.cs):

    [SpeedExport("Add", typeof(Func<double, double, double>), Speed=Speed.Fast)]
    public double Add(double x, double y)
    {
      return x + y;
    }

For the import, an interface with all the metadata is required. This makes it possible to access the strongly typed capabilities. The attribute SpeedExport just defines a single capability: speed. The interface ISpeedCapabilities defines the property Speed by using the same enumeration type Speed that was used with the SpeedExport attribute (code file WPFCalculator/CalculatorUtils/ISpeedCapabilities.cs):

namespace Wrox.ProCSharp.MEF
{
  public interface ISpeedCapabilities
  {
    Speed Speed { get; }
  }
}

Now it’s possible to change the definition of the import by using the interface ISpeedCapabilities instead of the dictionary defined earlier (code file WPFCalculator/SimpleCalculator/Calculator.cs):

    [ImportMany("Add", typeof(Func<double, double, double>))]
    public Lazy<Func<double, double, double>, ISpeedCapabilities>[] 
        AddMethods { get; set; }

Using the imports, the Speed property of the interface, ISpeedCapabilities can now be used directly:

      foreach (var addMethod in AddMethods)
      {
        if (addMethod.Metadata.Speed == Speed.Fast)
          result = addMethod.Value(operands[0], operands[1]);
      }

Using Metadata for Lazy Loading

MEF metadata is not only useful for selecting parts based on metadata information. Another great use is providing information to the host application about the part before the part is instantiated.

The following example is implemented to offer a title, a description, and a link to an image for the calculator extensions FuelEconomy and TemperatureConversion (code file WPFCalculator/CalculatorContract/ICalculatorExtensionMetadata.cs):

  public interface ICalculatorExtensionMetadata
  {
    string Title { get; }
    string Description { get; }
 
    string ImageUri { get; }
  }

For easy usage, an export attribute named CalculatorExtensionExport is created, as shown in the following code snippet. The implementation is very similar to the SpeedExport attribute shown earlier (code file WPFCalculator/CalculatorUtils/CalculatorExtensionAttribute.cs):

using System;
using System.ComponentModel.Composition;
 
namespace Wrox.ProCSharp.MEF
{
  [MetadataAttribute]
  [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
  public class CalculatorExtensionExportAttribute : ExportAttribute
  {
    public CalculatorExtensionExportAttribute(Type contractType)
      : base(contractType) { }
 
    public string Title { get; set; }
    public string Description { get; set; }
 
    public string ImageUri { get; set; }
  }
}

The exports of the parts can now be changed. In the next two code snippets the CalculatorExtensionExport attribute is applied with the parts FuelEconomy and TemperatureConversion. These two parts also use two images, Fuel.png and Temperature.png, which are copied to the add-in directory during the build process. These images can be used from the host application as well to display information before the parts are instantiated (code files WPFCalculator/FuelEconomy/FuelCalculatorExtension.cs and TemperatureConversionExtension.cs):

  [CalculatorExtensionExport(typeof(ICalculatorExtension),
    Title = "Fuel Economy",
    Description = "Calculate fuel economy",
    ImageUri = "Fuel.png")]
  public class FuelCalculatorExtension : ICalculatorExtension
  [CalculatorExtensionExport(typeof(ICalculatorExtension), 
    Title = "Temperature Conversion", 
    Description="Convert Celsius to Fahrenheit and Fahrenheit to Celsius",
    ImageUri = "Temperature.png")]
  public class TemperatureCalculatorExtension : ICalculatorExtension

IMPORTING PARTS

Now let’s take a look at using the WPF user controls with a WPF hosting application. The design view of the hosting application is shown in Figure 30-4. The application Calculator is a WPF application that loads the functional calculator add-in, which implements the interfaces ICalculator and IOperation, and add-ins with user interfaces that implement the interface ICalculatorExtension. To connect to the exports of the parts, you need imports.

The calculator host application uses the class CalculatorViewModel to bind input and result data to the user interface. The property CalcExtensions contains a list of all available extension add-ins; the property ActivatedExtensions contains a list of the extensions that are loaded (code file WPFCalculator/Calculator/CalculatorViewModel.cs):

using System;
using System.Collections.ObjectModel;
 
namespace Wrox.ProCSharp.MEF
{
  public class CalculatorViewModel : BindableBase
  {
    private string status;
 
    public string Status
    {
      get { return status; }
      set { SetProperty(ref status, value); }
    }
 
    private string input;
    public string Input
    {
      get { return input; }
      set { SetProperty(ref input, value); }
    }
 
    private string result;
    public string Result
    {
      get { return result; }
      set { SetProperty(ref result, value); }
    }
 
    private string fullInputText;
    public string FullInputText
    {
      get { return fullInputText; }
      set { fullInputText = value; }
    }
 
    private readonly ObservableCollection<IOperation> calcAddInOperators = 
        new ObservableCollection<IOperation>();
    public object syncCalcAddInOperators = new object();
    public ObservableCollection<IOperation> CalcAddInOperators
    {
      get
      {
        return calcAddInOperators;
      }
    }
 
    private readonly ObservableCollection<Lazy<ICalculatorExtension>> 
        calcExtensions = new ObservableCollection<Lazy<ICalculatorExtension>>();
    public ObservableCollection<Lazy<ICalculatorExtension>> CalcExtensions
    {
      get
      {
        return calcExtensions;
      }
    }
 
    private readonly ObservableCollection<Lazy<ICalculatorExtension>> 
      activatedExtensions = new ObservableCollection<Lazy<ICalculatorExtension>>();
    public object syncActivatedExtensions = new object();
    public ObservableCollection<Lazy<ICalculatorExtension>> ActivatedExtensions
    {
      get
      {
        return activatedExtensions;
      }
    }
  }
}

Importing Collections

An import connects to an export. When using exported parts, an import is needed to make the connection. With the Import attribute, it’s possible to connect to a single export. If more than one add-in should be loaded, the ImportMany attribute is required and needs to be defined as an array type or IEnumerable<T>. Because the hosting calculator application allows many calculator extensions that implement the interface ICalculatorExtension to be loaded, the class CalculatorExtensionImport defines the property CalculatorExtensions of type IEnumerable<ICalculatorExtension> to access all the calculator extension parts (code file WPFCalculator/Calculator/CalculatorExtensionImport.cs):

using System.Collections.Generic;
using System.ComponentModel.Composition;
namespace Wrox.ProCSharp.MEF
{
  public class CalculatorExtensionImport
  {
    [ImportMany(AllowRecomposition=true)]
    public IEnumerable<ICalculatorExtension> CalculatorExtensions { get; set; }
  }
}

The Import and ImportMany attributes enable the use of ContractName and ContractType to map the import to an export. Other properties that can be set with these attributes are AllowRecomposition and RequiredCreationPolicy. AllowRecomposition enables dynamic mapping to new exports while the application is running, and the unloading of exports. With RequiredCreationPolicy, you can specify whether the parts should be shared (CreationPolicy.Shared) or not shared (CreationPolicy.NonShared) between requestors, or whether the policy should be defined by the container (CreationPolicy.Any).

You can get a confirmation that all imports are successful (or errors in case they are not), you can implement the interface IPartImportsSatisfiedNotification. This interface just defines a single method, OnImportsSatifsifed, which is called when all imports of the class are successful. In the CalculatorImport class, the method fires an ImportsSatisfied event (code file WPFCalculator/Calculator/CalculatorImport.cs):

using System;
using System.ComponentModel.Composition;
using System.Windows.Controls;
namespace Wrox.ProCSharp.MEF
{
  public class CalculatorImport : IPartImportsSatisfiedNotification
  {
    public event EventHandler<ImportEventArgs> ImportsSatisfied;
    [Import(typeof(ICalculator))]
    public ICalculator Calculator { get; set; }
 
    public void OnImportsSatisfied()
    {
      if (ImportsSatisfied != null)
        ImportsSatisfied(this, new ImportEventArgs { 
            StatusMessage = "ICalculator import successful" });
    }
  }
}

The event of the CalculatorImport is connected to an event handler on creation of the CalculatorImport to write a message to a Status property that is bound in the UI for displaying status information (code file WPFCalculator/CalculatorManager/MainWindow.xaml.cs):

  public sealed class CalculatorManager : IDisposable
  {
    private DirectoryCatalog catalog;
    private CompositionContainer container;
    private CalculatorImport calcImport;
    private CalculatorExtensionImport calcExtensionImport;
    private CalculatorViewModel vm;
 
    public CalculatorManager(CalculatorViewModel vm)
    {
      this.vm = vm;
    }
 
    public async void InitializeContainer()
    {
      catalog = new DirectoryCatalog(Properties.Settings.Default.AddInDirectory);
      container = new CompositionContainer(catalog);
 
      calcImport = new CalculatorImport();
 
      calcImport.ImportsSatisfied += (sender, e) =>
      {
        vm.Status += string.Format("{0}
", e.StatusMessage);
      };
 
      await Task.Run(() =>
        {
          container.ComposeParts(calcImport);
        });
 
      await InitializeOperationsAsync();
    }

Lazy Loading of Parts

By default, parts are loaded from the container—for example, by calling the extension method ComposeParts on the CompositionContainer. With the help of the Lazy<T> class, the parts can be loaded on first access. The type Lazy<T> enables the late instantiation of any type T and defines the properties IsValueCreated and Value. IsValueCreated is a Boolean that returns the information if the contained type T is already instantiated. Value initializes the contained type T on first access and returns the instance.

The import of an add-in can be declared to be of type Lazy<T>, as shown in the Lazy<ICalculator> example (code file WPFCalculator/Calculator/CalculatorImport.cs):

      [Import(typeof(ICalculator))]
      public Lazy<ICalculator> Calculator { get; set; }

Calling the imported property also requires some changes to access the Value property of the Lazy<T> type. calcImport is a variable of type CalculatorImport. The Calculator property returns Lazy<ICalculator>. The Value property instantiates the imported type lazily and returns the ICalculator interface, enabling the GetOperations method to be invoked in order to get all supported operations from the calculator add-in (code file WPFCalculator/Calculator/CalculatorManager.cs):

    public Task InitializeOperationsAsync()
    {
      Contract.Requires(calcImport != null);
      Contract.Requires(calcImport.Calculator != null);
      return Task.Run(() =>
        {
          var operators = calcImport.Calculator.Value.GetOperations();
          lock (vm.syncCalcAddInOperators)
          {
            vm.CalcAddInOperators.Clear();
 
            foreach (var op in operators)
            {
              vm.CalcAddInOperators.Add(op);
            }
          }
        });
    }

Reading Metadata with Lazyily Instantiated Parts

The parts FuelEconomy and TemperatureConversion—all the parts that implement the interface ICalculatorExtension—are lazy loaded as well. As you’ve seen earlier, a collection can be imported with a property of IEnumerable<T>. Instantiating the parts lazily, the property can be of type IEnumerable<Lazy<T>>. Information about these parts is needed before instantiation in order to display information to the user about what can be expected with these parts. These parts offer additional information using metadata, as shown earlier. Metadata information can be accessed using a Lazy type with two generic type parameters. Using Lazy<ICalculatorExtension, ICalculatorExtensionMetadata>, the first generic parameter, ICalculatorExtension, is used to access the members of the instantiated type; the second generic parameter, ICalculatorExtensionMetadata, is used to access metadata information (code file WPFCalculator/Calculator/CalculatorExtensionImport.cs):

  public class CalculatorExtensionImport : IPartImportsSatisfiedNotification
  {
    public event EventHandler<ImportEventArgs> ImportsSatisfied;
 
    [ImportMany(AllowRecomposition = true)]
    public IEnumerable<Lazy<ICalculatorExtension, ICalculatorExtensionMetadata>> 
        CalculatorExtensions { get; set; }
 
    public void OnImportsSatisfied()
    {
      if (ImportsSatisfied != null)
        ImportsSatisfied(this, new ImportEventArgs 
        { StatusMessage = "ICalculatorExtension imports successful" });
    }
  }

The method RefreshExtensions imports the calculator extension parts based on their Lazy type and adds the lazy types to the collection CalcExtensions shown earlier (code file WPFCalculator/Calculator/CalculatorManager.cs):

    public void RefreshExensions()
    {
      catalog.Refresh();
      calcExtensionImport = new CalculatorExtensionImport();
      calcExtensionImport.ImportsSatisfied += (sender, e) =>
      {
        vm.Status += String.Format("{0}
", e.StatusMessage);
      };
 
      container.ComposeParts(calcExtensionImport);
      vm.CalcExtensions.Clear();
      foreach (var extension in calcExtensionImport.CalculatorExtensions)
      {
        vm.CalcExtensions.Add(extension);
      }
    }

Within the XAML code, metadata information is bound. The Lazy type has a Metadata property that returns ICalculatorExtensionMetadata. This way, Description, Title, and ImageUri can be accessed for data binding without instantiating the add-ins (XAML file WPFCalculator/Calculator/MainWindow.xaml):

        <RibbonGroup Header="Addins" ItemsSource="{Binding CalcExtensions}">
          <RibbonGroup.ItemTemplate>
            <DataTemplate>
              <RibbonButton ToolTip="{Binding Metadata.Description}"                  
                  Label="{Binding Metadata.Title}" Tag="{Binding}" 
                  LargeImageSource="{Binding Metadata.ImageUri, 
                      Converter={StaticResource bitmapConverter}}"  
                  Command="local:CalculatorCommands.ActivateExtension" />
            </DataTemplate>
          </RibbonGroup.ItemTemplate>
        </RibbonGroup>

To get an image from the link that is returned from the ImageUri property, a value converter is implemented that returns a BitmapImage (code file WPFCalculator/Calculator/UriToBitmapConverter.cs):

  public class UriToBitmapConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
      BitmapImage image = null;
      string uri = value.ToString();
      if (!string.IsNullOrEmpty(uri))
      {
        var stream = File.OpenRead(Path.Combine(
            Properties.Settings.Default.AddInDirectory, uri));
        image = new BitmapImage();
        image.BeginInit();
        image.StreamSource = stream;
        image.EndInit();
        return image;
      }
      else
      {
        return null;
      }
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }

NOTE WPF value converters are discussed in Chapter 36, “Business Applications with WPF.”

Figure 30-5 shows the running application where metadata from the calculator extensions is read—it includes the image, the title, and the description.

CONTAINERS AND EXPORT PROVIDERS

The import of parts is facilitated with the help of a container. The types for hosting parts are defined in the namespace System.ComponentModel.Composition.Hosting. The class CompositionContainer is the container for parts. In the constructor of this class, multiple ExportProvider objects can be assigned, as well as a ComposablePartCatalog. Catalogs are sources of parts and are discussed in the next section. Export providers enable you to access all exports programmatically with overloaded GetExport<T> methods. An export provider is used to access the catalog, and the CompositionContainer itself is an export provider. This makes it possible to nest containers in other containers.

Parts are loaded when the Compose method is invoked (if they are not lazy loaded). So far, the examples have used the ComposeParts method, as shown in the InitializeContainer method (code file WPFCalculator/Calculator/CalculatorManager.cs):

        public async void InitializeContainer()
        {
            catalog = new DirectoryCatalog(
                              Properties.Settings.Default.AddInDirectory);
            container = new CompositionContainer(catalog);
            calcImport = new CalculatorImport();
            calcImport.ImportsSatisfied += (sender, e) =>
                {
                   textStatus.Text += String.Format("{0}
", e.StatusMessage);
                };
            await Task.Run(() =>
              {
                container.ComposeParts(calcImport);
              });
            await InitializeOperationsAsync();            
        }

ComposeParts is an extension method defined with the class AttributedModelServices, which provides methods that use attributes and .NET reflection to access part information and add parts to the container. Instead of using this extension method, you can use the Compose method of CompositionContainer. The Compose method works with the class CompositionBatch. A CompositionBatch can be used to define which parts should be added or removed from the container. The methods AddPart and RemovePart have overloads whereby either an attributed part can be added (calcImport is an instance of the CalculatorImport class and contains Import attributes) or a part that derives from the base class ComposablePart:

            var batch = new CompositionBatch();
            batch.AddPart(calcImport);
            container.Compose(batch);

Both kinds of parts used with the Calculator hosting application are searched in the same way. The part that implements the interface ICalculator is instantiated immediately when the application is launched. The ICalculatorExtension parts are instantiated only when the user clicks the part information in the ribbon control. Clicking on the buttons of the ribbon controls invokes the OnActivateExtension handler method. Within the implementation of this method, the selected ICalculatorExtension part is instantiated by using the Value property of the Lazy<T> type. A reference is then added to the ActivatedExtensions collection (code file WPFCalculator/Calculator/MainWindow.xaml.cs):

    private void OnActivateExtension(object sender, ExecutedRoutedEventArgs e)
    {
      var button = e.OriginalSource as RibbonButton;
      if (button != null)
      {
        Lazy<ICalculatorExtension> control = button.Tag as 
          Lazy<ICalculatorExtension>;
        FrameworkElement el = control.Value.UI;
        viewModel.ActivatedExtensions.Add(control);
      }     
    }

All the activated ICalculatorExtension parts are shown as TabItem within the TabControl element, as the TabControl is bound to the ActivatedExtensions property. For the TabItem controls, both an ItemTemplate and a ContentTemplate are defined. The ItemTemplate defines a header to show the title and a button to close the part; the ContentTemplate accesses the user interface of the part by using the UI property (XAML file WPFCalculator/Calculator/MainWindow.xaml):

    <TabControl Grid.Row="1" Grid.Column="1" Margin="2" 
        ItemsSource="{Binding ActivatedExtensions}">
      <TabControl.ContentTemplate>
        <DataTemplate>
          <ContentPresenter Content="{Binding Value.UI}" />
        </DataTemplate>
      </TabControl.ContentTemplate>
      <TabControl.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal" Margin="0">
            <TextBlock Text="{Binding Metadata.Title}" Margin="0" />
            <Button Content="X" Margin="5,1" 
              Command="local:CalculatorCommands.CloseExtension" 
              Tag="{Binding}" />
          </StackPanel>
        </DataTemplate>
      </TabControl.ItemTemplate>
    </TabControl>

The close button within the ItemTemplate activates the CloseExtension command. This invokes the OnCloseExtension handler method whereby the part is removed from the ActivatedExtensions collection (code file WPFCalculator/Calculator/MainWindow.xaml.cs):

    private void OnCloseExtension(object sender, ExecutedRoutedEventArgs e)
    {
      Button b = e.OriginalSource as Button;
      if (b != null)
      {
        Lazy<ICalculatorExtension> ext = b.Tag as Lazy<ICalculatorExtension>;
        if (ext != null)
        {
          viewModel.ActivatedExtensions.Remove(ext);
        }
      }
    }

With an export provider, you can get information on exports added and removed by implementing a handler to the ExportsChanged event. The parameter e of type ExportsChangedEventArgs contains a list of added exports and removed exports that are written to a Status property (code file WPFCalculator/Calculator/CalculatorManager.cs):

      container = new CompositionContainer(catalog);
      container.ExportsChanged += (sender, e) =>
      {
        var sb = new StringBuilder();
 
        foreach (var item in e.AddedExports)
        {
          sb.AppendFormat("added export {0}
", item.ContractName);
        }
        foreach (var item in e.RemovedExports)
        {
          sb.AppendFormat("removed export {0}
", item.ContractName);
        }
        vm.Status += sb.ToString();
      };

CATALOGS

A catalog defines where MEF searches for requested parts. The sample application uses a DirectoryCatalog to load the assemblies with parts from a specified directory. With the DirectoryCatalog, you can get change information with the Changed event and iterate through all added and removed definitions. The DirectoryCatalog does not itself register to file system changes. Instead, you need to invoke the Refresh method of the DirectoryCatalog; and if changes were made since the last read, the Changing and Changed events are fired (code file WPFCalculator/Calculator/CalculatorManager.cs):

    public async void InitializeContainer()
    {
      catalog = new DirectoryCatalog(Properties.Settings.Default.AddInDirectory);
 
      catalog.Changed += (sender, e) =>
      {
        var sb = new StringBuilder();
 
        foreach (var definition in e.AddedDefinitions)
        {
          foreach (var metadata in definition.Metadata)
          {
            sb.AppendFormat("added definition with metadata - key: {0}, " + 
                "value: {1}
", metadata.Key, metadata.Value);
          }
        }
 
        foreach (var definition in e.RemovedDefinitions)
        {
          foreach (var metadata in definition.Metadata)
          {
            sb.AppendFormat("removed definition with metadata - key: {0}, " +
                "value: {1}
", metadata.Key, metadata.Value);
          }
        }
 
        vm.Status += sb.ToString();
      };
 
      container = new CompositionContainer(catalog);
 
      //...
 
    }

NOTE To get immediate notification of new add-ins loaded to a directory, you can use the System.IO.FileSystemWatcher to register for changes to the add-in directory, and invoke the Refresh method of the DirectoryCatalog with the Changed event of the FileSystemWatcher.

The CompositionContainer just needs a ComposablePartCatalog to find parts. DirectoryCatalog derives from ComposablePartCatalog. Other catalogs are AssemblyCatalog, TypeCatalog, and AggregateCatalog. Here’s a brief description of these catalogs:

  • DirectoryCatalog — Searches parts within a directory.
  • The AssemblyCatalog — Searches for parts directly within a referenced assembly. Unlike the DirectoryCatalog, whereby assemblies might change in the directory during runtime, the AssemblyCatalog is immutable and parts cannot change.
  • The TypeCatalog — Searches for imports within a list of types. IEnumerable<Type> can be passed to the constructor of this catalog.
  • The AggregateCatalog — A catalog of catalogs. This catalog can be created from multiple ComposablePartCatalog objects, and it searches in all these catalogs. For example, you can create an AssemblyCatalog to search for imports within an assembly, two DirectoryCatalog objects to search in two different directories, and an AggregateCatalog that combines the three catalogs for import searches.

When running the sample application (see Figure 30-6), the SimpleCalculator add-in is loaded, and you can do some calculations with operations supported by the add-in. From the AddIns menu, you can start add-ins that implement the interface ICalculatorExtension and see the user interface from these add-ins in the tab control. Information about exports and changes in the directory catalog is shown with the status information at the bottom. You can also remove an ICalculatorExtension add-in from the add-in directory (while the application is not running), copy the add-in to the directory while the application is running, and do a refresh of the add-ins to see the new add-ins during runtime.

SUMMARY

In this chapter, you learned about the parts, containers, and catalogs of the Managed Extensibility Framework (MEF). You’ve learned how an application can be built up with complete independency of its parts and dynamically load parts that can come from different catalogs such as an assembly or directory catalog.

The MEF implementation uses attributes or conventions to find and connect add-ins. You’ve seen the new convention-based parts registration that allows exporting parts without the need of attributes. This allows using parts where you can’t change the source code to add attributes, and also gives the option to create a framework based on MEF that doesn’t require the user of your framework to add attributes for importing the parts.

You’ve also learned how parts can be lazy loaded to instantiate them only when they are needed. Parts can offer metadata that can give enough information for the client to decide if the part should be instantiated or not.

The next chapter covers the basics of the Windows Runtime to create Windows 8 applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset