[Note: This article was based upon a very early preview of the Managed Extensibility Framework and may differ substantially from later releases.]

Introduction

Over the years applications have followed an increasing trend of providing the ability to extend their core features through the use of various extensibility mechanisms. From the Microsoft Office suite of applications, to Internet browsers, to instance messenger programs … virtually every application we use today offers some form of extensibility. Unfortunately, developing extensible applications on the .Net platform has always required development teams to provide their own infrastructure. That is, until now.

The Managed Extensibility Framework is a new library being developed by Microsoft to support the creation of extensible applications on the .Net platform. The following is a brief introduction to the features within the Managed Extensibility Framework.

Overview

The Managed Extensibility Framework provides an infrastructure for enabling an application to easily consume extensions by providing the ability to dynamically bind internal and add-in components together through contract-based declarations. The main types of components within the framework are the Container, the Catalog, and the composable Parts.

Container

The Container is responsible for managing the creation and composition of dependencies between parts. It is represented within the framework by the type CompositionContainer. This type implements an interface, ICompositionService, which can itself be added to the container for explicit use by Parts during the lifetime of the application.

Catalogs

Catalogs are responsible for discovering dependencies between the registered Parts. All catalogs extend the abstract base type ComposablePartCatalog. There are currently four types of catalogs provided within MEF:

AttributedAssemblyPartCatalog provides discovery of Parts within a collection of types
AttributedTypesPartCatalog provides discovery of Parts within a given assembly
DirectoryPartCatalog provides discovery of Parts within a given file system directory
AggregatingComposablePartCatalog provides the ability to combine multiple catalogs into a single composite catalog

Parts

Parts are the logical units of composition within MEF and are used to encapsulate information about types used during the composition process. Each Part is identified by a Contract and contains a list of Exports and Imports. Contracts take the form of a string identifier and are used to identify dependencies between Parts. Exports specify the services offered to other Parts, while Imports specify services required by other Parts.

Example Application – MefPad

The following example demonstrates the basic features of the Managed Extensibility Framework by walking through the creation of a simple text editor application which utilizes extensions as the file persistence mechanism.

Step 1 – Project and Form Creation

  • Create a new Windows Forms Application project named “MefPad”.
  • Add a reference to the System.ComponentModel.Composition assembly.
  • Rename “Form1.cs” to “MefPad.cs”.
  • Add a MenuStrip control docked at the top.
  • Add a top level menu item named “File”.
  • Add a submenu item under “File” named “Open”.
  • Double-click on the “Open” menu item to auto-create a Click event handler.
  • Return to the designer and add another submenu item under “File” named “Save”.
  • Double-click on the “Save” menu item to auto-create a Click event handler.
  • Return to the designer and add a TextBox control named “mainContentTextBox”.
  • Change the Dock property to “Fill”.

The following illustrates the resulting MefPad form design:

Step 2 – Configure MEF Container

  • Add the following call to Compose() in the MefPad constructor:
        public MefPad()
        {
            InitializeComponent();
            Compose();
        }
  • Add the following method:
        void Compose()
        {
            // search currently directory for extension assemblies
            var catalog = new DirectoryPartCatalog(".", "*Extensions.dll", true);
            var container = new CompositionContainer(catalog);
            container.AddPart(this);
            container.Compose();
        }

This method uses a DirectoryPartCatalog to discover any MefPad extensions. In this case, the catalog will discover any assemblies in the current executing directory whose name ends with “Extensions.dll”. Next, a CompositionContainer is instantiated with the catalog. The MefPad Form (i.e. “this”) is then added as a Part to the container. This will allow any Exports or Imports within the MefPad class to be discovered by the container. Finally, the container is directed to compose all Parts.

Step 3 – Define an Extension

  • Create a new interface type named IFilePersistenceExtension:
using System.IO;

namespace MEFPad
{
    public interface IFilePersistenceExtension
    {
        /// <summary>
        /// Description of the file type.
        /// </summary>
        string Description { get; }

        /// <summary>
        /// The extension of the file type.
        /// </summary>
        string Extension { get; }

        /// <summary>
        /// Reads from an open stream.
        /// </summary>
        /// <param name="stream">An open <see cref="Stream"/></param>
        /// <returns>character array of text read.</returns>
        char[] Read(Stream stream);

