Efter at have kigget lidt på MEF i teorien (1) (2), så har jeg fået brugt det i praksis.

Sammen med Martin Esmann er udviklingen af Surface Billed/Video applikationen nu blevet til en “Surface MEF applikation” hvor man selv kan skrive plug-ins til.

surfacedemo 

Så nu fungerer applikationen på følgende vis:

  1. Læg et tag på Surface bordet. Surface tags ser sådan her ud:
    __surface_coin_bw2
    Et tag er en surface-læsbar-stegkode. Som jeg hørte det på PDC, kan et tag indholde 2 Guid’s (temmelig mange unikke tags). UI’et ovenfor er designet til at tag’et sidder på bagsiden af en telefon.
  2. Instantier en usercontrol uden om tag’et med TagVisualizer kontrollen.
  3. Kald en webservice (hosted på Windows Azure selvfølgelig) hvor input er værdien af tag’et (ovenfor er det hex 0XC0). Webservicen returnerer en Dictionary<string,string> som er name-value pairs som feks {“welcometext”,”Hej Henrik!”} som brugt ovenfor.
  4. Load plug-ins. Ovenfor loades 4 stk (Videos, Clear, Flickr og Twitter).
  5. Hvert plugin har nu, i denne implementering, mulighed for at tilføje et UIElement til et Stackpanel (ovenfor har vi været så fantasifulde at vi kun har tilføjet SurfaceButtons), og udover det, så kan hvert plugin tilføje items til et datalag der er databundet til mit ScatterView. I demoen ovenfor har Flickr plugin’et hentet billeder fra min Flickr konto og smidt dem ind i scatterview’et
  6. Når tag’et fjernes fra bordet igen, så fjernes alt som du har tilføjet.

Hvis vi kigger på lidt kode, så lad os først kigge på hvordan et plugin ser ud når MEF anvendes. Efterfølgende kigger vi på SurfaceApplikationen som konsumerer plugins.

Nedenstående kode er implementeringen af min “Clear” service/plugin, som ikke gør andet end at slette alle mine elementer fra scatterview’et.

public interface ISocialAction
{
    UIElement GetUIElement();
    IDataProvider dataprovider { get; set; }
    ICloudDataProvider clouddata { get; set; }
    string tag { get; set; }
}
[Export(typeof(ISocialAction))] public class ClearService :
ISocialAction { [Import(typeof(ICloudDataProvider))] public ICloudDataProvider clouddata { get; set; } [Import(typeof(IDataProvider))] public IDataProvider dataprovider { get; set; } public string tag { get; set; } SurfaceButton sb = new SurfaceButton(); [ImportingConstructor] public ClearService([Import("Tag")] string tag) { this.tag = tag; } public UIElement GetUIElement() { sb.Content = "Clear"; sb.Click += new RoutedEventHandler(sb_Click); return sb; } void sb_Click(object sender, RoutedEventArgs e) { dataprovider.RemoveItems("default"); dataprovider.RemoveItems(tag.ToString()); } }

Det interessante i oventstående er, at jeg først laver en [Export(typeof(ISocialAction))]. Altså udstiller jeg klassen som et plugin af typen ISocialAction. Derefter er der 3 imports som er interessante. Første import binder til et plugin af typen ICloudData. ICloudData er bare en lille wrapper om min Azure webservice og er ikke så interessant her, da jeg ikke bruger den. Dernæst er der IDataProvider som er lidt indpakning af ObservableCollection<ScatterViewItem>. Implementeringen er ligegyldig, men mit interface og Export ser sådan ud:

public interface IDataProvider
{
    IEnumerable<ScatterViewItem> GetItemsByTag(string tagid);
    void AddItems(string tagID, List<ScatterViewItem> list);
    void AddItem(string tagID, ScatterViewItem item);
    IEnumerable<ScatterViewItem> GetItem<T>(string tagid);
    void RemoveItems(string tagID);
    void RemoveItems<T>(string tagID);
}
[Export(typeof(IDataProvider))]
public class DataProvider : IDataProvider
{
    static public ObservableCollection<ScatterViewItem> items { get; set; }

….

Sidste import er mit Tag som jeg bruger i constructoren. Det vil sige, at når jeg konstruerer en ClearService, så finder konstruktøreren selv en Export der matcher dette import.

Når jeg så placerer en tag (tag’et telefon) på Surfacebordet, så kalder jeg følgende kode fra min usercontrol:

[Import(typeof(ISocialAction))]
public IEnumerable<ISocialAction> Actions;
CompositionContainer c;
public void Init(int tag)
{
    this.VisualTag = tag.ToString(); ;
    var catalog = new AggregateCatalog();
    System.IO.Directory.CreateDirectory(@"c:\temp\Extensions");

    catalog.Catalogs.Add(new DirectoryCatalog("Extensions"));
    catalog.Catalogs.Add(new DirectoryCatalog(@"c:\temp\Extensions"));
    c = new CompositionContainer(catalog);
    var batch = new CompositionBatch();
    batch.AddPart(this);
    c.Compose(batch);
    foreach (var ext in Actions)
    {
        UIElement ui = ext.GetUIElement();
        this.Stackpanel.Children.Add(ui);
    }
….

Det interessante her at når Compose bliver kaldt, så matches alle imports og exports, og jeg får instantieret min Actions property, så jeg kan kalde GetUIElement på alle ISocialAction’s.

På denne vis har vi fået en god opdeling mellem selve Surface applikationen og de funktionaliteter (plug-ins) som der er. Faktisk kan vi tilføje plugins dynamisk ved at smide dll’er som indeholder ISocialActions i c:\temp\Extensions.