ServiceHost Sınıfından Genişletmek

Değerli Okurlarım Merhabalar,

WCF(Windows Communication Foundation) mimarisinde sunucu(Server) tarafındaki çalışma ortamınının(WCF Runtime) hazırlanması ile ilişkili olaraktan ServiceHost sınıfı kullanılmaktadır. Bir başka deyişle ServiceHost sınıfı uygulama sunucusu(Application Server) üzerindeki gerekli hazırlıkların yapılmasını sağlamaktadır. ServisHost sınıfı çoğunlukla, servis nesnesinin IIS(Internet Information Services) veya WAS(Windows Activation Services) üzerinden yayınlanmadığı durumlarda kullanılmaktadır.

Nitekim IIS ve WAS, ServiceHost tiplerinin çalışma zamanında kendileri ele almaktadır. Bununla birlikte geliştirici tarafından türetilen ServiceHost tiplerinin IIS veya WAS ortamlarında ele alınacak şekilde özelleştirmelerinin yapılmasıda mümkündür. System.ServiceModel isim alanı altında yer alan ServiceHost sınıfı ServiceHostBase isimli abstract sınıftan türemektedir. Doğal olarak sunduğu bazı kurallar değiştirilebilir bir başka deyişle ezilerek(override) yenide yorumlanabilir.

Özellikle çok sayıda servisin yayınlandığı vakalarda, uygulama sunucusu üzerindeki çalışma zamanı davranışlarının farklı ve daha kolay konfigure edilmesi istenebilir. İşte bu tip ihtiyaçlarda geliştirici(Developer) tarafından ServiceHost tipinden türetilen(Inherit) sınıflara ait nesne örneklerinin kullanılması tercih edilebilir. Örneğin, birden fazla EndPoint için varsayılan olarak kapalı olan Metadata yayınlamasının(Metada Publishing) kod tarafında açılması veya EndPoint gibi bilgilerin bir veri kaynağı üzerinden(örneğin bir veritabanı-database) okunmasını sağlamak için özel ServiceHost tiplerinin tasarlanması tercih edilebilir. İşte bu bölümde özel ServiceHost sınıflarının nasıl yazılabileceği incelenmektedir. Öncelikli olarak ServiceHost tipinin sınıf diyagram(Class Diagram) daki görüntüsüne bakmakta ve Framework içerisindeki yerini görmekte yarar vardır.

Genel olarak türetme doğrudan ServiceHost sınıfı üzerinden yapılmaktadır. Ezilebilen üyeler(overridable members) arasında en çok kullanılanı ApplyConfiguration metodudur. Bu metod ile Host için yüklenen konfigurasyon bilgilerine erişilmesi ve bazı davranışların değiştirilmesi mümkün olabilmektedir. Söz gelimi yazının başında belirtilen vakalardan ilkinde, EndPoint noktalarının her biri için otomatik olarak Metadata Publishing üretilmesinin istendiği bir durumda ApplyConfiguration metodu içerisinde gerekli eklemelerin yapılması sağlanabilir. Böylece servis uygulamasına kaç tane EndPoint eklenirse eklensin her biri için Metadata Publishing otomatik olarak eklenebilmektedir.

Varsayılan olarak Metadata Publishing seçeneği kapalıdır. Bunun sebebi Metadata bilgisinin istemeden yayınlanmasının önüne geçmektedir. Metadata yayınlamasının açık olması halinde kullanılan EndPoint' ler içerisindeki bağlayıcı tiplerin(Binding Type) çeşitlerine görede farklı protokoller üzerinden servis bilgilerinin çekilmesi sağlanabilmektedir. Bir başka deyişle servisin ne yaptığı, hangi operasyonları sunduğu bilgileri Metadata şeklinde sunulabilir. Bu, istemciler için gerekli olan proxy nesnelerinin üretilmesinde ele alınmaktadır. Söz konusu metadata bilgilerinin elde edilmesi için svcutil aracı komut satırından kullanılabilir. Yada Visual Studio 2008 ortamında Add Service Reference seçeneğinden yararlanılabilir.

Lakin öyle senaryolar vardırki metadata bilgisinin yayınlanması istenmez. Söz gelimi bir servisi kullanan başka bir servis olduğu göz önüne alınsın. Tüketici servis için proxy nesnesi önemlidir ve manuel olarak üretilip dağıtılabilir. Ancak servisin metadata bilgisinin bu iki servis dışındaki istemciler(Clients) tarafından elde edilmesi istenmemektedir. İşte bu gibi olasılıklar nedeni ile Metadata yayınlaması varsayılan olarak kapalıdır.

Yazıda ilk olarak Metadata yayınlamasının otomatik olarak açılmasını sağlayacak bir örnek üzerinde durulacaktır. Lakin bu örnekte ServiceHost sınıfından türeyen bir tip kullanılmaktadır. Ama öncesinde Metadata yayınlamasının birden fazla EndPoint üzerinden yapıldığı bir senaryoda konfigurasyon dosyasın içerisinde nasıl bir ayarlama yapılması gerektiğini incelemekte yarar vardır. Bu amaçla aşağıdaki servis sözleşmesinin(Service Contract) bulunduğu bir WCF servis kütüphanesi(WCF Service Library) geliştirildiği göz önüne alınsın.

Servis sözleşmesi ve uygulayıcı tip içeriği;

using System;
using System.ServiceModel;

namespace ServisKutuphanesi
{
    [ServiceContract(Name="Cebir Servisi",Namespace="http://www.bsenyurt.com/CebirServisi")]
    public interface ICebir
    {
        [OperationContract]
        double DaireAlan(double r);

        [OperationContract]
        double DaireCevre(double r);
    }

    public class Cebir
        : ICebir
    {
        #region ICebir Members

        public double DaireAlan(double r)
        {
            return Math.PI*r * r;
        }

        public double DaireCevre(double r)
        {
            return 2 * Math.PI * r;
        }

        #endregion
    }
}

Senaryoda odaklanılması gereken nokta özel ServiceHost tipi geliştirilmesi olduğundan, servis sözleşmesi ve uygulayıcı tip mümkün olduğunca basit tutulmuştur. Service tarafındaki uygulamada en büyük sorun Cebir servisine ait farklı EndPoint noktaları için Metadata yayınlamasının yapılmak istenmesidir. Bu nedenle uygulama sunucusu görevini üstlenen programdaki konfigurasyon dosyasında, Mex EndPoint tanımlamalarının yapılması gerekir.

Mex EndPoint tanımlamaları ile TCP, HTTP, HTTPS, NetPipe gibi protokoller üzerinden Metadata yayınlaması yapılabilmektedir. Burada en önemli nokta IMetadataExchange arayüzüdür(interface). Bilindiği gibi bir EndPoint içerisinde Address,Binding ve Contract tanımlamaları yapılmaktadır. IMetadataExchange metadata yayınlaması için gerekli sözleşmeyi(bildirmektedir). Bağlayıcı olarak mexHttpBinding, mexHttpsBinding, mexNamedPipeBinding, mexTcpBinding tipleri kullanılmaktadır. Address bilgisinde ise çoğunlukla baseAddress ile tanımlanan bilgiye ilave olaraktan Mex yazılması yeterli olmaktadır. Burada Mex bir takma ad görevini üstlenmektedir.

Host uygulama basit bir Console projesi olarak tasarlanmıştır ve konfigurasyon dosyası içeriği aşağıda görüldüğü gibidir.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="MetadataExchangeBehavior">
                    <serviceMetadata/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <services>
            <service behaviorConfiguration="MetadataExchangeBehavior" name="ServisKutuphanesi.Cebir">
                <endpoint address="" binding="netTcpBinding" name="CebirTcpEndPoint" contract="ServisKutuphanesi.ICebir" />
                <endpoint address="" binding="basicHttpBinding" name="CebirHttpEndPoint" contract="ServisKutuphanesi.ICebir" />
                <endpoint address="" binding="netNamedPipeBinding" name="CebirPipeEndPoint" contract="ServisKutuphanesi.ICebir" />
                <endpoint address="Mex" binding="mexTcpBinding" name="CebirMexTcpEndPoint" contract="IMetadataExchange" />
                <endpoint address="Mex" binding="mexHttpBinding" bindingConfiguration="" name="CebirMexHttpEndPoint" contract="IMetadataExchange" />
                <endpoint address="Mex" binding="mexNamedPipeBinding" bindingConfiguration="" name="CebirMexPipeEndPoint" contract="IMetadataExchange" />
                <host>
                    <baseAddresses>
                        <add baseAddress="net.tcp://localhost:3400/CebirServisi" />
                        <add baseAddress="http://localhost:3401/CebirServisi" />
                        <add baseAddress="net.pipe://localhost/CebirServisi" />
                    </baseAddresses>
                </host>
            </service>
        </services>
    </system.serviceModel>
</configuration>

Dikkat edileceği üzere senaryoya uygun olması açısından birden fazla farklı EndPoint kullanılmakta ve farklı protokollerin her biri için birer Mex EndPoint noktası ilave edilmektedir. Hem EndPoint noktaları hemde Mex EndPoint noktaları için TCP, HTTP ve Named Pipe formatında gereken temel adres(base address) tanımlamaları host elementi içerisinde yapılmaktadır. Buna göre istemciler servis çağrılarında temel adreslerde belirtilen lokasyonlara talepte bulunabilir. Diğer taraftan Mex adres değerlerine sahip EndPoint noktaları üzerinden proxy üretimide sağlanabilir. Servis uygulamasının konfigurasyon bilgilerine göre aslında aşağıdaki şekilde ifade edilen yayınlamalar söz konusudur.

Özetle servis üzerinde Metadata yayınlaması için sunulan her adres bilgisi üzerinden Proxy ve Config dosyası üretimleri gerçekleştirilebilmektedir. Servis uygulamasına ait program kodları ise aşağıdaki gibidir.

using System;
using System.ServiceModel;
using ServisKutuphanesi;

namespace Sunucu
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(Cebir));
            host.Open();
            Console.WriteLine("Sunucu dinlemede");
            Console.ReadLine();
            host.Close();
        }
    }
}

Program kodlarında standart olarak ServiceHost örneği oluşturulmuş ve Cebir nesne örneği parametre olarak verilerek gerekli WCF çalışma ortamı tesis edilmiştir. Sonrasında ise servis nesnesi açılarak(Open metodu ile), istemciden gelen talepleri dinlemek üzere konuşlandırılmıştır. Bu durumda servis uygulaması çalışırken, svcutil aracı kullanılaraktan Mex EndPoint noktaları üzerinden proxy ve config dosyası üretimleri gerçekleştirilebilmektedir. Aşağıdaki ekran görüntüsünde bu durum irdelenmektedir.

Görüldüğü gibi baseAddress elementlerinde belirtilen adreslerin tamamı üzerinden proxy ve config dosyası üretimi yapılabilmektedir. Elbetteki üretilen config dosyalarının her biri NetTcpBinding, BasicHttpBinding ve NetNamedPipeBinding tabanlı EndPoint bildirimlerinin üçünü birden içerecektir.

Artık yazıya konu olan örneğe geçilebilir. Özel olarak yazılan ServiceHost türevli bir sınıf içerisinde, konfigurasyon dosyasından gelen tüm baseAddress bilgileri için otomatik olarak Mex EndPoint eklenmesi sağlanmaya çalışılacaktır. Bunun için aşağıdaki gibi bir sınıf tasarlanması yeterlidir.

Görüldüğü gibi tek yapılması gereken ServiceHost tipinden bir sınıf türetmek ve bu senaryo için ApplyConfiguration metodunu ezmektir. SmartServiceHost sınıfının kod içeriği aşağıdaki gibidir.

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace Sunucu
{
    class SmartServiceHost
        :ServiceHost
    {
        // Yapıcı metoda gelen servis tipi ve adres bilgileri ServiceHost sınıfının eş düşen yapıcı metoduna(Constructor) yönlendirilir
        public SmartServiceHost(Type servisTipi,params Uri[] adresler)
            :base(servisTipi,adresler)
        {
        }

        protected override void ApplyConfiguration()
        {
            base.ApplyConfiguration(); // Konfigurasyon dosyasındaki bilgilerin yüklenmesi için bu metod çağrısı yapılmalıdır

            // Eğer servis için Metadata Publishing açık değilse bunun eklenmesi sağlanır
            ServiceMetadataBehavior servisDavranisi=this.Description.Behaviors.Find<ServiceMetadataBehavior>();
            if (servisDavranisi == null)
            {
                servisDavranisi=new ServiceMetadataBehavior();
                this.Description.Behaviors.Add(servisDavranisi); // MEX erişimi için yeni bir servis davranışı eklenir
            }

            // host elementi içerisindeki tüm baseAddress değerleri dolaşılır
            foreach (Uri temelAdres in this.BaseAddresses)
            {
                string schemaBilgisi = temelAdres.Scheme; // Şema bilgisi alınır
                if (schemaBilgisi == Uri.UriSchemeNetTcp) // Şema Tcp bazlı ise
                {
                    // Tcp için bir Mex EndPoint eklenir
                    // İlk parametre MEX için gerekli servis sözleşmesidir
                    // İkinci parametrede yer alan Create metodu ile gereki Mex EndPoint üretimi sağlanır 
                    this.AddServiceEndpoint("IMetadataExchange", MetadataExchangeBindings.CreateMexTcpBinding(), "Mex");
                }
                else if (schemaBilgisi == Uri.UriSchemeNetPipe) // Şema Pipe bazlı ise
                {
                    // Pipe için Mex EndPoint eklenir
                    this.AddServiceEndpoint("IMetadataExchange", MetadataExchangeBindings.CreateMexNamedPipeBinding(), "Mex");
                }
                else if (schemaBilgisi == Uri.UriSchemeHttp) // Şema Http bazlı ise
                {
                    servisDavranisi.HttpGetEnabled = true; // Http üzerinden yayınlamanın yapılacağı belirtilir
                    // Http için Mex EndPoint eklenir
                    this.AddServiceEndpoint("IMetadataExchange", MetadataExchangeBindings.CreateMexHttpBinding(), "Mex");
                }
                else if (schemaBilgisi == Uri.UriSchemeHttps) // Şema Https bazlı ise
                {
                    servisDavranisi.HttpsGetEnabled = true; // Https üzerinden yayınlamanın yapılacağı belirtilir
                    // Https için Mex EndPoint eklenir
                    this.AddServiceEndpoint("IMetadataExchange", MetadataExchangeBindings.CreateMexHttpsBinding(), "Mex");
                }
            }
        }
    }
}

ServiceHost sınıfına ait bir nesne örneklenirken servis nesnesinin tipi ve kullanılacak adres bilgileri parametre olarak verilmektedir. Bu nedenle SmartHostService sınıfının ilgili yapıcı metodundan(Constructor), base anahtar kelimesi ile ServiceHost sınıfında eş düşen yapıcı metoda parametre aktarımı gerçekleştirilmektedir. Diğer taraftan senaryoda istenen, var olan EndPoint noktaları için Mex tanımlamalarının yapılmasıdır. Bu nedenle konfigurasyon dosyasının içeriğine ulaşılması ve servis tanımlamalarında(Service Description) gerekli ilavelerin yapılması gerekmektedir. Söz konusu işlemler için ApplyConfiguration metodu ezilmiştir.

ApplyConfiguration metodu dışında ezilebilecek olan diğer üyelerde aşağıdaki şekilde görüldüğü gibidir.

Dikkat edileceği üzere servisi kapatma, açma veya hata oluşması anındaki olay metodların ezilmesi dahi mümkündür.

ApplyConfiguration metodu içerisinde ilk olarak base.ApplyConfiguration() fonksiyonuna çağrı yapılarak konfigurasyon dosyasındaki ayarların ortama yüklenmesi sağlanmaktadır. Sonrasında ise servis davranışında Metadata yayınlaması için gerekli elementin var olup olmadığına bakılır. Eğer yoksa eklenmesi sağlanır. İlerleyen adımlardaysa konfigurasyon dosyasındaki tüm temel adresler tek tek dolaşılır. Her temel adresin şema(Scheme) tipine bakılarak uygun bir Mex EndPoint yüklemesi yapılmaktadır.

Burada şema bilgisi elde edildikten sonra Uri sınıfının UriSchemeHttps, UriSchemeHttp, UriSchemeNetPipe, UriSchemeNetTcp sabit değerleri ile kıyaslama yapılmakta ve duruma göre uygun EndPoint üretimleri gerçekleştirilmektedir. Tüm Mex EndPoint noktaları IMetadataExchange arayüzünü, sözleşme(Contract) tipi olarak kullanmaktadır. Bununla birlikte uygun bağlayıcı tiplerin(Binding Type) üretimi için MetadataExchangeBindings sınıfının CreateMexHttpsBinding, CreateMexHttpBinding, CreateMexNamedPipeBinding ve CreateMexTcpBinding gibi metodları kullanılmaktadır. Artık servis uygulamasında ServiceHost yerine SmartServiceHost sınıfı aşağıdaki kod parçasında olduğu gibi kullanılabilir.

using System;
using System.ServiceModel;
using ServisKutuphanesi;

namespace Sunucu
{
    class Program
    {
        static void Main(string[] args)
        {
            // ServiceHost host = new ServiceHost(typeof(Cebir));
            SmartServiceHost host = new SmartServiceHost(typeof(Cebir));
            host.Open();
            Console.WriteLine("Sunucu dinlemede");
            Console.ReadLine();
            host.Close();
        }
    }
}

Artık konfigurasyon dosyasında yapılan Mex EndPoint noktalarına ait bildirimler kaldırılabilir. Servis uygulaması çalıştırıldığında debug moddayken, SmartServiceHost sınıfı içerisinde ezilmiş olan ApplyConfiguration metodunun sonunda aşağıdaki ekran görüntüsüne ulaşılabilir.

