Bagaimana cara mengirimkan nilai ke konstruktor pada layanan wcf saya?

103

Saya ingin meneruskan nilai ke konstruktor di kelas yang mengimplementasikan layanan saya.

Namun ServiceHost hanya mengizinkan saya meneruskan nama jenis yang akan dibuat, bukan argumen apa yang harus diteruskan ke pembuatnya.

Saya ingin lulus di pabrik yang membuat objek servis saya.

Apa yang saya temukan sejauh ini:

Ian Ringrose
sumber
6
Saya khawatir kerumitannya melekat pada WCF dan tidak banyak yang dapat Anda lakukan untuk meringankannya, selain tidak menggunakan WCF atau menyembunyikannya di balik fasad yang lebih ramah pengguna, seperti Fasilitas WCF Windsor jika Anda menggunakan Windsor
Krzysztof Kozmic

Jawaban:

122

Anda harus menerapkan kombinasi ubahsuaian ServiceHostFactory, ServiceHostdan IInstanceProvider.

Diberikan layanan dengan tanda tangan konstruktor ini:

public MyService(IDependency dep)

Berikut adalah contoh yang dapat menjalankan MyService:

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
}

Daftarkan MyServiceHostFactory di file MyService.svc Anda, atau gunakan MyServiceHost secara langsung dalam kode untuk skenario hosting mandiri.

Anda dapat dengan mudah menggeneralisasi pendekatan ini, dan kenyataannya beberapa Kontainer DI telah melakukan ini untuk Anda (petunjuk: Fasilitas WCF Windsor).

Mark Seemann
sumber
+1 (Tapi ya , #regions meskipun itu kasus pelanggaran yang paling parah, saya mengonversi ke antarmuka eksplisit tersirat sendiri: P)
Ruben Bartelink
5
Bagaimana saya bisa menggunakannya untuk hosting mandiri? Saya menerima pengecualian setelah menelepon ke CreateServiceHost. Saya hanya dapat memanggil ke metode yang dilindungi public override ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses); Pengecualiannya adalah Pesan pengecualiannya adalah: 'ServiceHostFactory.CreateServiceHost' tidak dapat dipanggil dalam lingkungan hosting saat ini. API ini mengharuskan aplikasi panggilan di-host di IIS atau WS.
Guy
2
@Guy Saya mengalami masalah sampel. Karena fungsinya protectedsaya tidak bisa memanggilnya sendiri dari Main ()
Andriy Drozdyuk
1
Ada masalah yang melekat dengan pendekatan ini, dan itu adalah ketergantungan Anda sebenarnya hanya dibuat sekali di lingkungan yang dihosting IIS. ServiceHostFactory, ServiceHost, dan InstanceProvider semuanya hanya dibuat satu kali hingga pool aplikasi didaur ulang, yang berarti bahwa dependensi Anda tidak dapat benar-benar di-refresh per panggilan (DbContext misalnya), yang memperkenalkan nilai-nilai caching yang tidak disengaja dan masa pakai yang lebih lama dari dependensi tersebut. tidak diinginkan. Saya tidak begitu yakin bagaimana menyelesaikan ini, ada pemikiran?
David Anderson
2
@MarkSeemann Saya hanya ingin tahu, mengapa Anda menyuntikkan depke dalam setiap InstanceProvider Kontrak . Anda dapat melakukan: di ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));mana IMyService antarmuka kontrak Anda MyService(IDependency dep). Jadi, masukkan IDependencyhanya ke InstanceProvider yang benar-benar membutuhkannya.
voytek
14

Anda cukup membuat dan instance dari Anda Servicedan meneruskan instance itu ke ServiceHostobjek. Satu-satunya hal yang harus Anda lakukan adalah menambahkan [ServiceBehaviour]atribut untuk layanan Anda dan menandai semua objek yang dikembalikan dengan [DataContract]atribut.

Ini mock upnya:

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();
    }
}

dan penggunaan:

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

host.Open();

Saya berharap ini akan membuat hidup seseorang lebih mudah.

kerim
sumber
5
Itu hanya berlaku untuk lajang (seperti yang ditunjukkan oleh InstanceContextMode.Single).
John Reynolds
11

Jawaban Markus dengan IInstanceProviderbenar.

Daripada menggunakan ServiceHostFactory kustom, Anda juga bisa menggunakan atribut kustom (misalnya MyInstanceProviderBehaviorAttribute). Turunkan dari Attribute, buat itu menerapkan IServiceBehaviordan menerapkan IServiceBehavior.ApplyDispatchBehaviormetode seperti

// 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;
    }
}

Kemudian, terapkan atribut ke kelas implementasi layanan Anda

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

Opsi ketiga: Anda juga dapat menerapkan perilaku layanan menggunakan file konfigurasi.

dalo
sumber
2
Secara teknis, ini juga terlihat seperti solusi, tetapi dengan pendekatan itu, Anda akan memasangkan IInstanceProvider dengan layanan.
Mark Seemann
2
Hanya pilihan kedua, tidak ada penilaian tentang apa yang lebih baik. Saya telah menggunakan ServiceHostFactory kustom beberapa kali (terutama ketika Anda ingin mendaftarkan beberapa perilaku).
dalo
1
Masalahnya adalah Anda dapat memulai misalnya DI container hanya di konstruktor atribut .. Anda tidak dapat mengirim data yang ada.
Guy
5