        /// <summary>
        /// Writes to an open stream.
        /// </summary>
        /// <param name="stream">An open <see cref="Stream"/></param>
        /// <param name="text">The text to be written to the stream</param>
        void Write(Stream stream, char[] text);
    }
}

This interface defines the types which MefPad will use for reading and saving files to disk. The Description and Extension properties will be used by the file dialog box presented to the user while the Read() and Write() methods will be used to open and save files respectively.

Step 4 – Create an Import

  • Add the following property to the MefPad class:
        [Import]
        public List<IFilePersistenceExtension> FilePersistenceExtensions { get; set; }

This property will contain the collection of IFilePersistenceExtension types which will be presented as file type options to the user. The [Import] attribute is used to indicate that the MefPad Part has a dependency upon IFilePersistenceExtension types. Upon discovering any Parts with a contract of this type within the Catalog, the Container will add instances of the Part to the FilePersistenceExtensions collection.

Step 5 – Create the Business Logic

  • Add the following method to the MefPad.cs class:
        T GetFileDialog() where T : FileDialog, new()
        {
            var fileDialog = new T();
            var fileTypesBuffer = new StringBuilder();

            if (FilePersistenceExtensions != null)
                FilePersistenceExtensions
                    .ForEach(x =>
                             fileTypesBuffer.Append(string.Format("{0}{1}|*{2}",
                                                                  (fileTypesBuffer.Length == 0)
                                                                      ? null
                                                                      : "|", x.Description,
                                                                  x.Extension)));

            fileDialog.Filter = fileTypesBuffer.ToString();
            return fileDialog;
        }
    }

This method provides the common setup behavior used when configuring the open and save dialog boxes to be presented to the user. The ForEach() method is used to add file types to the dialog filter.

  • Replace the openToolStripMenuItem_Click() and saveToolStripMenuItem_Click() event handler methods with the following code:
        void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Stream stream;
            OpenFileDialog openFileDialog = GetFileDialog<OpenFileDialog>();

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                if ((stream = openFileDialog.OpenFile()) != null)
                {
                    char[] content = FilePersistenceExtensions[openFileDialog.FilterIndex - 1].Read(stream);
                    mainContentTextBox.Text = new String(content);
                    stream.Close();
                }
            }
        }

        void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Stream stream;
            SaveFileDialog saveFileDialog = GetFileDialog<SaveFileDialog>();

            if (saveFileDialog.ShowDialog() == DialogResult.OK)
            {
                if ((stream = saveFileDialog.OpenFile()) != null)
                {
                    FilePersistenceExtensions[saveFileDialog.FilterIndex - 1].Write(stream, mainContentTextBox.Text.ToCharArray());
                    stream.Close();
                }
            }
        }

These methods handle the “Open” and “Save” Click menu item events respectively. The openToolStripMenuItem_Click() method calls the Read() method of the IFilePersistenceExtension instance corresponding to the selected filter index. Likewise, the saveToolSripMenuItem_Click() method calls the Write() method of the IFilePersistenceExtension instance corresponding to the selected filter index.

Step 6 – Create the Extensions

  • Create a new Class Library project named MefPadExtensions.
  • Add a reference to the MefPad project.
  • Add a reference to the System.ComponentModel.Composition.
  • Change the build output folder to ..MEFPadbinDebug.
  • Create a new class named TextFilePersistenceExtension with the following source:
using System.IO;

namespace MEFPadExtensions
{
    [Export(typeof (IFilePersistenceExtension))]
    public class TextFileSaverExtension : IFilePersistenceExtension
    {
        public string Description
        {
            get { return "Text file"; }
        }

        public string Extension
        {
            get { return ".txt"; }
        }

        public char[] Read(Stream stream)
        {
            var streamReader = new StreamReader(stream);
            string bytes;

            try
            {
                bytes = streamReader.ReadToEnd();
                return bytes.ToCharArray();
            }

            finally
            {
                streamReader.Close();
                stream.Close();
            }
        }

        public void Write(Stream stream, char[] text)
        {
            var streamWriter = new StreamWriter(stream);

            try
            {
                streamWriter.Write(text);
            }
            finally
            {
                streamWriter.Close();
                stream.Close();
            }
        }
    }
}

