Wcf hizmetimde kurucuya nasıl değer iletirim?


104

Hizmetimi uygulayan sınıftaki kurucuya değerler iletmek istiyorum.

Ancak ServiceHost, oluşturucusuna hangi bağımsız değişkenlerin iletileceğini değil, yalnızca oluşturulacak türün adını iletmeme izin veriyor.

Hizmet nesnemi oluşturan bir fabrikaya geçebilmek istiyorum.

Şimdiye kadar bulduklarım:


6
Korkarım karmaşıklık WCF'ye özgüdür ve onu hafifletmek için WCF kullanmamak veya Windsor kullanıyorsanız Windsor'un WCF Tesisi gibi daha kullanıcı dostu bir cephenin arkasına saklamak dışında yapabileceğiniz pek bir şey yoktur
Krzysztof Kozmic

Yanıtlar:


122

Sen özel bir kombinasyonunu uygulamak gerekir ServiceHostFactory, ServiceHostve IInstanceProvider.

Bu yapıcı imzasına sahip bir hizmet verildiğinde:

public MyService(IDependency dep)

İşte MyService'i çalıştırabilecek bir örnek:

public class MyServiceHostFactory : ServiceHostFactory
{
    private readonly IDependency dep;

    public MyServiceHostFactory()
    {
        this.dep = new MyClass();
    }

    protected override ServiceHost CreateServiceHost(Type serviceType,
        Uri[] baseAddresses)
    {
        return new MyServiceHost(this.dep, serviceType, baseAddresses);
    }
}

public class MyServiceHost : ServiceHost
{
    public MyServiceHost(IDependency dep, Type serviceType, params Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        foreach (var cd in this.ImplementedContracts.Values)
        {
            cd.Behaviors.Add(new MyInstanceProvider(dep));
        }
    }
}

public class MyInstanceProvider : IInstanceProvider, IContractBehavior
{
    private readonly IDependency dep;

    public MyInstanceProvider(IDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    #region IInstanceProvider Members

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return this.GetInstance(instanceContext);
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        return new MyService(this.dep);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        var disposable = instance as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }

    #endregion

    #region IContractBehavior Members

    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    }

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        dispatchRuntime.InstanceProvider = this;
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }

    #endregion
}

MyService.svc dosyanıza MyServiceHostFactory'yi kaydedin veya MyServiceHost'u kendi kendine barındırma senaryoları için doğrudan kodda kullanın.

Bu yaklaşımı kolayca genelleştirebilirsiniz ve aslında bazı DI Container'lar bunu sizin için zaten yaptı (işaret: Windsor'un WCF Tesisi).


1 (Ama iğrenç, #regions o suçun az şiddetli vaka olsa da, kendi kendime impl açık arayüze dönüştürmek: P)
Ruben Bartelink

5
Kendi kendine barındırmak için nasıl kullanabilirim? CreateServiceHost'u çağırdıktan sonra bir istisna alıyorum. Yalnızca korumalı yöntemi genel geçersiz kılma ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses) çağırabilirim; İstisna, istisna mesajı şudur: 'ServiceHostFactory.CreateServiceHost' mevcut barındırma ortamında çağrılamaz. Bu API, çağıran uygulamanın IIS veya WAS'ta barındırılmasını gerektirir.
Guy

2
@ Adam örnek problemi yaşıyorum. Çünkü işlevi protectedMain'den kendim
arayamam

1
Bu yaklaşımla ilgili doğal bir sorun vardır ve bu, bağımlılığınızın IIS barındırılan bir ortamda yalnızca bir kez oluşturulduğudur. ServiceHostFactory, ServiceHost ve InstanceProvider, uygulama havuzu geri dönüştürülene kadar yalnızca bir kez oluşturulur; bu, bağımlılığınızın çağrı başına gerçekten yenilenemeyeceği anlamına gelir (örneğin, DbContext), bu da değerlerin istenmeyen önbelleğe alınmasını ve bağımlılığın daha uzun ömrünü ortaya çıkarır. istenmiyor. Bunu nasıl çözeceğime gerçekten emin değilim, herhangi bir fikrin var mı?
David Anderson

2
@MarkSeemann Merak ediyorum, neden depher Sözleşmenin InstanceProvider'ına enjekte ettiniz? Yapabilirsin: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));nerede IMyService bir sözleşme arayüzü var MyService(IDependency dep). Bu yüzden IDependencyyalnızca gerçekten ihtiyaç duyan InstanceProvider'a enjekte edin.
voytek

14

Basitçe kendi örneğinizi oluşturabilir ve Servicebu örneği ServiceHostnesneye aktarabilirsiniz . Yapmanız gereken tek şey [ServiceBehaviour], hizmetinize bir öznitelik eklemek ve döndürülen tüm nesneleri [DataContract]öznitelik ile işaretlemektir .

İşte bir model:

namespace Service
{
    [ServiceContract]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class MyService
    {
        private readonly IDependency _dep;

        public MyService(IDependency dep)
        {
            _dep = dep;
        }

        public MyDataObject GetData()
        {
            return _dep.GetData();
        }
    }

    [DataContract]
    public class MyDataObject
    {
        public MyDataObject(string name)
        {
            Name = name;
        }

        public string Name { get; private set; }
    }

    public interface IDependency
    {
        MyDataObject GetData();
    }
}

ve kullanım:

var dep = new Dependecy();
var myService = new MyService(dep);
var host = new ServiceHost(myService);

host.Open();

Umarım bu, birileri için hayatı kolaylaştırır.


5
Bu sadece singletonlar için işe yarar (ile belirtildiği gibi InstanceContextMode.Single).
John Reynolds

11

Mark'ın cevabı IInstanceProviderdoğru.

Özel ServiceHostFactory'yi kullanmak yerine özel bir öznitelik de kullanabilirsiniz (örneğin MyInstanceProviderBehaviorAttribute). Aşağıdakilerden türetin Attribute, uygulamasını sağlayın IServiceBehaviorve aşağıdaki IServiceBehavior.ApplyDispatchBehaviorgibi uygulayın

// YourInstanceProvider implements IInstanceProvider
var instanceProvider = new YourInstanceProvider(<yourargs>);

foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
    foreach (var epDispatcher in dispatcher.Endpoints)
    {
        // this registers your custom IInstanceProvider
        epDispatcher.DispatchRuntime.InstanceProvider = instanceProvider;
    }
}