Saya bekerja dari jawaban Mark, tetapi (untuk skenario saya setidaknya), itu sangat rumit. Salah satu ServiceHostkonstruktor menerima sebuah instance layanan, yang bisa Anda teruskan langsung dari ServiceHostFactoryimplementasi.

Untuk mendukung contoh Mark, akan terlihat seperti ini:

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)
    {
    }
}
McGarnagle
sumber
12
Ini akan berfungsi jika layanan Anda dan semua dependensi yang diinjeksi aman untuk thread. Kelebihan tertentu dari konstruktor ServiceHost pada dasarnya menonaktifkan manajemen siklus proses WCF. Sebaliknya, Anda mengatakan bahwa semua permintaan bersamaan akan ditangani oleh instance. Itu mungkin atau mungkin tidak memengaruhi kinerja. Jika Anda ingin dapat menangani permintaan berbarengan, seluruh grafik objek tersebut harus aman untuk thread, atau Anda akan mendapatkan perilaku non-deterministik, perilaku yang salah. Jika Anda bisa menjamin keamanan benang, solusi saya memang sangat rumit. Jika Anda tidak dapat menjamin itu, solusi saya diperlukan.
Mark Seemann
3

Persetan ... Saya memadukan injeksi ketergantungan dan pola pelacak layanan (tetapi kebanyakan itu masih injeksi ketergantungan dan bahkan terjadi di konstruktor yang berarti Anda dapat memiliki status hanya-baca).

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
    }
}

Dependensi layanan dengan jelas ditentukan dalam kontrak Dependencieskelas bertingkatnya . Jika Anda menggunakan kontainer IoC (yang belum memperbaiki kekacauan WCF untuk Anda), Anda dapat mengonfigurasinya untuk membuat Dependenciesinstance, bukan layanan. Dengan cara ini Anda mendapatkan perasaan fuzzy hangat yang diberikan oleh wadah Anda sementara juga tidak harus melewati terlalu banyak rintangan yang diberlakukan oleh WCF.

Saya tidak akan kehilangan waktu tidur karena pendekatan ini. Begitu pula seharusnya orang lain. Bagaimanapun, wadah IoC Anda adalah kumpulan delegasi statis yang besar, gemuk, dan statis yang menciptakan berbagai hal untuk Anda. Apa menambahkan satu lagi?

Ronnie Overby
sumber
Sebagian dari masalahnya adalah saya ingin membuat perusahaan menggunakan injeksi ketergantungan, dan jika tidak terlihat bersih dan sederhana bagi seorang programmer yang tidak pernah menggunakan injeksi ketergantungan, maka injeksi ketergantungan tidak akan pernah digunakan oleh programmer lain. Namun saya tidak pernah menggunakan WCF selama bertahun-tahun, dan saya tidak melewatkannya!
Ian Ringrose
Inilah pendekatan saya terhadap properti sekali tulis stackoverflow.com/questions/839788/…
Ronnie Overby
0

Kami menghadapi masalah yang sama ini dan menyelesaikannya dengan cara berikut. Ini adalah solusi sederhana.

Dalam Visual Studio cukup buat aplikasi layanan WCF normal dan hapus antarmuka itu. Biarkan file .cs di tempatnya (cukup ganti namanya) dan buka file cs itu dan ganti nama antarmuka dengan nama kelas asli Anda yang mengimplementasikan logika layanan (dengan cara ini kelas layanan menggunakan pewarisan dan menggantikan implementasi Anda yang sebenarnya). Tambahkan konstruktor default yang memanggil konstruktor kelas dasar, seperti ini:

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

Kelas dasar MyService adalah implementasi aktual dari layanan. Kelas dasar ini tidak boleh memiliki konstruktor tanpa parameter, tetapi hanya konstruktor dengan parameter yang menerima dependensi.

Layanan harus menggunakan kelas ini, bukan MyService asli.

Ini solusi sederhana dan bekerja seperti pesona :-D

Ron Deijkers
sumber
4
Anda belum memisahkan Service1 dari dependensinya, yang merupakan intinya. Anda baru saja membuat instance dependensi dalam konstruktor untuk Service1, yang dapat Anda lakukan tanpa kelas dasar.
saille
0

Ini adalah solusi yang sangat membantu - terutama untuk seseorang yang merupakan pembuat kode WCF pemula. Saya memang ingin memposting sedikit tip untuk setiap pengguna yang mungkin menggunakan ini untuk layanan yang dihosting IIS. MyServiceHost perlu mewarisi WebServiceHost , bukan hanya ServiceHost.

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

Ini akan membuat semua binding yang diperlukan, dll. Untuk endpoint Anda di IIS.

Eric Dieckman
sumber
-2

Saya menggunakan variabel statis tipe saya. Tidak yakin apakah ini cara terbaik, tetapi berhasil untuk saya:

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

Ketika saya memberi contoh host layanan, saya melakukan hal berikut:

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

    ...

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

    ....
}
Boris
sumber
5
Statis / Lajang itu jahat! - lihat stackoverflow.com/questions/137975/…
Immortal Blue