This class provides the MefPad application with simple file persistence capabilities.

  • Create a new class named EncryptedFilePersistenceExtension with the following source:
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace MEFPadExtensions
{
    [Export(typeof (IFilePersistenceExtension))]
    public class EncryptedFilePersistenceExtension : IFilePersistenceExtension
    {
        readonly byte[] iv = Encoding.ASCII.GetBytes("ABCDEFGHIJKLMNOP");
        readonly byte[] key = Encoding.ASCII.GetBytes("ABCDEFGHIJKLMNOP");

        public string Description
        {
            get { return "Encrypted"; }
        }

        public string Extension
        {
            get { return ".crypt"; }
        }

        public char[] Read(Stream stream)
        {
            Rijndael rijndael = Rijndael.Create();
            var cryptoStream = new CryptoStream(stream,
                                                rijndael.CreateDecryptor(key, iv),
                                                CryptoStreamMode.Read);
            var streamReader = new StreamReader(cryptoStream);
            string bytes;

            try
            {
                bytes = streamReader.ReadToEnd();
                return bytes.ToCharArray();
            }
            finally
            {
                streamReader.Close();
                cryptoStream.Close();
                stream.Close();
            }
        }

        public void Write(Stream stream, char[] text)
        {
            Rijndael rijndael = Rijndael.Create();
            var cryptoStream = new CryptoStream(stream,
                                                rijndael.CreateEncryptor(key, iv),
                                                CryptoStreamMode.Write);
            var streamWriter = new StreamWriter(cryptoStream);

            try
            {
                streamWriter.Write(text);
            }
            finally
            {
                streamWriter.Close();
                cryptoStream.Close();
                stream.Close();
            }
        }
    }
}

This class provides the MefPad application with encrypted file persistence capabilities.

Step 7 – Run the Application

  • Start the application and enter some text into the text box.

  • From the menu bar, select File -> Save

Notice in the Save as type: dropdown box that two choices are presented: “Text file” and “Encrypted”. Selecting each of these types will result in the entered text being saved using the corresponding extension. The source for this example can be downloaded here.

Conclusion

The Managed Extensibility Framework provides developers with the tools needed to easily create extensible .Net applications. Moreover, its future ubiquitous presence as an option for enabling extensibility will provide a readily accessible solution across projects and companies. By leveraging MEF for your extensibility needs, you’ll be able to focus less on the concerns of infrastructure development and more on simply creating great software for your company.

Tagged with:  

Skinning with Extension Methods

On October 19, 2008, in Uncategorized, by derekgreer

Introduction

One of the new language features introduced with C# 3.0 and Visual Basic.Net 9.0 is Extension Methods. Extension methods enable new behaviors to be invoked on otherwise closed types. One application of extension methods that will be discussed here is their use in class library development for enabling multiple styles of interfaces for components. This might be thought of as “skinning” an API.

Extension Methods Overview

Extension methods are a syntactic sugar approach to enabling the adornment of new behaviors on existing types without requiring the type to be modified or sub-classed. Their use enables coding the invocation of an extension method as if it were a member of the extended type. This is achieved by providing a static method whose first parameter is of the type to be extended, specified with the this modifier. The following is a simple example of an extension method which provides a Reverse() method for System.String:

using System;
using System.Text;

namespace ExtensionMethodConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            string testString = "This is a test.";
            Console.WriteLine(testString.Reverse());
            Console.ReadLine();
        }
    }

    public static class StringExtensions
    {
        public static String Reverse(this string s)
        {
            var sb = new StringBuilder();

            for (int i = s.Length; i > 0; i--)
            {
                sb.Append(s[i - 1]);
            }

            return sb.ToString();
        }
    }
}

Once compiled, this call to string.Reverse() is converted to an invocation of the static Reverse() method with the string instance passed as the first parameter. The following output of ildasm.exe shows the generated code:

  .method private hidebysig static void  Main(string[] args) cil managed

  {

    .entrypoint

   // Code size       26 (0x1a)

    .maxstack  1

    .locals init ([0] string testString)

    IL_0000:  nop

    IL_0001:  ldstr      "This is a test."

    IL_0006:  stloc.0

    IL_0007:  ldloc.0

    IL_0008:  call       string ExtensionMethodConsoleApplication.StringExtensions::Reverse(string)

    IL_000d:  call       void [mscorlib]System.Console::WriteLine(string)

    IL_0012:  nop

    IL_0013:  call       string [mscorlib]System.Console::ReadLine()

    IL_0018:  pop

    IL_0019:  ret

  } // end of method Program::Main

