I was toying last night with my work-in-progress WPF version of Comicster, specifically looking at file formats. I want this version to easily support opening and saving collections to multiple file formats, and the logical way to do that was via some sort of plug-in system.

Eventually (and by that I mean probably when .NET 4.0 is released) I'll look at using the Managed Extensibility Framework to power the file-format plug-in system. I found a great post by Brad Abrams which walks through a simple MEF tutorial, and it looks trivially easy to, say, scan a folder for plug-in assemblies and grab the relevant types from them.

In the meantime, though, I've settled on defining my various "core" file formats in a single assembly and using reflection to load them on demand. The idea is that I've created two simple interfaces: ICollectionReader and ICollectionWriter. I've separated the read and write functionality because some file formats will only support opening and not saving. Then in the same assembly I've defined a few classes that implement those interfaces. For example, XmlCollectionReader (for XML files - duh) and PackageCollectionReader (for CMXX files, see this post for more info).

Once those classes were defined, I needed a way, when the user clicks the "Open" button, to find which file formats were available to read. I settled upon this nifty LINQ statement:

var readers = (
       from t in Assembly.GetAssembly(typeof(ICollectionReader)).GetExportedTypes()
       where t.IsClass
           && typeof(ICollectionReader).IsAssignableFrom(t)
           && t.GetConstructor(new Type[0]) != null
       select Activator.CreateInstance(t) as ICollectionReader
   ).ToList();

So what's happening here? I'm asking for a list of all the public classes that live in the same assembly as ICollectionReader and implement that interface, and I make sure that they have a constructor which takes no parameters, because otherwise the call to Activator.CreateInstance would throw an exception. I end up with a list of instances of these "reader" objects.

Each reader object has two properties: "Extension" which is the file extension for the file format supported by that reader (eg ".xml") and "Filter" which is the friendly name for that file format to display in a file dialog (eg "XML Files (*.xml)|*.xml"). Using those properties I can construct an OpenFileDialog based on all of the known reader objects:

var ofd = new OpenFileDialog
{
    Title = "Open Collection",
    Filter = string.Join("|", readers.Select(r => r.Filter).ToArray()),
    DefaultExt = readers[0].Extension,
};

Once I show that dialog I can check that the filename the user selected has a supported reader:

var reader = readers.FirstOrDefault(r => string.Compare(r.Extension,
    Path.GetExtension(ofd.FileName), StringComparison.OrdinalIgnoreCase) == 0);
if (reader == null) { MessageBox.Show("No readers available for this file extension"); return; }

And lastly, use the ICollectionReader's "ReadCollection" method to open the file:

this.DataContext = reader.ReadCollection(ofd.OpenFile());

There's a little bit more code in my "Open" command's Execute handler to do some sanity checking, but this is the meat-and-potatoes of it. The thing I'm most proud of is the initial LINQ statement, but ironically that'd be the first thing to go if I were to move to MEF. Ah well - if you want to make a MEF omelette you have to break a few Reflection eggs.