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
}