Önceki bir kaç yazımda MEF(Managed Extensibility Framework) ile ilgili bir şeyler yazmış, kısaca ve basitçe anlatmaya çalışmıştım. Bu yazımda onları birleştirerek ve ilerki yazılarda da kullanabileceğimiz bir örnek olması adına MEF’in WPF ortamında basitçe uygulanmasına değinerek MEF’i biraz daha iyi anlamaya çalışacağız.
Biz yazılımcılar daha çok kod kavramını sevdiğimiz için 🙂 çok karmaşık olmasa da diğer yazılara nazaran biraz daha kod örneği içeren bir yazı olacak bu şimdiden belirtim. Bu arada kodları elimden geldiğince çok basit yazıp, ilerleyen yazılarda başka konular ile onları değiştirerek biraz daha eli yüzü düzgün hale getireceğiz.
Ne yapacağız?
Gerçek hayatta kullanamayacağımız, anlamsız bir WPF “plugin” uygulaması yapıyor olacağız. Gerçek hayatta kullanamayacağız belki ama kullanılabilir uygulamalar yapmamız açısından vizyon katacak bir örnek olacak.
Başlıyoruz…
Öncelikle yandaki resimdeki gibi bir proje yapısı oluşturalım. “WPFMEF” ana WPF uygulamamız olacak. Bu uygulama, yüklenen “plugin”leri çalıştırmakla yükümlü olacak. “FirstWPFPlugin” ve “SecondWPFPlugin” projeleri ise “WPF User Control” olarak yaratacağımız, WPF’e kullanıcı kontrolü olarak yükleyeceğimiz,”plugin”ler olacak...”Common” projesi ise tüm uygulamalarda ortak olarak kullnabileceğimiz bileşenleri içerecek.
Ana uygulama…
“WPFMEF” projesi az önce belirttiğim gibi MEF ile oluşturduğumuz “plugin”leri çalıştaracak uygulama. Basitçe aşağıdaki gibi bir ekran görüntüsüne sahip.
Ya da aşağıdaki gibi bir XAML’e…
<Window x:Class="WPFMEF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="502" Width="775">
<Window.Resources>
<DataTemplate x:Key="ListBoxDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="90"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=Name}"></TextBlock>
<Button Grid.Column="1" Width="50" Content="Çalıştır" CommandParameter="{Binding Path=Name}" Click="Button_Click"></Button>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Height="374" ItemTemplate="{StaticResource ResourceKey=ListBoxDataTemplate}" HorizontalAlignment="Left" Margin="12,54,0,0" Name="listBox1" VerticalAlignment="Top" Width="190" />
<Label Content="Sistemde yüklü pluginler:" Height="28" HorizontalAlignment="Left" Margin="0,20,0,0" Name="label1" VerticalAlignment="Top" Width="190" />
<Canvas Height="374" HorizontalAlignment="Left" Margin="211,54,0,0" Name="canvas1" VerticalAlignment="Top" Width="502" />
<Label Content="Plugin içeriği" Height="28" HorizontalAlignment="Left" Margin="211,20,0,0" Name="label2" VerticalAlignment="Top" />
</Grid>
</Window>
Fark etmiş olduğunuz gibi basit bir ekran…Plugin’lerin listeneceği bir “Listbox” ve seçilen “plugin”nin çalışması için bir “Canvas” kontrolü içeriyor temel olarak. Bu uygulama için “plugin”leri yönetecek bir uygulama demiştik en başta hatırlarsanız. Peki nasıl yönetecek?…
Bunun için bu projede PluginFactory diye bir sınıf yaratmamız lazım. Bu sınıf ile “plugin”lerimizi yöneteceğiz. Sonraki yazılarda da bu sınıfı geliştirip, güzelleştiriyor olacağım. Aşağıdaki kod parçası şimdilik işimize yarayacaktır. Yorum şeklinde kodları açıklamaya çalıştım.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition.Hosting;
using Common;
using System.Windows.Controls;
namespace WPFMEF
{
public class PluginFactory
{
//Plugin'lerimiz yüklü olduğu dizin
private string pluginPath;
//Plugin'lerimizin yüklü olduğu dizinin
//MEF tarafındaki karşılığı.Adından da
//anlaşıldığı üzere katalog gibi düşünebiliriz.
//Uygulamada Plugin'lerimizin tutulduğu
//yer de diyebiliriz.
private DirectoryCatalog catalog;
//Import edilen Part'ların,yani plugin'lerin
//yaşam sürecini sürdürdüğü kısım olarak tanımlamak
//bu aşamada yeterli olacaktır.
private CompositionContainer container;
//PluginFactory sınıfımızı yaratıyoruz.
//Bunun için dışarıdan, plugin'lerin sistem üzerinde
//nerede olduklarını parametre olarak veriyoruz.
//İçeride ilgili atamaları yapıp,gerekli nesneleri
//yaratıyoruz.
public PluginFactory(string pluginPath)
{
try
{
this.pluginPath = pluginPath;
this.catalog = new DirectoryCatalog(pluginPath);
this.container = new CompositionContainer(catalog);
}
catch (System.IO.DirectoryNotFoundException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
}
//Bu metod ile sisteme yüklenmiş tüm "MEF Part"larını,
//ya da kendi tabirimiz ile "plugin"leri listeliyoruz.
//Önceki yazılarımda bahsetmiş olduğum "metadata" bilgisine
//sahip olan "plugin"leri listeliyoruz.
public List<IPluginMetaData> GetAllPlugins()
{
List<IPluginMetaData> list = new List<IPluginMetaData>();
//Burada kendi interface'imiz ile yarattığımız,MEF Part'larını
//alıyoruz.Belirttiğimiz dizindeki diğer 'Part'lar bu sayede gelmeyecektir.
//Ayrıca bu noktada yine kendi yarattığımız IPluginMetaData'sı ile
//'Part'ları listemize ekliyoruz.
//"PluginForWPF" parametresi 'Plugin'lerimizi işaretlediğimiz
//bir kontrat bilgisi olarak bu metodda kullanılıyor. "Plugin" yaratırken
//tekrardan dönüyor olacağız.
var controllerExport = container.GetExports(typeof(IPlugin),
typeof(IPluginMetaData),
"PluginForWPF");
//Yukarıdaki ifadeyi LINQ şeklinde kullanarak bir List<> tipindeki
//değişkene atmak tabi ki mümkün.
//Ancak biraz daha açık olması adına aşağıdaki gibi bir kod bloğunu
//listemizi oluşturmak adına kullanabiliriz.
foreach (var item in controllerExport)
{
if (item.Metadata is IPluginMetaData)
list.Add((IPluginMetaData)item.Metadata);
}
return list;
}
//Bu metod ile ismini verdiğimiz "Part"ı ya da kendi tabirimiz ile
//"Plugin"ni çekip kullanıyoruz.Bir önceki metoddaki yaklaşımın
//çok benzeri...
public UserControl LoadPlugin(string name)
{
var controllerExport = container.GetExports(typeof(IPlugin),
typeof(IPluginMetaData),
"PluginForWPF");
foreach (var item in controllerExport)
{
IPluginMetaData metaData = item.Metadata as IPluginMetaData;
if (metaData.Name == name)
{
return (UserControl)item.Value;
}
}
return null;
}
}
}
Şimdi ana uygulamamızdan bu PluginFactory sınıfını kullanarak, ana ekranda “plugin”lerimizi yönetebileceğiz. Bunun için de ana uygulamızda aşağıdaki gibi bir kod kullanmamız gerekecek.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WPFMEF
{
public partial class MainWindow : Window
{
private PluginFactory _pluginFactory;
public MainWindow()
{
InitializeComponent();
LoadPlugins();
}
//"Plugin"lerimizi yada MEF'deki adıyla Part'ları
//Ekrandaki ListBox kontrolüne ekliyoruz. Bu sayede
//sistem tarafından hangi "Part"lar yüklenmiş bunları
//görebiliyoruz
private void LoadPlugins()
{
_pluginFactory = new PluginFactory(@"C:\WpFPlugins");
Binding binder = new Binding();
binder.Source = _pluginFactory.GetAllPlugins();
listBox1.SetBinding(ListBox.ItemsSourceProperty, binder);
}
//Yüklenen "Plugin"leri çalıştırmak için bu event'i kullanıyoruz.
//Bu "event" ListBox kontrolünde listelenen "Plugin" isimlerine tıklandığı
//zaman çalışan bir metod.
private void Button_Click(object sender, RoutedEventArgs e)
{
canvas1.Children.Clear();
Button button = sender as Button;
UserControl control = _pluginFactory.LoadPlugin(button.CommandParameter.ToString());
canvas1.Children.Add(control);
}
}
}
Sıra geldi “Plugin”lere…
Öncelikle “Common” projesinde aşağıdaki gibi bir sınıf tanımlamamız lazım. Bu sınıf ile ilgili ayrıntıları önceki MEF yazılarımdan tekrar tazeleyebilirsiniz.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
namespace Common
{
//"Metadata"mızın içeriğni belirliyoruz.
//Ne gibi bilgiler tutabileceğimizi istediğimiz gibi tanımlayabiliriz.
public interface IPluginMetaData
{
string Name { get; }
string Version { get; }
string Author { get; }
}
public interface IPlugin
{ }
//ExportAttribute tipinde, "Attribute" tanımlıyoruz ki
//Yaratacağımız Part'da bu "Attribute"u kullanabilelim
//Burda önemli olan constructor'da base'i çağırıp
//ExportAttribute'da hangi arayüz ile tanımlama yapacağımızı belirtmek.
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginMetadataAttribute : ExportAttribute
{
public PluginMetadataAttribute(string name, string version, string author)
: base(typeof(IPluginMetaData))
{
Name = name;
Version = version;
Author = author;
}
//IPluginMetaData arayüzünde ki özellikleri burada da tanımlıyoruz
//Hepsini tanımlama zorunda değiliz.Tanımlamadıklarımız varsayılan
//değerleri ile gelecektir.
public string Name { get; set; }
public string Version { get; set; }
public string Author { get; set; }
}
}
Plugin’ler için gerekli temel şeyleri yarattıktan sonra, ilk plugin’imizi kodlayabiliriz. Yukarda da belirttiğim gibi, bu ilk plugin bir WPF kullanıcı kontrolü olacak.”FirstWPFPlugin” projesinde ilgili kodları yazıyor olacağız. Çok basit ve anlamsız bir arayüz oluşturalım isterseniz…
<UserControl x:Class="Plugins.FirstWPFPlugin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Content="Tıklayalım Bakalım" Click="button1_Click" Height="23" HorizontalAlignment="Left" Margin="77,107,0,0" Name="button1" VerticalAlignment="Top" Width="135" />
<Label Content="Bu ilk WPF Plugin'inimiz." Height="28" HorizontalAlignment="Left" Margin="77,84,0,0" Name="label1" VerticalAlignment="Top" FontStretch="Expanded" Width="149" />
</Grid>
</UserControl>
Şimdi de aynı anlamsızlıkta kod tarafını yazalım.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Common;
using System.ComponentModel.Composition;
namespace Plugins
{
//Export, özelliğini bu yarattığımız kontrole vererek
//MEF'e bu kontrolün bir PART olarak Import edileceğini
//söylüyoruz. Bu noktada IPlugin şeklinde önceden yarattığımız
//interface'i de parametre olarak vermemiz gerekiyor.
//MEF'de Export ettiğimiz tüm bileşenlerin belli bir kontart ismi
//yada belli bir arayüz tipinden geliyor olması gerekmekte.
//"PluginForWPF" bizim kontrat ismimiz, IPlugin'de arayüzümüz.
[Export("PluginForWPF", typeof(IPlugin))]
//PluginMetadata özelliği ise Export ettiğimiz sınıflara
//ekstra olarak verebileceğimiz metadata bilgisini içeren,
//yine kendi yarattığımız bir özellik sınıfı.
[PluginMetadata("FirstWPFPlugin", "1.0", "Arda")]
//Bu kısma ilerleyen yazılarda değiniyor olacağım
[PartCreationPolicy(CreationPolicy.NonShared)]
//WPF kullanıcı kontrolü olduğundan UserControl sınıfından
//türüyor olması gerekmekte.IPlugin ise bizim kendi arayüzümüz.
public partial class FirstWPFPlugin : UserControl,IPlugin
{
public FirstWPFPlugin()
{
InitializeComponent();
}
//Sanırım fazla açıklamaya gerek yok (:
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello world");
}
}
}
Bu projeyi “Build” edip daha sonra oluşan *.dll’i, ana uygulamamızın “plugin”leri kontrol ettiği dizin altına koymamız gerekmekte. Bu örnekte bu dizin “C:\WpfPlugins” oluyor…(:
Ana uygulamamız bu dizine bakıp, bu dizindeki “plugin”leri yüklüyor olacak. Uygulamayı çalıştırdığımızda aşağıdaki gibi bir ekran görüntüsü elde ediyor olacağız.
Umarım MEF ile ilgili olarak en azından bir şeylerin aklınızda oluşmasında yardımcı olan bir yazı olmuştur. İlerleyen yazılarda bu örnekleri geliştirip çok daha faydalı örnekler yapıyor olacağız. Bundan dolayı her türlü sorunuzu yada fikrinizi beklerim…
Not: Bu yazıdaki örnek kodları ve projeyi buradan indirebilirsiniz.(Visual Studio 2010 projesi)
Not: Yukarıda bahsettiğim “SecondWPFPlugin”den bahsetmedim ama ekteki kodlarda oda mevcut.