Exercise 1: Using MEF to Extend an Application

One practical use of the Managed Extensibility Framework is adding modules to an application at runtime. This is useful in a scenario in which users chooses specific modules to purchase or install originally and may add more modules at a later time. Using MEF, you can configure your application to monitor a well-known directory and add any module assemblies found in that directory. Dropping module assemblies into a directory allows your application to load those assemblies without explicitly setting references to them.

Task 1 – Updating Your Application to Load Composable Parts

In this task, you will modify an existing WPF Window class to create extension hooks that will allow dynamically importing queries. You will update this query classes later in this exercise.

  1. Open Microsoft Visual Studio 2010 from Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010.
  2. Open the ContosoAutomotive.sln solution file. By default, this file is located in the folder Source\Ex1\begin(choosing the folder that matches the language of your preference.)
  3. Add a reference to the MEF library on the ContosoAutomotive project. To do this:
    1. Select the ContosoAutomotive project in the Solution Explorer and select Project | Add Reference… The Add References dialog appears.
    2. Select the .NET tab and then select the System.ComponentModel.Composition component. Click the OK button to add a reference to this library.

      Figure 1

      Add a Reference to the MEF library.

  4. Open the App class in code view. To do this, right-click on the App.xaml file in the Solution Explorer and select View Code.
  5. Update the App class to use the MEF library. To do this, add the following statement on top of the using clause list above the App class definition.

    C#

    using System.ComponentModel.Composition.Hosting;

    Visual Basic

    Imports System.ComponentModel.Composition.Hosting

  6. Create an aggregate catalog to load parts from the current application assembly and from assemblies located in the current folder in the file system. Also, create a composition container instance based on it and create an exported CashMaker instance. To do this, modify the AppStartup method with the following code.

    (Code Snippet – Intro to MEF Lab - Ex1 Task1 Step6 - StartupCatalogAndContainer CSharp)

    C#

    void AppStartup(object sender, StartupEventArgs args)
    FakePre-3cff8ffef5d048f78ec4028603bac202-36990b0498594d5a9e5fe4ccb3c7a791 var catalog = new AggregateCatalog(new DirectoryCatalog("."), new AssemblyCatalog(Assembly.GetExecutingAssembly())); var container = new CompositionContainer(catalog); var window = container.GetExportedValue<CashMaker>();FakePre-7babbdf19ba54ae39cb9a1fdccef73ce-815f74d63fa245a391dd30367752a76bFakePre-8b2a47a66acf46cea8c1d824158f4715-d876298fa5df48bab47da119cd97a6bfFakePre-1d72df6dbbe54420a768b556c2a714e9-71c9330cf72f487e814afa807a6a3a18

    (Code Snippet – Intro to MEF Lab - Ex1 Task1 Step6 - StartupCatalogAndContainer VB)

    Visual Basic

    Sub AppStartup(ByVal sender As Object, ByVal args As StartupEventArgs)
    FakePre-a9c82ed683284c33800b403db94cd8b5-7bbeb1ab369a4a769cee03237bbb1264 Dim catalog = New AggregateCatalog(New DirectoryCatalog("."), New AssemblyCatalog(Assembly.GetExecutingAssembly())) Dim container = New CompositionContainer(catalog) Dim window = container.GetExportedValue(Of CashMaker)()FakePre-33ac31474fff430f86a18bb4314abce8-d6a5111e7af947e6b783d5790799b93bFakePre-40b2251392264a31a51d120c1a86103a-1f4d48fcb94240d1b68a38b0625a656dFakePre-86d557ac31fb47f1a2e5a7a8949da58f-8cca55b9b9514ef186057d03d1caec9f

    Note:
    The starting point for enabling MEF composability is creating a composition container based on one or several catalogs. MEF's container interacts with Catalogs to have access to composable parts. The container itself resolves a part's dependencies and exposes Exports to the outside world. In addition, you are free to add composable part instances directly to the container if you wish.

    Note:
    There are different kinds of Catalogs, which are able to discover parts on different ways. This exercise uses an Aggregate Catalog combining a Directory Catalog searching on the Application directory (represented by the “.”) and Assembly Catalog for look at the types inside the application assembly (retrieved using the Assembly.GetExecutingAssembly method).

    The full list of Catalogs available on MEF is:

    - Assembly Catalog: Discovers the different parts on a specific assembly.

    - Directory Catalog: Discovers parts inside the assemblies on a specific directory.

    - Aggregate Catalog: Allows use more than one catalog combining them.

    - Type Catalog: Looks inside the assemblies for specific types.

    For more information on Catalogs, see Using catalogs.

    Note:
    Application’s main window is also located and loaded using MEF through the container’s GetExportedValue method. In the following steps, you will decorate the CashMaker class (which is the main window) with the Export attribute to be discoverable by MEF.

    The GetExportedValue method will throw an exception if no parts or more than one implement the solicited contract. This is not a problem in the exercise implementation because we are not using an interface to discover the type, we are using the class itself so it has to be defined and cannot be duplicated, ensuring that MEF will find only one instance.

  7. Open the CashMaker Window in code view. To do this, right-click on the CashMaker.xaml file in the Solution explorer and select View Code.
  8. Update the CashMaker class to use the MEF library. To do this, add the following statement at the top of the CashMaker class definition.

    C#

    using System.ComponentModel.Composition;

    Visual Basic

    Imports System.ComponentModel.Composition

  9. Since the CashMaker class is now a composable part you will export the class making it available to the composition container. To do this, decorate the CashMaker class with the Export attribute as shown in the code below.

    C#

    [Export]
    public partial class CashMaker : Window
    FakePre-60168ee1184c4062a3b4d396d3c2fe08-2f03a2335c0a4e94a26c3b868311bcfbFakePre-e3166c3ed2984d659bae3f20f2051a02-9fbdaab41c9f46fab4ac3e11462554a0FakePre-086e0a1e200146049cf8ed810aab9a1a-ac7704fd09304be293a6965c2860cf47

    Visual Basic

    <Export()>
    Class CashMaker
    FakePre-01666b6a6d4f40b19c393eb16ec0d891-2b45beb42086422ea9a073f5e1d2b051FakePre-d31a9b854a484b3cae99a96c31a9a1d1-d2306cadc598426190170d7566b2f895FakePre-6fd724aa4a884e43bb58f520f124e6f6-a6a845a3549a4cf7a02d2c8fd3bb7993

    Note:
    NOTE: Import and Export values are interconnected with unique Contracts. Contracts serve as uni-directional bridges. An export contract can consist of further metadata used to filter on its discovery. For example, it might indicate a specific capability that the export offers.

    As you decorate the CashMaker class with the Export attribute, it will discover composable parts exporting the ICarQuery contract, defined in the ContosoAutomotive.Common project. You will declare a collection to hold the Imports and expose them through a property. MEF's composition container will resolve applicable Exports and Imports loading the collection for you.

  10. Add the following code to the CashMaker.xaml.cs (C#) or CashMaker.xaml.vb (Visual Basic) file, just above its constructor.

    (Code Snippet – Intro to MEF Lab - Ex1 Task1 Step10 - ImportedCarQueries CSharp)

    C#

    [ImportMany(AllowRecomposition = true)] public ObservableCollection<ICarQuery> CarQueries { get; set; }

    (Code Snippet – Intro to MEF Lab - Ex1 Task1 Step10 - ImportedCarQueries VB)

    Visual Basic

    <ImportMany(AllowRecomposition:=True)> Public Property CarQueries() As ObservableCollection(Of ICarQuery)

    Note:
    You can import collections with the ImportMany attribute. This means that all instances of the specific contract will be imported from the container.

    MEF parts can also support recomposition. This means that as new exports become available in the container, collections are automatically updated with the new set.

  11. Once MEF completes the CarQueries collection import process, you want your application to be notified and so it can take a particular action. To do this, make the CashMaker class to implement the IPartImportsSatisfiedNotification interface as shown in the following code.

    C#

    [Export]
    FakePre-ed5e096aafd748a4af49dbfbaca752b9-c19343260f1245c9a6ca950946626accFakePre-d419c95d31fd4da18fa68643b45caabc-0538e3e9b0704f2bbb926524113594f8FakePre-88e9aea8665d49dda79d24fb1e647076-1a67261bc4074d80b169a144eebd8ee0FakePre-f70d9d1dfa5d45c7aaefce61cf0b8554-c3c1796a1cfc483892cd2bc8084d17c8

    Visual Basic

    <Export>
    FakePre-46777be59f3746818828dbf7d96c7ada-2fba7ad8e1424ec2ba01aed7cf7b3883FakePre-6594aa5ed9f24808ac52e62d52684579-24d25b54d96540c7bf522c245b1f54e9 Implements IPartImportsSatisfiedNotificationFakePre-b96b97fa212446e3ae96382623d30d71-cd0b7d75527e476d8b669454c4d5e271FakePre-03841053ed6d4a119dca3a328612608d-8ae07558c5364085ad00ef2391f48562

  12. Implement the IPartImportsSatisfiedNotification interface and bind the CarQueries collection to the UI by setting the DataContext property of the commandGrid control. To do this, paste the following OnImportsSatisfied method code inside the CashMaker class.

    (Code Snippet – Intro to MEF Lab - Ex1 Task1 Step12 - OnImportsSatisfied CSharp)

    C#

    public void OnImportsSatisfied() { this.commandGrid.DataContext = this.CarQueries; }

    (Code Snippet – Intro to MEF Lab - Ex1 Task1 Step 12 - OnImportsSatisfied VB)

    Visual Basic

    Public Sub OnImportsSatisfied() Implements IPartImportsSatisfiedNotification. OnImportsSatisfied Me.commandGrid.DataContext = Me.CarQueries End Sub

    Note:
    After MEF’s Container fills the different imports inside the class, it calls the OnImportsSatisfied method if the class implements the IPartImportsSatisfiedNotification interface. This method is used as a notification when the container has filled all the imports. It is also called after a recomposition occurs.

  13. Now you can remove the old code used to explicitly create and bind the query instances. To do this, in the CashMaker constructor remove the code used to populate the commandGrid.DataContext property as shown below.

    C#

    public CashMaker() { this.InitializeComponent(); new Thread(() => this.GenerateCars()).Start(); // Removed code }

    Visual Basic

    Public Sub New() Me.InitializeComponent() Dim thread = New Thread(() => Me.GenerateCars()) thread.Start() ' Removed code End Sub

Task 2 – Updating Your Composable Part to Export Contracts

In this task, you will modify an existing Windows Class Library project with services classes to query a collection of cars. You will use MEF to mark the queries to export a contract to another application.

  1. Add a reference to the MEF library on the ContosoAutomotive.Extensions project. To do this:
    1. Select the ContosoAutomotive.Extensions project in the Solution Explorer and select Project | Add Reference… The Add References dialog appears.
    2. Select the .NET tab and then select the System.ComponentModel.Composition component. Click the OK button to add a reference to this library.

      Figure 1

      Add a Reference to the MEF library.

  2. Open the CohoQuery.cs file (for Visual C# projects) or CohoQuery.vb file (for Visual Basic projects) in the ContosoAutomotive.Extensions project. Add the following namespace declaration to import the types contained in the MEF library at the top of the CohoQuery class definition.

    C#

    using System.ComponentModel.Composition;

    Visual Basic

    Imports System.ComponentModel.Composition

  3. The CohoQuery class is one of several parts available in your application. Take into account that you used the ImportMany attribute in the CashMaker class to pull in only components that implement the ICarQuery contract. You will decorate the CohoQuery class indicating that it matches that contract. To do this, decorate the CohoQuery class with the Export attribute as shown in the code below:

    C#

    [Export(typeof(ICarQuery))]
    public class CohoQuery : CarQueryBase
    FakePre-cd0c76aac38241c59151dd02b7932435-94ea9c62606a4393835d1a60193e315eFakePre-e501d0819ef542d7920e2d856486129a-53ecabcb4d8d4c6b9770b6dced4acdf5FakePre-f3e5b9daca314307985a29c5d9de436e-e34ade00b9524d5b8cccc19703b9842d

    Visual Basic

    <Export(GetType(ICarQuery))>
    Public Class CohoQuery
    FakePre-a7c15405b350422bbd9f33ac0d6fcd90-cf1aee1b5ec6409581b6a0c710ff41fbFakePre-3ed3bbf5e406472ab01b69e652bdc13a-21e0cb519b8a4fcdbef39b02464f82a3FakePre-bdac28b6ede94e69a9dfdbd7a2e224bd-1f783f9f8ae44ccfbb3201d8996a1275

    Note:
    You are using the Export attribute to define that the CohoQuery class is a part of the application and it implements the ICarQuery contract, which is the type that the import property inside the CashMaker class will look for.

  4. Now, you will apply the same changes to the FabrikamQuery class. To do this, open the FabrikamQuery.cs file (for Visual C# projects) or FabrikamQuery.vb file (for Visual Basic projects) in the ContosoAutomotive.Extensions project. Add the following namespace declaration to import the types contained in the MEF library at the top of the FabrikamQuery class definition.

    C#

    using System.ComponentModel.Composition;

    Visual Basic

    Imports System.ComponentModel.Composition

  5. You will decorate the FabrikamQuery class indicating that it matches the ICarQuery contract. To do this, decorate the FabrikamQuery class with the Export attribute as shown in the code below:

    C#

    [Export(typeof(ICarQuery))]
    public class FabrikamQuery : CarQueryBase
    FakePre-2c74328faf5647ad87c7d6b59f1e6709-51149b0424a94f4196842efa325d74c2FakePre-34b7efe753c64b8cad3564db84ceac42-c93923e1b2eb4ea09183495f9c7e5a65FakePre-276050f0bfee49adb2871095e88e0047-85992bd5ca634f6b8ec9ad0919a5ffd7

    Visual Basic

    <Export(GetType(ICarQuery))>
    Public Class FabrikamQuery
    FakePre-830c03056e274c898e9cb5b4551e9d1d-1105fce2c0b548728daddde22cb4c48dFakePre-d806a5ab19f6432fad6d87d26203f8b9-ff3255ddb3aa4c299848ae89f4fe39b6FakePre-a33c9252345b49e8a08aac6af7a1228c-b1ac686f966347bf8b81efe22e601e9f

  6. At this point, you can start a new instance of the ContosoAutomotive project to check that the two queries are shown in the UI. To do this, right click the ContosoAutomotive project and select Debug | Start new instance.

    Figure 1

    MEF loaded queries in ContosoAutomotive application.

  7. To verify the MEF extensibility, you will add several pre-built query classes. To do this, right-click ContosoAutomotive.Extensions in Solution Explorer, point to Add and select Existing Item. In the Add Existing Item dialog, browse to the ContosoAutomotive.Extensions in Source\Assets for the language of your project, hold the CTRL key down while you select every file in this folder and click Add.

    Figure 2

    Added new queries in ContosoAutomotive.Extensions project (C#)

    Figure 3

    Added new queries in ContosoAutomotive.Extensions project (Visual Basic)

  8. At this point, you can start a new instance of the ContosoAutomotive project to check that the two queries are shown in the UI. To do this, right click the ContosoAutomotive project and select Debug | Start new instance.

    Figure 4

    MEF loaded queries in ContosoAutomotive application

Task 3 – Creating a New Extension

In this task, you will create a new application extension with a query services class. You will use MEF to Export contracts and a build action to avoid references between projects. Additionally, you can mix CLR languages and create the new project in different CLR language than your main application.

  1. Create a new application extension project named Woodgrove. To do this, right click the solution node in the Solution Explorer and select Add | New Project. In the Add New Project dialog select Windows Class Library project type and name it ContosoAutomotive.Woodgrove.

    Figure 5

    New Woodgrove extension project (C#)

    Figure 6

    New Woodgrove extension project (Visual Basic)

  2. Delete the default class file Class1.cs (C#) or Class1.vb (Visual Basic).
  3. Add a reference to the MEF library on the ContosoAutomotive.Woodgrove project. To do this:
    1. Select the ContosoAutomotive.Woodgrove project in the Solution Explorer and select Project | Add Reference… The Add References dialog appears.
    2. Select the .NET tab and then select the System.ComponentModel.Composition component. Click the OK button to add a reference to this library.

      Figure 1

      Add a Reference to the MEF library.

  4. Add a reference to the ContosoAutomotive.Common library on the ContosoAutomotive.Woodgrove project. To do this:
    1. Select the ContosoAutomotive.Woodgrove project in the Solution Explorer and select Project | Add Reference… The Add References dialog appears.
    2. Select the Projects tab and then select the ContosoAutomotive.Common component. Click the OK button to add a reference to this library.

      Figure 1

      Add a Reference to the ContosoAutomotive.Common library.

  5. Create a new class named WoodgroveQuery. To do this, right click the ContosoAutomotive.Woodgrove project and select Add | Class and name it WoodgroveQuery.
  6. Open the WoodgroveQuery.cs file (for Visual C# projects) or WoodgroveQuery.vb file (for Visual Basic projects) in the ContosoAutomotive.Woodgrove project. Add the following namespace declaration to import the types contained in the MEF library at the top of the WoodgroveQuery class definition.

    (Code Snippet – Intro to MEF Lab - Ex1 Task3 Step6 – WoodgroveQueryNamespaces CSharp)

    C#

    using System.ComponentModel.Composition; using ContosoAutomotive.Common;

    (Code Snippet – Intro to MEF Lab - Ex1 Task3 Step6 - WoodgroveQueryNamespaces VB)

    Visual Basic

    Imports System.Collections.Generic Imports System.ComponentModel.Composition Imports ContosoAutomotive.Common

  7. The WoodgroveQueryclass matches the same contract and implements the same interface as the previous modified classes. You will implement a new query class similar to the previous one. To do this, replace the class implementation with the following code.

    (Code Snippet – Intro to MEF Lab - Ex1 Task3 Step7 - WoodgroveQueryClass CSharp)

    C#

    [Export(typeof(ICarQuery))] public class WoodgroveQuery : CarQueryBase { public WoodgroveQuery() { this.Name = "Woodgrove Cycles"; this.Description = "Who doesn't love a Woodgrove? The fastest thing on two wheels!"; this.ImagePath = "Images/woodgrove.jpg"; } protected override IEnumerable<Car> RunQuery(IEnumerable<Car> cars) { var results = from c in cars where c.Make == "Woodgrove" && c.Price >= 50000 && c.Year >= 2000 && c.Transmission == Transmission.Manual && c.SatelliteRadio == true && c.Mpg >= 20 && c.Interior == InteriorType.Suede && c.MoonRoof == false && c.Mileage <= 40000 select c; return results; } }
    FakePre-23b9794a49ce4b4ab72d84d02b59f643-ac38119ae66946a0a40311f7a30e1528
    

    (Code Snippet – Intro to MEF Lab - Ex1 Task3 Step7 - WoodgroveQueryClass VB)

    Visual Basic

    <Export(GetType(ICarQuery))> Public Class WoodgroveQuery Inherits CarQueryBase Public Sub New() Me.Name = "Woodgrove Cycles" Me.Description = "Who doesn't love a Woodgrove? The fastest thing on two wheels!" Me.ImagePath = "Images/woodgrove.jpg" End Sub Protected Overrides Function RunQuery(ByVal cars As IEnumerable(Of Car)) As IEnumerable(Of Car) Dim results = From c In cars _ Where c.Make = "Woodgrove" _ AndAlso c.Price >= 50000 _ AndAlso c.Year >= 2000 _ AndAlso c.Transmission = Transmission.Manual _ AndAlso c.SatelliteRadio = True _ AndAlso c.Mpg >= 20 _ AndAlso c.Interior = InteriorType.Suede _ AndAlso c.MoonRoof = False _ AndAlso c.Mileage <= 40000 Return results End Function End Class
    FakePre-f7bb79b7830c4f2f8a9e6890ce0c80cc-6ab737c2920b4bafbe283d6ea1ff275dFakePre-9691385a27b2495c8fa3ff843d3ab636-52e86d0765fb46bdb76a3cd721817f51FakePre-d5c3b1a88baa4953b645c6d61f8465bf-3c8ca1b84d49409dad454a6fc8616272
    

  8. For the new extension project to be discoverable by the catalog, you should place the assembly in the same folder as the main application. You can achieve this in the development environment by configuring the build events. To do this, right click on the ContosoAutomotive.Woodgrove project and select Properties. In the Properties page, select the Build Events tab (for C# projects) or select the Compile tab and press the Build Events… button (for Visual Basic projects) and enter the following value in the Post-build event command line:

    Post-Build Command

    copy "$(TargetPath)" "$(SolutionDir)ContosoAutomotive\bin\$(ConfigurationName)"

    Note:
    MEF’s Container will be able to discover the application parts inside the new assembly since you are copying it to the application directory, and a Directory Catalog is looking at it.

Next Step

Exercise 1: Verification