Extensibility as Defined by Wikipedia-

In software engineering, extensibility (sometimes confused with forward compatibility) is a system design principle where the implementation takes into consideration future growth. It is a systemic measure of the ability to extend a system and the level of effort required to implement the extension. Extensions can be through the addition of new functionality or through modification of existing functionality. The central theme is to provide for change while minimizing impact to existing system functions.

Late Binding and Interfaces

If you want to your application to be able to grow and be used in ways not originally planned for their are two concepts that you need to keep in mind. The first is Late Binding. 

The concept behind Late Binding is that your application is not aware of the specific implementation of a piece of functionality at compile time.  This basically boils down to creating your application with the idea that you or someone else will add to or change its behavior in the future.

The use of Interfaces is what allows this Late Binding to be able to happen in a simple manner.  Basically you define how your application can be extended and create Interfaces that modal the extension points of your application.  In the case of a data driven web site maybe you are going to allow custom validation/rendering and actions on the data or maybe you are going to have some widget/webpart framework that you are going to use.  Applications like Paint.Net allow you to add Image transformation plug-ins as the application is designed around image manipulation.  The more areas you open up to extension the longer shelf life your application is liable to have and the fewer growing pains that it will have.

MEF-Scripting(Iron*)-IOC

MEF- If you haven't heard of MEF(Managed Extensibility Framework) than I highly recommend you check it out. MEF is built around the idea of Importing/Exporting parts(Interfaces in our discussion here).  It allows you to declare what Interfaces you want to import and where you want to look for the dll's that have the created the classes that implement these Interfaces.  MEF handles the discovery and instantiation of the classes and makes the late binding a 5-6 line affair.

Scripting-  Using IronRuby and IronPython in your .Net application isn't actually late binding as the scripting languages aren't compiled into dll's and loaded at runtime but they allow your application to get instantiated objects that implement the Interfaces that you define at runtime.  I will put a note here that debugging IronScripts running in your .Net application is not the easiest thing ever.  For more information on how to get .Net interfaces from an IronRuby script check out this previous post http://blog.runxc.com/post/2010/03/12/Calling-an-Interface-Method-using-IronRuby-in-C.aspx

IOC-  Inversion of Control has many of the same benefits as MEF but its focus is more on being able to easily change out a piece of your application such as the logging or Repository as compared to MEF which focuses on Extensibility and plugins.  I won't go into IOC too much here as much has been said on it.

Putting it all together

In the application that I have been building we want to make extending the application as easy as possible and we have been using both MEF and IronRuby for different scenarios.  Using them more and more I have been realizing that the two technologies can be used interchangeably if set up correctly.

Code Example

I have been giving a few demonstrations lately to developers where I work to bring them up to speed in MEF and decided to incorporate IronRuby and have an example that uses both of them.

I have created a small console application in .Net 4.0 that is based off of a blog article called the MEF Zoo which I recommend you read if you want to get up to speed on how to use MEF.  The application "Interface Driven Extensibility Zoo" or IDE Zoo is a zoo that lets each of the animals speak and lets you know where the came from below is the code for the console app.

namespace IDE
{
    class Program
    {
        static void Main(string[] args)
        {
            Zoo zoo = new Zoo();
 
            foreach (IAnimal animal in zoo.Animals)
            {
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("The {0} says:", animal.GetType().ToString());
                Console.ForegroundColor = animal.Color;
                Console.WriteLine(animal.Speak());
 
            }
            Console.ReadKey();
        }
 
    }
 
    public class Zoo
    {
        public Zoo()
        {
            ExtensibilityHelper.Compose(this);
        }
        [ImportMany]
        public List<IAnimal> Animals { get; set; }
 
    }
 
    [InheritedExport]
    public interface IAnimal
    {
        ConsoleColor Color { get; }
        string Speak();
    }
}

 

All of the magic  in IDE Zoo happens in the static method ExtensibilityHelper.Compose  shown below.

public class ExtensibilityHelper
{
    public static void Compose(Zoo zoo)
    {
        var runtime = Ruby.CreateRuntime();
            
        var engine = runtime.GetEngine("rb");
        //A catalog that can aggregate other catalogs
        var aggrCatalog = new AggregateCatalog();
        string pluginFolder = 
            Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Plugins";
        //A directory catalog, to load parts from dlls in the Extensions folder
        var dirCatalog = new DirectoryCatalog(pluginFolder, "*.dll");
        //An assembly catalog to load information about part from this assembly
        var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
 
        aggrCatalog.Catalogs.Add(dirCatalog);
        aggrCatalog.Catalogs.Add(asmCatalog);
 
        //Create a container
        var container = new CompositionContainer(aggrCatalog);
 
        //Composing the parts
        container.ComposeParts(zoo);
            
        //Lets now get all the parts that are exposed as IronRuby scripts
        var rubyFileNames = 
            Directory.GetFiles(pluginFolder, "*.rb").Select(x => new FileInfo(x));
        var IAnimals = rubyFileNames.Where(x=> x.Name.StartsWith("IAnimal")).ToList();
        foreach (var animalInfo in IAnimals)
        {
            var animal = engine.Execute<IAnimal>(File.ReadAllText(animalInfo.FullName));
            zoo.Animals.Add(animal);
        }
    }
}

 

And just to add even more code below are some of the animals.

IronRuby Animal

require 'IDE'


class Snake
    include IDE::IAnimal
             
    def Color
        return System::ConsoleColor.Green
    end

    def Speak()
        return "Hisss".ToString()
    end
end
return Snake.new

 

.Net Animal

    public class Tiger: IAnimal
    {
        #region IAnimal Members
 
        public ConsoleColor Color
        {
            get { return ConsoleColor.Yellow; }
        }
 
        public string Speak()
        {
            return "Roar";
        }
 
        #endregion
    }

 

IDEZoo SolutionIDEZoo.zip

Submit this story to DotNetKicksShout it   Bookmark and Share