Ardından, özniteliği hizmet uygulama sınıfınıza uygulayın

[ServiceBehavior]
[MyInstanceProviderBehavior(<params as you want>)]
public class MyService : IMyContract

Üçüncü seçenek: Yapılandırma dosyasını kullanarak bir servis davranışı da uygulayabilirsiniz.


2
Teknik olarak, bu da bir çözüm gibi görünüyor, ancak bu yaklaşımla, IInstanceProvider'ı hizmetle sıkı bir şekilde birleştiriyorsunuz.
Mark Seemann

2
Sadece ikinci bir seçenek, neyin daha iyi olduğuna dair değerlendirme yok. Özel ServiceHostFactory'yi birkaç kez kullandım (özellikle birkaç davranışı kaydetmek istediğinizde).
dalo

1
Sorun, örneğin DI kapsayıcısını yalnızca öznitelik oluşturucusunda başlatabilmenizdir .. var olan verileri gönderemezsiniz.
Guy

5

Mark'ın cevabına göre çalıştım, ancak (en azından benim senaryom için) gereksiz derecede karmaşıktı. Yapıcılardan biri, ServiceHostdoğrudan ServiceHostFactoryuygulamadan aktarabileceğiniz bir hizmet örneğini kabul eder .

Mark'ın örneğini üstlenmek için şöyle görünecektir:

public class MyServiceHostFactory : ServiceHostFactory
{
    private readonly IDependency _dep;

    public MyServiceHostFactory()
    {
        _dep = new MyClass();
    }

    protected override ServiceHost CreateServiceHost(Type serviceType,
        Uri[] baseAddresses)
    {
        var instance = new MyService(_dep);
        return new MyServiceHost(instance, serviceType, baseAddresses);
    }
}

public class MyServiceHost : ServiceHost
{
    public MyServiceHost(MyService instance, Type serviceType, params Uri[] baseAddresses)
        : base(instance, baseAddresses)
    {
    }
}

12
Bu, hizmetiniz ve enjekte edilen tüm bağımlılıklar iş parçacığı açısından güvenli ise işe yarar. ServiceHost yapıcısının bu özel aşırı yüklemesi, WCF'nin yaşam döngüsü yönetimini esasen devre dışı bırakır. Bunun yerine, tüm eşzamanlı isteklerin tarafından ele alınacağını söylüyorsunuz instance. Bu, performansı etkileyebilir veya etkilemeyebilir. Eşzamanlı istekleri işleyebilmek istiyorsanız, tüm nesne grafiğinin iş parçacığı için güvenli olması gerekir, aksi takdirde deterministik olmayan, yanlış davranışlar elde edersiniz. İplik güvenliğini garanti edebiliyorsanız, benim çözümüm gerçekten gereksiz yere karmaşıktır. Bunu garanti edemezseniz, benim çözümüm gereklidir.
Mark Seemann

3

Vidala… Bağımlılık enjeksiyonu ve servis bulma modellerini harmanladım (ama çoğunlukla bağımlılık enjeksiyonu ve hatta kurucuda gerçekleşiyor, bu da salt okunur duruma sahip olabileceğiniz anlamına geliyor).

public class MyService : IMyService
{
    private readonly Dependencies _dependencies;

    // set this before creating service host. this can use your IOC container or whatever.
    // if you don't like the mutability shown here (IoC containers are usually immutable after being configured)
    // you can use some sort of write-once object
    // or more advanced approach like authenticated access
    public static Func<Dependencies> GetDependencies { get; set; }     
    public class Dependencies
    {
        // whatever your service needs here.
        public Thing1 Thing1 {get;}
        public Thing2 Thing2 {get;}

        public Dependencies(Thing1 thing1, Thing2 thing2)
        {
            Thing1 = thing1;
            Thing2 = thing2;
        }
    }

    public MyService ()
    {
        _dependencies = GetDependencies(); // this will blow up at run time in the exact same way your IoC container will if it hasn't been properly configured up front. NO DIFFERENCE
    }
}

Hizmetin bağımlılıkları, iç içe geçmiş Dependenciessınıfının sözleşmesinde açıkça belirtilmiştir . Bir IoC konteyneri kullanıyorsanız (WCF karmaşasını sizin için halihazırda düzeltmeyen bir konteynır), Dependenciesservis yerine örnek oluşturmak için onu yapılandırabilirsiniz . Bu şekilde, WCF tarafından empoze edilen çok fazla çemberden atlamak zorunda kalmazken, kabınızın size verdiği sıcak bulanık hissi elde edersiniz.

Bu yaklaşım yüzünden hiç uykumu kaybetmeyeceğim. Başkası da olmamalı. Sonuçta, IoC konteyneriniz, sizin için şeyler oluşturan büyük, şişman, statik bir delege koleksiyonudur. Bir tane daha eklemek ne?


Sorunun bir kısmı, şirketi bağımlılık enjeksiyonu kullanarak elde etmek istememdi ve eğer bağımlılık enjeksiyonu kullanmamış bir programcı için temiz ve basit görünmüyorsa, bağımlılık enjeksiyonu hiçbir zaman başka bir programcı tarafından kullanılmayacaktı. Ancak WCF'yi yıllardır kullanmıyorum ve bunu kaçırmıyorum!
Ian Ringrose

İşte bir kez yazılabilen bir mülk stackoverflow.com/questions/839788/…
Ronnie Overby

0

Aynı problemle karşı karşıyaydık ve aşağıdaki şekilde çözdük. Bu basit bir çözüm.

Visual Studio'da normal bir WCF servis uygulaması oluşturun ve arayüzünü kaldırın. .Cs dosyasını yerinde bırakın (sadece yeniden adlandırın) ve o cs dosyasını açın ve arayüzün adını, hizmet mantığını uygulayan orijinal sınıf adınızla değiştirin (bu şekilde, hizmet sınıfı, kalıtımı kullanır ve gerçek uygulamanızı değiştirir). Aşağıdaki gibi temel sınıfın yapıcılarını çağıran varsayılan bir kurucu ekleyin:

public class Service1 : MyLogicNamespace.MyService
{
    public Service1() : base(new MyDependency1(), new MyDependency2()) {}
}

MyService temel sınıfı, hizmetin gerçek uygulamasıdır. Bu temel sınıf, parametresiz bir kurucuya sahip olmamalı, yalnızca bağımlılıkları kabul eden parametrelere sahip oluşturuculara sahip olmalıdır.

Hizmet, orijinal MyService yerine bu sınıfı kullanmalıdır.

Bu basit bir çözüm ve bir cazibe gibi çalışıyor: -D


4
Service1'i bağımlılıklarından ayırmadınız, bu da önemli bir nokta. Temel sınıf olmadan yapabileceğiniz Service1 için yapıcıdaki bağımlılıkları başlattınız.
2015

0

Bu çok yararlı bir çözümdü - özellikle acemi bir WCF kodlayıcı olan biri için. Bunu IIS tarafından barındırılan bir hizmet için kullanıyor olabilecek tüm kullanıcılar için küçük bir ipucu göndermek istedim. MyServiceHost'un yalnızca ServiceHost'u değil, WebServiceHost'u devralması gerekir .

public class MyServiceHost : WebServiceHost
{
    public MyServiceHost(MyService instance, Type serviceType, params Uri[] baseAddresses)
        : base(instance, baseAddresses)
    {
    }
}

Bu, IIS'deki uç noktalarınız için gerekli tüm bağlamaları vb. Oluşturacaktır.


-2

Türümün statik değişkenlerini kullanıyorum. Bunun en iyi yol olduğundan emin değilim, ama benim için çalışıyor:

public class MyServer
{   
    public static string CustomerDisplayName;
    ...
}

Hizmet ana bilgisayarını örneklediğimde şunları yapıyorum:

protected override void OnStart(string[] args)
{
    MyServer.CustomerDisplayName = "Test customer";

    ...

    selfHost = new ServiceHost(typeof(MyServer), baseAddress);

    ....
}

5
Statik / Singleton'lar kötüdür! - bkz. stackoverflow.com/questions/137975/…
Immortal Blue
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.