Genişletilebilir uygulamaların asıl gücü tak-çalıştır desteğidir. Bir önce ki yazıda ele aldığımız katalog ve taşıyıcı kavramlarının çalışma anında tak-çalıştır modüllerini nasıl takip ettiğini ve kullandığını inceliyoruz.
Çalışma Zamanı Desteği
Çalışma anında uygulamazın tak-çalıştır modülleri nasıl yönettiğini görelim. Öncelikle katalog ve taşıyıcıyı oluşturalım.
[cs]
var assemblyCatalog = new AssemblyCatalog (Assembly .GetExecutingAssembly ());
var directoryCatalog = new DirectoryCatalog (Path .Combine ("../.." , "Plugins" ));
var aggregateCatalog = new AggregateCatalog (assemblyCatalog, directoryCatalog);
_compositionContainer = new CompositionContainer (aggregateCatalog);
DirectoryCatalog örneğinin Plugins dizinini dinlediğini görüyorsunuz. DirectoryCatalog siz yenilemedikçe dizin içinde ki modülleri tekrar yüklemeyecektir. Plugins dizinini dinlemeli ve oluşan değişikliklerde DirectoryCatalog örneğinizi yenilemelisiniz.
[cs]
// Plugins dizinini takip et
var watcher = new FileSystemWatcher (directoryCatalog.FullPath )
{
Filter = "*.dll" ,
EnableRaisingEvents = true
};
// dizindeğişince katalogu yenile
watcher.Changed += (s, e) => directoryCatalog.Refresh ();
Yukarıda ki kodlama ile Plugins dizininde ki dosyalar değiştiğinde katalogumuzu yeniliyoruz. Katalog ilgili dizinde ki tüm assembly dosyalarına bakarak [Export] tanımlarını almaktadır. Basit bir uygulama ile çalışma anında katalogun nasıl değiştiğini görelim. Elimizde karakter dizisi dönen bir ISeriesFactory interface var.
[cs]
public interface ISeriesFactory
{
///
/// seri oluştur
///
/// dizide olması gereken eleman sayısı /// oluşturdupu seriyi dönderir List Create (int numberOfElement); }
Bu interface ait uygulama yazmak istiyoruz. Fakat interface uygulama sınıfları çalışma anında eklenecekler. Aşağıdakine benzer bir önyüz hazırlıyoruz. Sol tarafta katalogda yer alan ihraç [Export] tanımları yer alacak. Sağ tarafta ise katalogda yer alan ISeries interface’sini uygulayan sınıflar listelenmiş. Seçilen sınıfı çağıracak olan birde buton eklenmiş durumdadır.

Uygulamayı çalıştırıp uygulamanın dinlediği Plugins dizinine ISeries interface’sini uygulayan assembly dosyalarını kopyalıyoruz. Katalog listesinin doğrudan güncellendiğini görürüz. Bir tane ISeries uygulaması seçip serinin eleman sayısını girelim ve çalıştır diyelim.

ContractName ve MetaData
Uygulama katalogu içinde birden çok ISeriesFactory uygulaması var. Bu durumda elimizde ki parçalar arasında sorgular çalıştırarak istediğimiz parçayı bulmamız gerekmektedir. [ExportMetadata] niteliği ile katalog üzerinde arama yapabilmek için[ Export] tanımlarımıza etiket değerleri verebilmekteyiz. Parçalara ContractName değeri ekleyerek doğrudan istediğimiz parçayı içe aktarabilir veya kendimiz özel sorgular yazarak aradığınız parçayı taşıyıcı üzerinde bulabiliriz.
[cs]
[Export ("Fibanacci" ,typeof (ISeriesFactory ))]
public class Fibanacci : ISeriesFactory
private LazyGetFibanacciSeries(CompositionContainer compositionContainer)
{
Lazy fibanacci =
compositionContainer.GetExport ("Fibanacci" );
return fibanacci;
}
Veya taşıyıcının sizin yerinize ContractName metadata değeri ile [Import] işlemi yapmasını sağlayabilirsiniz.
[cs]
[Import ("Fibanacci" )]private ISeriesFactory _fibanacci ;
Yukarıda ki tanımlama katalog üzerinde aşağıdaki gibi saklanmaktadır.

Görüldüğü üzere yukarıda ki [Export] tanımına ait bir Meradata dizisi bulunmaktadır. [Export] tanımın ilk parametresinde verdiğimiz isim ContractName olarak saklanmaktadır. Metadata dizisinde ExportTypeIdentity etiketinin değeri ise [Export] yapılan tipin tam adıdır. Eğer bir [Export] kontratı için bir isim belirtmez iseniz ExportTypeIdentity değeri ContractName alanına atanacaktır. ContractName ve ExportTypeIdentity metadata değeri ile katalog üzerinde sorgulama yapabilirsiniz.
Buraya kadar her şey güzel ama matematik ve karakter serilerinin aynı listede görülmesini istemiyorsunuz. O zaman kendinize ait [ExportMetadata] etiketleri tanımlama zamanı gelmiştir.
[cs]
[Export ("Fibanacci" ,typeof (ISeriesFactory ))]
[ExportMetadata ("Series" ,"Math" )]
public class Fibanacci : ISeriesFactory
[Export (typeof (ISeriesFactory ))]
[ExportMetadata ("Series" , "String" )]
public class UpperLetters : ISeriesFactory
Yukarda Prime serisinin bir matematik seri olduğunu benzer şekilde UpperLatter serinin bir karakter serisi olduğunu belirtiyoruz. Şimdi formumuza yeni bir combobox ekleyerek kullanıcıya seri türlerini gösterelim. Her seçilen seri türünde de seri uygulamaları listemizi güncelleyelim.

[cs]
private void cbTypes_SelectedIndexChanged (object sender, EventArgs e)
{
var selectedType = cbTypes .SelectedItem .ToString ();
var seriesList = new StringList ();
_compositionContainer
.Catalog .Parts
.SelectMany (part => part.ExportDefinitions )
.Where (c=>c.Metadata .ContainsKey ("Series" )
&& c.Metadata ["Series" ].ToString () == selectedType)
.ToList ()
.ForEach (c=>seriesList.Add (c.ContractName ));
sourceSeries .DataSource = seriesList;
}
Yukarıda görüldüğü üzere seçilen seri türüne göre seri uygulama sınıflarını listeletebiliyoruz. Fakat bu yöntemde benim eklenti sınıflarında verilen etiket değerlerini bilmem gerekiyor. Metadata nın adı “Series” olmalı değeri “Math” veya “String” olmalı şeklinde kurallarım var. Eklenti kodlanırken bu kurallarımı zorlayan bir şey yok. Benim ihtiyacım ISeriesFactory interface ile birlikte sınıflara Metadata bilgisini de dağıtmalı ve böylece beklenmedik metadata değerleri ile karşılaşmamalıyım. O halde yeni bir Metadata sınıfı yazalım ve bu Metadata ya sahip ISeriesFactory sınıfları ile birlikte çalışalım.
[cs]
public enum SeriesType {Math, String}
public interface ISeriesFactoryMetadata
{
SeriesType SeriesType { get ; }
string Name { get ; }
}
[MetadataAttribute ]
[AttributeUsage (AttributeTargets.Class,AllowMultiple= false )]
public class SeriesFactoryMetadataAttribute
: ExportAttribute , ISeriesFactoryMetadata
{
public SeriesFactoryMetadataAttribute ()
: base (typeof (ISeriesFactory )) { }
public SeriesType SeriesType { get ; set ; }
public string Name { get ; set ; }
}
Yukarıda [Export] kontratını türettiğimizi ve sınıfmıza [Metadata] özelliği eklediğimizi görüyorsunuz. Böylelikle [Export] kontratı tanımlarken [Export] ve metadatayı ayrı ayrı tanımlamak zorunda değiliz. Ayrıca Metadata string olarak girilen Metadata etiketlerimizin yerini ISeriesFactoryMetadata interface property'leri aldı. Şimdide uygulamamızda ISeriesFactoryMetadata'yı kullanalım.
[cs]
//[Export("Fibanacci",typeof (ISeriesFactory))]
//[ExportMetadata("Series", "Math")]
[SeriesFactoryMetadata (Name ="Fibannacci Series" , SeriesType =SeriesType.Math)]
public class Fibanacci : ISeriesFactory
Görüldüğü üzere kod çok daha anlaşılır oldu. Uygulamayıda ISeriesFactoryMetadata'yı kullanacak şekilde değiştirelim.
[cs]
private void btnCreate_Click (object sender, EventArgs e)
{
var selectedType = (SeriesType ) cbTypes .SelectedItem ;
var selectedSeries = cbSeries .SelectedItem .ToString ();
var series = _series .First (c =>
c.Metadata .SeriesType == selectedType
&& c.Metadata .Name == selectedSeries).Value ;
var list = new StringList ();
list.AddRange (series.Create (int .Parse (txtNumber .Text )));
sourceResult .DataSource = list;
}
[ImportMany ] private Lazy [] _series ;
private void cbTypes_SelectedIndexChanged (object sender, EventArgs e)
{
var selectedType = (SeriesType ) cbTypes .SelectedItem ;
var seriesList = new StringList ();
_series
.Where (c=>c.Metadata .SeriesType == selectedType)
.ToList ()
.ForEach (c => seriesList.Add (c.Metadata .Name ));
sourceSeries .DataSource = seriesList;
}
Uygulama geliştirici olarak eklentilernden geleçek olan Metadata bilgilerimi güçlü bir tipe çekmiş oldum. Eklenti geliştiricilerede uygulama içinde gerekli olan Metadata bilgilerine doğrudan ulaşmış oldular.
Neden SeriesFactoryMetadataAttribute sınıfnıda bir interface uygulamak zorundadır ? Compositon geri tarafta metadata sınıfı için hala bir anahtar-değer sözlüğü üretmektedir. Doğrudan SeriesFactoryMetadataAttribute sınıfnı [Import] ile almak için SeriesFactoryMetadataAttribute sınıfının üretilen sözlüğü constractor parametresi olarak alması gerekmektedir. Daha kolay yöntem ise SeriesFactoryMetadataAttribute sınıfın bir interface uygulaması ve interface üzerinde ki property değerlerinin composition tarafından okunmasıdır.
Parçaların Hayat Döngüsünü Belirlemek
Ben asal sayıları hesaplayan dizinin her çağrılmada tekrar hesaplama yapmasını istemiyorum. Çünkü bu hesapla uzun süremektedir. Bu sınıfın uygulamada sadece bir defa oluşturulmasını istiyorum. Prime sınıfı ürettiği asay sayıları saklasın ve mümkün oldupunca az hesapla yapsın istiyorum. Taşıyıcıya bu sınıfı her çağrıda oluşturma sadece bir defa oluştur ve kullan demem gerekiyor.
[cs]
[SeriesFactoryMetadata (Name = "Prime Series" , SeriesType = SeriesType.Math)]
[PartCreationPolicy (CreationPolicy.Shared)]
public class Prime : ISeriesFactory
Böylece Prime sınıfını paylaşımlı/Shared işaretleyerek kullandığım taşıyıcı üzerinde aynı anda sadece bir tane örneği bulunacağını belirtiyoruz. Eğere bir nesnenin her erişimde yeniden oluşturulmasını istiyor iseniz NonShared olarak işaretlemeniz gerekmektedir. Fibanacci serisinde nasıl kullanılması gerektiğini [Import] anında karar verilmesini istiyorum. Yani Fibanacci serisinin [Import] eden parça isterse taşıyıcı üzerinde mevcut olan örneği isterse yeni bir örneği kullanabilir.
[cs]
[SeriesFactoryMetadata (Name = "Fibannacci Series" , SeriesType=SeriesType.Math)]
[PartCreationPolicy (CreationPolicy.Any)]
public class Fibanacci : ISeriesFactory
[Import("Fibanacci", RequiredCreationPolicy = CreationPolicy.NonShared )]
private ISeriesFactory _fibanacci ;
Composition aksi belirtilmedikce [Export] kontratı olarak Any, [Import] kontratı olrakta Shared kullanmaktadır. Yani aksi belirtilmedikce taşıyıcı üzerinde aynı [Export] kontratı için bir tane parça var olacaktır.
Katalogu Çalışma Zamanında Değiştirme
Fibanacci sınıfın yeni versiyonunu yazdık. Şimdi uygulamayı dinamik olarak güncellememiz gerekiyor. Taşıyıcıya [Import] ettiğiniz parçalarda bir değişiklik olursa tekrar [Import] işlemini yapmasını söyleyebiliyoruz.
[cs]
[ImportMany (AllowRecomposition = true )]
private Lazy [] _series ;
Ayrıca her hangi bir [Import] parçası taşıyan bir sınıfta IPartImportsSatisifiedNotification interface'i uyguladı ise tüm [Import] işlemleri bittikten sonra ImportCompleted olayı taşıyıcı tarafından çağırılmaktadır.
Sonuç
Böylelikle [Export] kontratlarımıza Metadata özelliklerini eklemeyi ve eklediğimiz Metadata özelliklerini bir sınıf yapısına getirmeyi inceledik. Taşıyıcının Metadata ve ContractName ile nasıl çalıştığını ve taşıyıcı üzerinde sorgulama yapmayı gördük. Daha sonra taşıyıcı üzerinde ki parçaların hayat döngüsünü yönetmeyi inceledik. Son olarakta çalışma anında değişen katalogdan haberdar olmayı gördük. Bu iki yazı ile her hangi bir .NET platformunda genişletilebilir uygulama yazacak duruma geldik. Bir sonra ki makalede Genişletilebilir Uygulamalarda Hata Ayıklama ve Takip etme özelliklerini inceliyor olacağız.