Class Library Skins

Class libraries are typically developed to segment a specific set of cohesive behaviors within an application, and are often created for use by multiple consumers. In some cases, the nature of a class library may lend itself to decoupling the consumer API from the interface and behavior implementation, allowing multiple API styles to exist for the same component.

Consider, for example, a class library which contains no inherent behavior of its own, but exists solely for providing a common API which abstracts a choice from among multiple class libraries designed for the same purpose. This is often required within application frameworks needing to provide a common infrastructure while remaining loosely coupled to the specific infrastructure implementation choice.

The usual approach for providing an abstraction API is simply for the author to choose an API style which suites their own tastes. This may be dissimilar to any specific class library, but will sometimes result in an API modeled after the author’s favored implementation choice … perhaps to the dismay of those required to consume the API. When the behavior of the class library can be represented fairly concisely, but requires a fair amount of convenience methods to make the API palatable, this presents an excellent opportunity to utilize extension methods to provide API “skins”.

For example, consider the common task of logging. For .Net development, there exists several logging APIs to choose from in addition to the native tracing API. Two popular libraries are Log4Net and the Enterprise Library Logging Application Block. While both libraries provide similar features, the APIs provided by each are stylistically very different. Log4Net supplies
an API which provides different methods for each message severity type (e.g. Log.Error(…), Log.Fatal(…)) while the Logging Application Block provides a number of overloaded Write() methods which follow the Microsoft method naming guidelines whereby verbs are used to name methods.

One approach to providing a concise façade that will accommodate both libraries without choosing one style over another would be to encapsulate the information of each log message into a single type (e.g. an ILogEntry) and to provide an interface which exposes a single Write() method which takes an ILogEntry as its single parameter:

    public interface ILoggingService
    {
        void Write(ILogEntry log);
    }

    public interface ILogEntry
    {
        Guid ActivityId { get; set; }
        string AppDomainName { get; set; }
        ICollection<string> Categories { get; set; }
        string Message { get; set; }
        int Priority { get; set; }
        string ProcessId { get; set; }
        string ProcessName { get; set; }
        TraceEventType Severity { get; set; }
        DateTime TimeStamp { get; set; }

        // ... other properties and methods
    }

In fairness, this approach does reflect some of characteristics of the Logging Application Block API, as the methods on its Static Logger façade similarly result in a call to a LogWriter.Write(LogEntry entry) method. Nevertheless, following this example of encapsulating the information about a log message into a single type greatly simplifies the interface, resulting in less demands being placed on implementers and allowing extension methods to provide variability in how the service is exercised.

With the ILoggingService defined, two sets of extension methods matching both the Logging Application Block and the Log4Net APIs may be defined, providing the usual set of methods familiar to proponents of each:

    public static class EntLibLogServiceExtensions

    {
        public static void Write(this ILoggingService loggingService, object message)
        {
            // ...
        }

        public static void Write(this ILoggingService loggingService, object message, string category)
        {
            // ...
        }

        public static void Write(this ILoggingService loggingService, object message, string category, int priority,
                                 int eventId, TraceEventType severity)
        {
            // ...
        }

        // ... TODO implement remainng Logging Application Block API
    }

    public static class Log4NetLogServiceExtensions
    {
        public static void Debug(this ILoggingService loggingService, object message)
        {
            // ...
        }

        public static void Error(this ILoggingService loggingService, object message)
        {
            // ...
        }

        public static void Fatal(this ILoggingService loggingService, object message)
        {
            // ...
        }

        // ... TODO implement remainng Log4Net API
    }

By ensuring each collection of extension methods are scoped in their own namespace, consumers are also free to choose which API style is exposed through the IDE’s intellisense.

Conclusion

While the use of extension methods should be used judiciously in general, and their use for providing themed-APIs will certainly not be appropriate in most cases, this feature does provide a nice “specialty tool” in the toolbox of class library designers.

Tagged with: