MEF

Today I wish to tell about the build system a dynamic, modular application using Managed Extensibility Framework (MEF). Generally this is quite an old technology, which resulted from the system Add-On from the company Microsoft. MEF was a friendly shell over the monstrous system previously proposed. The result was so good that the MEF included in delivery .Net Framework 4 by default, so no need to search and download the library separately, and worry whether the library has a person.

It is also now clear that this technology can be used in the development of Enterprise, as it is in the main supply of the framework, but it’s such an argument against which project managers do not trample. Typically, project managers are very reluctant to use technologies and products which are not mentioned in press releases MS, even if the technology is widely and successfully used by the community. Although they can understand, this is the first point on which they will be kicking in the event of any overlap.

So, MEF easy to make the application modular, and adding new libraries can be carried out on the fly, without the use of sophisticated techniques. All the code is transparent and easy to understand.

MEF can be used starting with version. Net 3.5, taking the necessary libraries from CodePlex. As you may have guessed, from there you can take the source code and see how it all works.

Main features of MEF:

  • Provides a standardized way to organize workshops for the application and its appendices. In general, supplements are not tied to a specific application and implement a common interface can be used by different programs, which generally does not cancel and tight binding to the master application. Supplements may be dependent on one another and MEF alone connect them in the correct order.
  • Provides search and download of the add-ons by MEF
  • Allows you to tag metadata additions for additional opportunities to connect and filter adds.

Creating a simple application

For the beginning do something in the spirit of the HelloWorld application, and then do something more useful, because I would curse myself if focused on HelloWorld example that usually does not demonstrate anything))

So, the application will be very simple that can be. Let it be a console and will display the phrase in various languages, depending on what type of library we will choose as the application. For the beginning ,that’s it! You will be able to focus only on the MEF code.

Create a console application. It will be the master application. In order to connect and find other libraries allocate additional communication interface in the assembly. So, at this stage we will have a console project HelloConsole and the assembly LangDefiniton.

To a project HelloConsole add reference to the assembly System.ComponentModel.Composition System.ComponentModel.Composition and to our library LangDefinition. At this point the solution should look like this:

In the project LangDefinition create a new interface, where we define the method SayHello, which will return the needed text.

namespace LangDefinition {
    public interface ILanguage {
        string SayHello();
    }
}

 

After the defined the interface, you can write code that will load use a supplement to our application. To do this, go to the project HelloConsole and there create a class AddingComposer.

The method, which is responsible for finding and downloading additions will LoadAddings. It shall indicate in which directory to look for additions and load them.

public void LoadAddings() {
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(new DirectoryCatalog("Adding"));

    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
}

 

On the third line indicates exactly where to look for additions. In this case, the specified provider DirectoryCatalog, which allows you to specify the path to the directory with additions (absolute or relative path of the executable library, in this case, the executable file), including filters to specify the library search.

You can also specify an assembly where to look for additions. For this we need to create AssemblyCatalog. In addition, you can specify the types that need to be imported. Use the TypeCatalog.

On the fifth line specifies what and how to gather in the main application. You can mark additions as thread-safe .

To gain access to that found by complement, one has to interface with our field and mark it with a special attribute Import, which tells the system that is imported from the supplement type. Total class would look like this:

public class AddingComposer {
    [Import]
    public ILanguage Language { get; set; }

    public void LoadAddings() {
        var catalog = new AggregateCatalog();
        catalog.Catalogs.Add(new DirectoryCatalog("Adding"));

        var container = new CompositionContainer(catalog);
        container.ComposeParts(this);
    }
}

 

At the moment everything is ready to ensure that write the actual code additions.

Add a new class library project. We call it RussianHello. Refer it to the project with an interface to System.ComponentModel.Composition and implement the interface ILanguage.

namespace RussianHello {
     [Export(typeof (ILanguage))]
     public class RuHello : ILanguage {
          public string SayHello() {
              return "Привет";
          }
     }
}

 

To see MEF class, you have to mark the attribute Export.V this case could not specify the interface exports.

Another point to test the application. It will be useful to specify a folder directly on the formation of libraries Addings in the directory where the executable will lie.

The final step is to call the class AddingComposer in the body of Main.

private static void Main(string[] args) {
    var addingComposer = new AddingComposer();

    addingComposer.LoadAddings();
    var hello = addingComposer.Language.SayHello();
    Console.WriteLine(hello);

    Console.ReadLine();
}

 

After this, run the application and see:

This is the basic functionality of MEF, on which many of the articles and come to an end, but of course we go any further, consistently developing the application.

Adding another language

Create another assembly, where realise the interface ILanguage for English.

namespace EnglishHello {
     [Export(typeof (ILanguage))]
     public class EnHello : ILanguage {
         public string SayHello() {
             return "Hello";
         }
     }
}

After that we can cast the appropriate libraries in the folder Adding and watch the result that the message is changing.

But this will happen only if the folder only additions one more addition. If you cast the two additions, the runtime will throw an error on an application that can not be resolved as a complement to download and use.

To resolve this situation, we use metadata for export applications.

Metadata for export

Metadata for exports may be untyped and typed. Let’s start with untyped, as it’s more simple to use.

weak typing