Dikkat edileceği üzere altı adet EndPoint tanımlaması yer almaktadır. Bunlardan üçüde Mex EndPoint noktalarıdır. Dolayısıyla istemciler kendileri için gerekli Proxy ve config dosyası üretimlerini yine gerçekleştirebilirler.

Söz konusu örnekte SmartServiceHost nesnesi bir Console uygulaması üzerinde ele alınmaktadır. Oysaki bu tipin IIS veya WAS tabanlı bir host uygulama tarafındanda ele alınması istenebilir. Burada devreye bir fabrika nesnesi(Factory Object) girmektedir. Nitekim IIS üzerinden yayınlanan svc uzantılı dosyalara ait ServiceHost direktifi içerisinde bulunan Factory niteliği(attribute) ile, kullanılacak olan ServiceHost tipi belirtilebilmektedir. Söz konusu senaryoda özel bir ServiceHost tipi olduğundan bunun üretiminden sorumlu bir Factory sınıfınında geliştirilmesi gereklidir.

Yazının bu bölümünde geliştirilen ServiceHost türevli SmartServiceHost sınıfının IIS üzerinden host edilen bir WCF Service üzerinden nasıl kullanılacağı ele alınmaktadır. Bu amaçla öncellikle IIS üzerinde bir WCF servis açılması gerekmektedir. Söz konusu proje servis kütüphanesinide referans etmelidir. Sonrasında ise uygulamaya aşağıdaki sınıfın eklenmesi yeterlidir.

using System;
using System.ServiceModel.Activation;

public class SmartServiceHostFactory
    :ServiceHostFactory
{
    protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        return new SmartServiceHost(serviceType, baseAddresses);
    }
    public SmartServiceHostFactory()
    {
    }
}

SmartServiceHostFactory sınıfı içerisinde CreateServiceHost isimli metod ezilmektedir(overriding). Dikkat edilecek olursa metod geriye SmartServiceHost tipinden bir nesne örneği döndürmektedir. Bir başka deyişle WCF çalışma ortamını tesis edecek nesne örneği üretilmektedir. CreateServiceHost metoduna gelen parametrelerden ilki, svc dosyasında yer alan Service niteliğinin değerini taşımaktadır. Elbette svc dosyası içerisinde hangi Factory nesnesinin kullanılacağının bildirilmeside gereklidir. Bu nedenle svc dosyasının içeriği aşağıdaki gibi düzenlenmelidir.

<%@ ServiceHost Language="C#" Debug="true" Service="ServisKutuphanesi.Cebir" Factory="SmartServiceHostFactory" %>

Dikkat edilmesi gereken önemli bir nokta vardır. IIS üzerinden yayınlama yapıldığı ve bu senaryoda WAS kullanılmadığı için web.config dosyası içerisinde net.pipe ve net.tcp formatındaki adreslere izin verilmeyecektir. Dolayısıyla şu aşamada test olması açısından sadece HTTP bazlı bir EndPoint noktasına yer verilmektedir. Web.config dosyası için örnek içerik aşağıdaki gibidir.

<?xml version="1.0"?>
<configuration>
    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
            </serviceBehaviors>
        </behaviors>
        <services>
            <service behaviorConfiguration="" name="ServisKutuphanesi.Cebir">
                <endpoint address="" binding="basicHttpBinding" name="CebirHttpEndPoint" contract="ServisKutuphanesi.ICebir" />
                <host>
                    <baseAddresses>
                        <add baseAddress="http://localhost:3401/CebirServisi" />
                    </baseAddresses>
                </host>
            </service>
        </services>
    </system.serviceModel>
</configuration>

Bu işlemlerin ardından Service.svc dosyası herhangibir tarayıcı uygulamadan talep edilirse otomatik olarak HTTP Metadata Publishing' in açıldığı görülcektir. Tabiki SvcUtil aracı ilede metadata bilgileri çekilebilir.

Yazıya ikinci bir örnek ile devam edelim. Bu örnekte özel olarak yazılmış olan ServiceHost sınıfı, WCF çalışma ortamı için gerekli EndPoint bilgilerini bir veritabanı tablosundan almaktadır. Böyle bir senaryo çoğunlukla servis tarafındaki bildirimlerin konfigurasyon dosyası dışarısında daha güvenli bir yerden tedarik edilmesi istendiği durumlarda göz önüne alınabilir. Senaryonun çok karmaşık olmaması amacıyla basit bir tablo üzerinde EndPoint oluşumları için gerekli bir kaç alan bilgisi tutulmaktadır. İlk olarak bu tablonun tasarımı ile işe başlanabilir. Tablonun oluşturulması için gerekli sorgu cümlesi aşağıdaki şekilde tasarlanabilir.