Metadata is added using a special attribute ExportMetadata. We need the constructor, where you can specify a variable name and its value will be used to uniquely identify the add-on.

For a class RuHello add:

[ExportMetadata("Lang","Ru")]

Then we obtain

[Export(typeof (ILanguage))]
[ExportMetadata("Lang","Ru")]
public class RuHello : ILanguage { ... }

 

Similarly add class EnHello, to see

[Export(typeof (ILanguage))]
[ExportMetadata("Lang","En")]
public class EnHello : ILanguage { ...  }

 

After that we can choose the Build-to-field Lang. Sample itself will be carried out in the master application.

For import multiple we use attribute ImportMany which is used in type to import. This attribute is better to hang on a lazy collection of MEF namespace for posted initialization of components.

[ImportMany(typeof (ILanguage))]
private Lazy<ILanguage, IDictionary<string, object>>[] langVaults { get; set; }

 

Choosing the right additions can be carried out following sample:

public ILanguage GetHello(string type) {
    LoadAddings();

    return (from codeVault in langVaults
              where (string) codeVault.Metadata["Lang"] == type
              select codeVault).Single().Value;
}

 

After this, the main application code looks like this:

public class AddingComposer {
    [ImportMany(typeof (ILanguage))]
    private Lazy<ILanguage, IDictionary<string, object>>[] langVaults { get; set; }

    private void LoadAddings() {
        var catalog = new AggregateCatalog();
        catalog.Catalogs.Add(new DirectoryCatalog("Adding"));

        var container = new CompositionContainer(catalog);
        container.ComposeParts(this);
    }

    public ILanguage GetHello(string type) {
        LoadAddings();

        return (from codeVault in langVaults
                  where (string) codeVault.Metadata["Lang"] == type
                  select codeVault).Single().Value;
    }
}

private static void Main(string[] args) {
    var lang = Console.ReadLine();

    var addingComposer = new AddingComposer();
    var hello = addingComposer
               .GetHello(lang)
               .SayHello();

    Console.WriteLine(hello);

    Console.ReadLine();
}

 

You can mark try to enter values Ru and En and see how the application displays the data to us from the right supplements.

Best of all, before the application starts you can do to clean the folder Adding, copyingand there additions as you work. They will be picked up without restarting the application.

Strong Typing

Now try the same things with typed metadata.

In general, the algorithm is as follows: declare a class that will be typed attribute, inherited from his ExportAttribute. Create an interface that will follow a set of properties, but only for reading. Use the newly created attribute on the desired class.

Assume that we have changed the metadata to the point:

namespace EnglishHello {
     [Export(typeof (ILanguage)),
      ExportMetadata("Lang","En"),
      ExportMetadata("Version","2"),
      ExportMetadata("Comment","English language"),
     ]
      public class EnHello : ILanguage {
          public string SayHello() {
             return "Hello";
          }
      }
}

 

Logically languages are fairly limited and may be given enumerated type, version can only be an integer value, this comment line. To get all the advantages of typing, you need to create a necessary attribute types. Since then begin.

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class LangMetaData : ExportAttribute {
    public Lang Language { get; set; }
    public int Version { get; set; }
    public string Comment { get; set; }
}

 

The class inherits from ExportAttribute, while attributes are applied to it MetadataAttribute and indicates the types it will be used and how. In this case, the attribute should apply only to classes with multiple inheritance through the use is prohibited.

Now we need to create an interface that will follow a set of properties, and they must be declared read-only.

public interface ILangMetaData {
    Lang Language { get;  }
    int Version { get;  }
    string Comment { get; }
}

 

The interface can be declared next to the attribute.

Then you can use attribute. It looks prettier than untyped metadata.

namespace EnglishHello {
     [Export(typeof(ILanguage)),
     LangMetaData(
         Language = Lang.En,
         Version = 2,
         Comment = "English Language"
     )]
     public class EnHello : ILanguage {
         public string SayHello() {
             return "Hello";
         }
     }
}

 

There is no way to make a mistake, the compiler will notice;

  • Know precisely what options might be, intelliSense tell;
  • It is easy to change and seek to use the project properties;
  • Sane filter when searching for additions to the application.

It remains only to modify the master application to strongly typed metadata understood. The list will announce as we desired dictionary from the interface and the interface metadata.

[ImportMany]
private Lazy<ILanguage, ILangMetaData>[] langVaults { get; set; }

 

I draw your attention that the attribute is not specified ImportMany type that can be booted. If you leave the type, then load the add-on twice and MEF can not correctly resolve the dependency. It is necessary to specify the type or in ImportMany, or in Export, but not in two places at once.

Method GetHello now changed, and in my opinion would be more informative and accurate
Метод GetHello теперь изменится, и на мой взгляд станет более информативным и аккуратным

public ILanguage GetHello(Lang lang) {
    LoadAddings();

    return (from codeVault in langVaults
              where codeVault.Metadata.Language == lang
              select codeVault).Single().Value;
}

 

Hmm … I haven’t guessed any difficult examples yet, right? Well, it’s probably because I do not particularly complicated things and treated. Rest in the next section, and even so there are too many letters. Wait, it is almost ready;)

Hard’n’heavy

Tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>