USE [ServiceBase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE 
    [dbo].[EndPoints]
    (
        [EndPointId] [int] IDENTITY(1,1) NOT NULL,
        [Name] [varchar](20) COLLATE Turkish_CI_AS NOT NULL,
        [ServiceAlias] [nvarchar](20) COLLATE Turkish_CI_AS NOT NULL,
        [Protocol] [nvarchar](10) COLLATE Turkish_CI_AS NOT NULL CONSTRAINT [DF_EndPoints_Protocol] DEFAULT (N'net.tcp'),
        [ServerName] [nvarchar](50) COLLATE Turkish_CI_AS NOT NULL CONSTRAINT [DF_EndPoints_ServerName] DEFAULT (N'localhost'),
        [PortNumber] [int] NULL,
        [Binding] [nvarchar](20) COLLATE Turkish_CI_AS NOT NULL,
        [Contract] [nvarchar](50) COLLATE Turkish_CI_AS NOT NULL,
        CONSTRAINT [PK_EndPoints] PRIMARY KEY CLUSTERED 
        (
                [EndPointId] ASC
            )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
        ) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[EndPoints] WITH CHECK ADD CONSTRAINT [CK_Bindings] CHECK (([Binding]='WS2007FederationHttpBinding' OR [Binding]='WS2007HttpBinding' OR [Binding]='WSFederationHttpBinding' OR [Binding]='WebHttpBinding' OR [Binding]='WSDualHttpBinding' OR [Binding]='WSHttpBinding' OR [Binding]='BasicHttpBinding' OR [Binding]='MsmqIntegrationBinding' OR [Binding]='NetPeerTcpBinding' OR [Binding]='NetMsmqBinding' OR [Binding]='NetNamedPipeBinding' OR [Binding]='NetTcpBinding'))
GO
ALTER TABLE [dbo].[EndPoints] CHECK CONSTRAINT [CK_Bindings]
GO
ALTER TABLE [dbo].[EndPoints] WITH CHECK ADD CONSTRAINT [CK_PortNumber] CHECK (([PortNumber]>=(0) AND [PortNumber]<=(65535)))
GO
ALTER TABLE [dbo].[EndPoints] CHECK CONSTRAINT [CK_PortNumber]
GO
ALTER TABLE [dbo].[EndPoints] WITH CHECK ADD CONSTRAINT [CK_Protocol] CHECK (([Protocol]='net.p2p' OR [Protocol]='net.pipe' OR [Protocol]='https' OR [Protocol]='http' OR [Protocol]='net.msmq' OR [Protocol]='net.tcp'))
GO
ALTER TABLE [dbo].[EndPoints] CHECK CONSTRAINT [CK_Protocol]

Bu sorgu ifadesinde hataların önüne mümkün olduğunca geçebilmek için Port numarası, bağlayıcı tip adı ve protokol bilgilerinin girişi kısıtlamalar(Constraints) ile kontrol altına alınmaktadır. Bu şekilde Binding, PortNumber ve Protocol alanlarına girilecek olan değerler sınırlandırılmaktadır. Elbette bu tabloya EndPoint bilgilerinin girilmesi için bir uygulama arayüzü kullanılacaksa, giriş kontrollerinin veritabanı yerine program tarafında yapılmasıda söz konusudur. Nitekim bu sayede WCF alt yapısından tiplerin(Örneğin bağlayıcı sınıflar, enum sabitleri vb...) çalışma zamanında validasyon süreçleri içerisine dahil edilmesi garanti edilmiş olacak ve tutarlı veri girişi sağlanabilecektir. Tabloda örnek olarak aşağıdaki verilerin saklandığı göz önüne alınabilir.

Bu tabloda ye alan Protocol, ServerName ve PortNumber bilgileri yardımıyla EndPoint için gerekli adres(Address) oluşturulabilir. Adres bilgileri baseAddress olacak şekilde ele alınabilir. Yine EndPoint için gerekli sözleşme(Contract) bilgisi Contract isimli alandan tedarik edilebilir. Son olarak bağlayıcı(Binding) bilgisi için Binding alanındaki metinsel bilgiden yararlanılmaktadır. Tabiki çalışma zamanında ilgili bağlayıcı tipe ait nesne örneği gerektiğinden bu metinsel bilginin karşılığı olan bağlayıcı tip için ek bir kodlama yapılması gerekebilir. EndPoint bilgilerinin saklanacağı bu tabloyu kullanacak olan türetilmiş ServiceHost sınıfının tasarımı ise aşağıdaki şekilde yapılabilir.

using System;
using System.ServiceModel;
using System.Data.SqlClient;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.MsmqIntegration;

namespace Sunucu
{
    class TableBasedServiceHost
        :ServiceHost
    {

        public TableBasedServiceHost(Type servisTipi, params Uri[] adresler)
            : base(servisTipi, adresler)
        {
        }

        protected override void ApplyConfiguration()
        {
            // Service için Metadata davranışı olup olmadığına bakılır. Yoksa bir tane oluşturulur.
            ServiceMetadataBehavior servisDavranisi = this.Description.Behaviors.Find<ServiceMetadataBehavior>();
            if (servisDavranisi == null)
            {
                servisDavranisi = new ServiceMetadataBehavior();
                this.Description.Behaviors.Add(servisDavranisi); // MEX erişimi için yeni bir servis davranışı eklenir
            }

            // ServiceBase veritabanındaki EndPoints tablosundan satırlar okunur.
            using (SqlConnection _conn = new SqlConnection("data source=.;database=ServiceBase;integrated security=SSPI"))
            {
                SqlCommand _cmd = new SqlCommand("Select EndPointId,Name,ServiceAlias,Protocol,ServerName,PortNumber,Binding,Contract From EndPoints", _conn);
                _conn.Open();
                SqlDataReader reader = _cmd.ExecuteReader();
                string adres = null;
                while (reader.Read())
                {
                    // Servis için gerekli baseAddress bilgileri oluşturulurken PortNumber alanının değerinin null olup olmadığına bakılır
                    // Bu alan null ise sunucu adından sonra port numarası bilgisi verilmez.
                    if (!String.IsNullOrEmpty(reader["PortNumber"].ToString()))
                        adres = String.Format("{0}://{1}:{2}/{3}", reader["Protocol"].ToString(), reader["ServerName"].ToString(), reader["PortNumber"].ToString(), reader["ServiceAlias"].ToString());
                    else
                        adres = String.Format("{0}://{1}/{2}", reader["Protocol"].ToString(), reader["ServerName"].ToString(), reader["ServiceAlias"].ToString());
                    // Elde edilen adres verisinden bir Uri nesne örneği oluşturulur
                    Uri baseAddress = new Uri(adres);
                    // Oluşturulan baseAddress bilgisi servise eklenir.
                    this.AddBaseAddress(baseAddress);
                    // EndPoint oluşturulur ve eklenir. 
                    // EndPoint noktasının oluşturulması sırasında bağlayıcı tip(Binding Type) için BaglayiciUret isimli yardımcı metod kullanılır.
                    this.AddServiceEndpoint(reader["Contract"].ToString(), BaglayiciUret(reader["Binding"].ToString()), "");
        
                    #region MEX EndPoint Ekleme İşlemleri
                    
                    // Adresteki scheme bilgisine göre gerekli Mex EndPoint noktaları oluşturulur.
                    string schemaBilgisi = baseAddress.Scheme;
                    if (schemaBilgisi == Uri.UriSchemeNetTcp) // TCP için
                    { 
                        this.AddServiceEndpoint("IMetadataExchange",  MetadataExchangeBindings.CreateMexTcpBinding(), "Mex");
                    }
                    else if (schemaBilgisi == Uri.UriSchemeNetPipe) // PIPE için
                    {
                        this.AddServiceEndpoint("IMetadataExchange",  MetadataExchangeBindings.CreateMexNamedPipeBinding(), "Mex");
                    }
                    else if (schemaBilgisi == Uri.UriSchemeHttp) // HTTP için
                    {
                        servisDavranisi.HttpGetEnabled = true;
                        this.AddServiceEndpoint("IMetadataExchange",  MetadataExchangeBindings.CreateMexHttpBinding(), "Mex");
                    }
                    else if (schemaBilgisi == Uri.UriSchemeHttps) // HTTPS için
                    {
                        servisDavranisi.HttpsGetEnabled = true;
                        this.AddServiceEndpoint("IMetadataExchange", MetadataExchangeBindings.CreateMexHttpsBinding(), "Mex");
                    }
                    
                    #endregion
                }
                reader.Close();
            }
        }

        // EndPoints tablosunda bağlayıcı adları string olarak tutulduğundan Binding tipinden nesne üretimi gerçekleştiren bir metoddan yararlanılmaktadır.
        private Binding BaglayiciUret(string baglayiciAdi)
        {
            Binding baglayici = null;
            switch (baglayiciAdi)
            {
                case "NetTcpBinding":
                    baglayici= new NetTcpBinding();
                    break;
                case "NetNamedPipeBinding":
                    baglayici = new NetNamedPipeBinding();
                    break;
                case "WS2007FederationHttpBinding":
                    baglayici = new WS2007FederationHttpBinding();
                    break;
                case "WS2007HttpBinding":
                    baglayici = new WS2007HttpBinding();
                    break;
                case "WebHttpBinding":
                    baglayici = new WebHttpBinding(); // System.ServiceModel.Web referansının ekli olması gerekir
                    break;
                case "WSDualHttpBinding":
                    baglayici = new WSDualHttpBinding();
                    break;
                case "WSHttpBinding":
                    baglayici = new WSHttpBinding();
                    break;
                case "BasicHttpBinding":
                    baglayici = new BasicHttpBinding();
                    break;
                case "MsmqIntegrationBinding":
                    baglayici = new MsmqIntegrationBinding();
                    break;
                case "NetPeerTcpBinding":
                    baglayici = new NetPeerTcpBinding();
                    break;
                case "NetMsmqBinding":
                    baglayici = new NetMsmqBinding();
                    break;
                default:
                    baglayici = new NetTcpBinding();
                    break;
            }
            return baglayici;
        }
    }
}

TableBasedServiceHost nesnesi içerisinde yer alan ApplyConfiguration metodu içerisinde, EndPoints tablosundaki satırlardan yararlanılarak base address, EndPoint ve Mex EndPoint nesnelerinin oluşturulması sağlanmaktadır. Elbette bu yeni ServiceHost türevli sınıfa ait nesne örneğinin kullanılması için bir uygulama sunucusuna ihtiyaç vardır. Bu amaçla Console uygulamasının kodlarını aşağıdaki gibi değiştirmek yeterlidir.

using System;
using System.ServiceModel;
using ServisKutuphanesi;

namespace Sunucu
{
    class Program
    {
        static void Main(string[] args)
        {
            TableBasedServiceHost host = new TableBasedServiceHost(typeof(Cebir));
            host.Open();
            Console.WriteLine("Sunucu dinlemede");
            Console.ReadLine();
            host.Close();
        }
    }
}

Programın başarılı bir şekilde çalıştığı görülür. Debug mod içerisindekyen TableBasedServiceHost sınıfınına ait ApplyConfiguration metodu işleyişini tamamlandığında servise ait içerik aşağıdaki Quick Watch görüntüsünde olduğu gibidir. Dikkat edilecek olursa tabloda örnek olarak girilen tüm base adres değerleri BaseAddress özelliğinin işaret ettiği koleksiyona yüklenmiştir. Bununla birlikte altı adet EndPoint tanımlaması EndPoints özelliği ile işaret edilen koleksiyona eklenmiştir. Bunlardan üçü bir önceki örnektine benzer olacak şekilde Mex EndPoint bilgileridir. Servis tipi zaten yapıcı metod ile bildirilmektedir. Son olarak sözleşme bilgisininde ImplementedContracts özelliğine set edildiği görülmektedir.

Tabiki buradaki senaryo bir Best Practices olarak algılanmamalıdır. Tablo tasarımı daha farklı bir şekilde düzenlenebilir. Bununla birlikte bağlantı, tablo adı ve alanlarının değişme ihtimali göz önünde bulundurularak bunların ServiceHost türevli sınıf içerisine aktarımı için gerekli önlemlerin alınması gereklidir. Elbette daha ileri bir senaryoda düşünülebilir. Söz gelimi, servisin kullanacağı EndPoint tanımlamaları tablodaki alanlarda serileştirilmiş(Serialize) şekilde tutulabilir. Hatta birden fazla servis nesnesi(Service Instance) için birden fazla EndPoint tanımlamasının yapılabileceği ilişkisel(Relational) bir veritabanı tasarımıda söz konusu olabilir.

Söz konusu servislerin kullandığı EndPoint bilgilerinin tablolardaki ilgili alanlara eklenmesi içinde yetkilendirilmiş(Authorized) bir ekran(örneğin bir Windows uygulaması) tasarlanabilir. Böylece EndPoint girişleri yetki dahilinde yapılır. Diğer taraftan EndPoint bilgileri XML gibi açık bir dosyada durmadıklarından görece daha güvenli bir ortamda saklanırlar. Üstelik bu veritabanının servis uygulamasının host edildiği sunucunun arkasında bir sunucuda yer aldığıda göz önüne alınabilir. Bu vizyon çok daha fazla genişletilebilir.

Böylece geldik bir makalemizin daha sonuna. Bu makalemizde ServiceHost türevli bir sınıfın nasıl geliştirilip kullanılabileceği iki örnek senaryo üzerinden incelemeye çalıştık. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Yorum ekle

Loading