Apa solusi terbaik untuk klien `menggunakan` masalah blok WCF?

404

Saya suka instantiating klien layanan WCF saya dalam satu usingblok karena cukup banyak cara standar untuk menggunakan sumber daya yang mengimplementasikan IDisposable:

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

Tapi, seperti yang disebutkan dalam artikel MSDN ini , membungkus klien WCF dalam sebuah usingblok bisa menutupi kesalahan yang mengakibatkan klien dibiarkan dalam keadaan rusak (seperti waktu habis atau masalah komunikasi). Singkatnya, ketika Buang () dipanggil, metode Tutup () klien akan menyala, tetapi melempar kesalahan karena itu dalam kondisi rusak. Pengecualian asli kemudian ditutup oleh pengecualian kedua. Tidak baik.

Solusi yang disarankan dalam artikel MSDN adalah untuk sepenuhnya menghindari menggunakan usingblok, dan sebagai gantinya instantiate klien Anda dan menggunakannya sesuatu seperti ini:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

Dibandingkan dengan usingblok, saya pikir itu jelek. Dan banyak kode untuk ditulis setiap kali Anda membutuhkan klien.

Untungnya, saya menemukan beberapa solusi lain, seperti yang ini di IServiceOriented. Anda mulai dengan:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

Yang kemudian memungkinkan:

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

Itu tidak buruk, tetapi saya tidak berpikir itu sebagai ekspresif dan mudah dimengerti sebagai usingblok.

Solusi yang saat ini saya coba gunakan adalah yang pertama saya baca di blog.davidbarret.net . Pada dasarnya Anda mengganti metode klien di Dispose()mana pun Anda menggunakannya. Sesuatu seperti:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

Ini tampaknya dapat memungkinkan usingblok lagi tanpa bahaya menutupi pengecualian kondisi yang salah.

Jadi, apakah ada gotchas lain yang harus saya perhatikan untuk menggunakan solusi ini? Adakah yang datang dengan sesuatu yang lebih baik?

Eric King
sumber
42
Yang terakhir (yang memeriksa hal ini. Pernyataan) adalah lomba; itu mungkin tidak salah ketika Anda memeriksa boolean, tetapi mungkin salah ketika Anda memanggil Tutup ().
Brian
15
Anda membaca status; itu tidak salah. Sebelum Anda menelepon Tutup (), salurannya salah. Tutup () melempar. Permainan telah berakhir.
Brian
4
Waktu berlalu. Ini mungkin periode yang sangat singkat, tetapi secara teknis, dalam periode waktu antara memeriksa kondisi saluran dan memintanya untuk menutup, kondisi saluran dapat berubah.
Eric King
8
Saya akan menggunakan Action<T>sebagai gantinya UseServiceDelegate<T>. minor.
hIpPy
2
Saya benar-benar tidak suka pembantu statis ini Service<T>karena mempersulit pengujian unit (seperti kebanyakan hal statis lakukan). Saya lebih suka itu non-statis sehingga dapat disuntikkan ke kelas yang menggunakannya.
Fabio Marreco

Jawaban:

137

Sebenarnya, meskipun saya ngeblog (lihat jawaban Luke ), saya pikir ini lebih baik daripada bungkus IDisposable saya. Kode khas:

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
}); 

(edit per komentar)

Sejak Usepengembalian batal, cara termudah untuk menangani nilai kembali adalah melalui variabel yang ditangkap:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated
Marc Gravell
sumber
2
@MarcGravell Di mana saya bisa menyuntikkan klien itu? Saya berasumsi bahwa ChannelFactory menciptakan klien, dan objek pabrik baru di dalam kelas Layanan, yang berarti bahwa kode tersebut harus dire-refored sedikit untuk memungkinkan pabrik kustom. Apakah ini benar, atau saya kehilangan sesuatu yang jelas di sini?
Anttu
16
Anda dapat dengan mudah memodifikasi pembungkus sehingga Anda tidak perlu variabel tangkapan untuk hasilnya. Sesuatu seperti ini: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris
3
Mungkin bermanfaat https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/ dan https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/ dan http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón
Bagaimana saya bisa menambah kredensial dengan menggunakan cara ini?
Hippasus
2
Menurut pendapat saya, solusi yang paling benar adalah: 1) Lakukan pola Tutup / Batalkan tanpa kondisi balapan 2) Tangani situasi ketika operasi layanan melempar pengecualian 3) Menangani situasi ketika kedua metode Tutup dan Batalkan melempar pengecualian 4) Menangani pengecualian asinkron seperti ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet
88

Diberi pilihan antara solusi yang disarankan oleh IServiceOriented.com dan solusi yang disarankan oleh blog David Barret , saya lebih suka kesederhanaan yang ditawarkan dengan mengabaikan metode Dispose () klien. Ini memungkinkan saya untuk terus menggunakan pernyataan using () seperti yang diharapkan orang dengan objek sekali pakai. Namun, seperti yang ditunjukkan oleh @Brian, solusi ini berisi kondisi balapan di mana Negara mungkin tidak disalahkan ketika dicentang tetapi bisa pada saat Close () dipanggil, dalam hal ini CommunicationException masih terjadi.

Jadi, untuk menyiasati ini, saya telah menggunakan solusi yang memadukan yang terbaik dari kedua dunia.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
Matt Davis
sumber
2
bukankah berisiko menggunakan pernyataan 'Coba-Akhirnya' (atau gula sintaksis - "menggunakan () {}") dengan sumber daya yang tidak dikelola? Contohnya, jika opsi "Tutup" gagal, pengecualian tidak tertangkap, dan akhirnya mungkin tidak berjalan. Juga, jika ada pengecualian dalam pernyataan terakhir itu bisa menutupi pengecualian lain. Saya pikir itu sebabnya Try-Catch lebih disukai.
Zack Jannsen
Zack, tidak jelas tentang objek Anda; apa yang saya lewatkan? Jika metode Close melempar eksepsi, blok akhirnya akan dijalankan sebelum eksepsi dilontarkan. Baik?
Patrick Szalapski
1
@ jmoreno, saya membuka kancing suntingan Anda. Jika Anda akan melihat, tidak ada blok tangkap sama sekali dalam metode ini. Idenya adalah bahwa setiap pengecualian yang terjadi (bahkan pada akhirnya) harus dibuang, tidak ditangkap secara diam-diam.
Matt Davis
5
@MattDavis Mengapa Anda perlu successflag? Mengapa tidak try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin
Bagaimana dengan mencoba-coba Close(); success = true;? Saya tidak ingin ada pengecualian yang dilemparkan jika saya berhasil membatalkannya di blok terakhir. Saya hanya ingin pengecualian dilemparkan jika Abort () gagal dalam kasus itu. Dengan cara ini, coba / tangkap akan menyembunyikan pengecualian kondisi lomba potensial dan masih memungkinkan Anda untuk membatalkan () koneksi di blok akhirnya.
goku_da_master
32

Saya menulis fungsi tingkat tinggi untuk membuatnya berfungsi dengan benar. Kami telah menggunakan ini di beberapa proyek dan tampaknya bekerja dengan baik. Inilah yang seharusnya dilakukan sejak awal, tanpa paradigma "menggunakan" atau seterusnya.

TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code)
{
    var chanFactory = GetCachedFactory<TChannel>();
    TChannel channel = chanFactory.CreateChannel();
    bool error = true;
    try {
        TReturn result = code(channel);
        ((IClientChannel)channel).Close();
        error = false;
        return result;
    }
    finally {
        if (error) {
            ((IClientChannel)channel).Abort();
        }
    }
}

Anda dapat melakukan panggilan seperti ini:

int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);

Ini cukup banyak seperti yang Anda miliki dalam contoh Anda. Dalam beberapa proyek, kami menulis metode pembantu yang sangat diketik, jadi kami akhirnya menulis hal-hal seperti "Wcf.UseFooService (f => f ...)".

Saya merasa cukup elegan, semua hal dipertimbangkan. Apakah ada masalah khusus yang Anda temui?

Ini memungkinkan fitur bagus lainnya untuk dicolokkan. Misalnya, di satu situs, situs mengautentikasi ke layanan atas nama pengguna yang masuk. (Situs tidak memiliki kredensial dengan sendirinya.) Dengan menulis pembantu metode "UseService" kami sendiri, kami dapat mengonfigurasi pabrik saluran seperti yang kami inginkan, dll. Kami juga tidak terikat untuk menggunakan proxy yang dihasilkan - antarmuka apa pun akan melakukan .

MichaelGG
sumber
Saya mendapatkan pengecualian: Properti Alamat di ChannelFactory.Endpoint adalah nol. Titik Akhir ChannelFactory harus memiliki Alamat yang valid yang ditentukan . Apa itu GetCachedFactorymetode?
Marshall
28

Ini adalah cara yang disarankan Microsoft untuk menangani panggilan klien WCF:

Untuk detail lebih lanjut, lihat: Pengecualian yang Diharapkan

try
{
    ...
    double result = client.Add(value1, value2);
    ...
    client.Close();
}
catch (TimeoutException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}
catch (CommunicationException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}

Informasi tambahan Begitu banyak orang tampaknya mengajukan pertanyaan ini di WCF sehingga Microsoft bahkan membuat sampel khusus untuk menunjukkan bagaimana menangani pengecualian:

c: \ WF_WCF_Samples \ WCF \ Basic \ Client \ ExpectedExceptions \ CS \ client

Unduh sampel: C # atau VB

Mempertimbangkan bahwa ada begitu banyak masalah yang melibatkan pernyataan menggunakan , (dipanaskan?) Diskusi internal dan utas tentang masalah ini, saya tidak akan membuang waktu saya mencoba menjadi koboi kode dan menemukan cara yang lebih bersih. Saya hanya akan menyedotnya, dan mengimplementasikan klien WCF cara verbose (belum tepercaya) ini untuk aplikasi server saya.

Kegagalan tambahan opsional untuk ditangkap

Banyak pengecualian berasal dari CommunicationExceptiondan saya tidak berpikir sebagian besar pengecualian harus diulang. Saya menelusuri setiap pengecualian pada MSDN dan menemukan daftar pendek pengecualian yang dapat dicoba lagi (selain di TimeOutExceptionatas). Beri tahu saya jika saya melewatkan pengecualian yang harus diulang.

  // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
catch (ChannelTerminatedException cte)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
// reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
catch (EndpointNotFoundException enfe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following exception that is thrown when a server is too busy to accept a message.
catch (ServerTooBusyException stbe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

Memang, ini adalah sedikit kode biasa untuk ditulis. Saat ini saya lebih suka jawaban ini , dan tidak melihat "peretasan" dalam kode yang dapat menyebabkan masalah di jalan.

goodguys_activate
sumber
1
Apakah kode dari sampel masih menyebabkan masalah? Saya mencoba menjalankan proyek UsingUsing (VS2013) tetapi sambungannya "Hope this code wasn't important, because it might not happen."masih dieksekusi ...
janv8000
14

Saya akhirnya menemukan beberapa langkah kuat menuju solusi bersih untuk masalah ini.

Alat kustom ini memperluas WCFProxyGenerator untuk memberikan proksi penanganan pengecualian. Ini menghasilkan proksi tambahan yang disebut ExceptionHandlingProxy<T>yang mewarisi ExceptionHandlingProxyBase<T>- yang terakhir mengimplementasikan daging fungsi proxy. Hasilnya adalah Anda dapat memilih untuk menggunakan proxy default yang mewarisi ClientBase<T>atau ExceptionHandlingProxy<T>yang merangkum mengelola masa pakai saluran dan saluran pabrik. ExceptionHandlingProxy menghormati pilihan Anda dalam dialog Tambahkan Referensi Layanan sehubungan dengan metode dan jenis pengumpulan asinkron.

Codeplex memiliki proyek yang disebut Exception Handling WCF Proxy Generator . Ini pada dasarnya menginstal alat kustom baru ke Visual Studio 2008, kemudian gunakan alat ini untuk menghasilkan proksi layanan baru (Tambahkan referensi layanan) . Ini memiliki beberapa fungsionalitas yang bagus untuk menangani saluran yang rusak, batas waktu dan pembuangan yang aman. Ada video luar biasa di sini yang disebut ExceptionHandlingProxyWrapper yang menjelaskan cara kerjanya.

Anda dapat menggunakan Usingpernyataan itu lagi dengan aman , dan jika salurannya salah pada permintaan apa pun (TimeoutException atau CommunicationException), Pembungkus akan menginisialisasi ulang saluran yang rusak dan mencoba lagi kueri. Jika itu gagal maka akan memanggil Abort()perintah dan membuang proxy dan rethrow Pengecualian. Jika layanan melempar FaultExceptionkode itu akan berhenti mengeksekusi, dan proksi akan dibatalkan dengan aman melemparkan pengecualian yang benar seperti yang diharapkan.

Neil
sumber
@IMimmy Status Beta. Tanggal: Sabtu 11 Juli 2009 oleh Michele Bustamante . Proyek Mati?
Kiquenet
11

Berdasarkan jawaban Marc Gravell, MichaelGG, dan Matt Davis, pengembang kami membuat yang berikut:

public static class UsingServiceClient
{
    public static void Do<TClient>(TClient client, Action<TClient> execute)
        where TClient : class, ICommunicationObject
    {
        try
        {
            execute(client);
        }
        finally
        {
            client.DisposeSafely();
        }
    }

    public static void DisposeSafely(this ICommunicationObject client)
    {
        if (client == null)
        {
            return;
        }

        bool success = false;

        try
        {
            if (client.State != CommunicationState.Faulted)
            {
                client.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
            {
                client.Abort();
            }
        }
    }
}

Contoh penggunaan:

string result = string.Empty;

UsingServiceClient.Do(
    new MyServiceClient(),
    client =>
    result = client.GetServiceResult(parameters));

Itu sedekat mungkin dengan sintaks "menggunakan", Anda tidak harus mengembalikan nilai dummy saat memanggil metode void, dan Anda dapat membuat beberapa panggilan ke layanan (dan mengembalikan beberapa nilai) tanpa harus menggunakan tupel.

Selain itu, Anda dapat menggunakan ini dengan ClientBase<T>keturunan daripada ChannelFactory jika diinginkan.

Metode ekstensi terbuka jika pengembang ingin membuang proxy / saluran secara manual.

TrueWill
sumber
Menggunakan ini masuk akal jika saya menggunakan PoolingDuplex dan tidak menutup koneksi setelah panggilan sehingga layanan klien saya mungkin hidup beberapa hari dan menangani panggilan balik server. Sejauh yang saya mengerti solusi yang dibahas di sini masuk akal untuk satu panggilan per sesi?
sll
@sll - ini untuk menutup koneksi segera setelah panggilan kembali (satu panggilan per sesi).
TrueWill
@cacho Menjadikan DisposeSafelypribadi merupakan pilihan, dan akan menghindari kebingungan. Mungkin ada kasus penggunaan di mana seseorang ingin memanggilnya secara langsung, tetapi saya tidak dapat menemukan satu kasus begitu saja.
TrueWill
@truewill hanya untuk dokumentasi, penting juga untuk menyebutkan bahwa metode ini aman bukan?
Cacho Santa
1
Menurut pendapat saya, solusi yang paling benar adalah: 1) Lakukan pola Tutup / Batalkan tanpa kondisi balapan 2) Tangani situasi ketika operasi layanan melempar pengecualian 3) Menangani situasi ketika kedua metode Tutup dan Batalkan melempar pengecualian 4) Menangani pengecualian asinkron seperti ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet
8

@Marc Gravell

Bukankah boleh menggunakan ini:

public static TResult Using<T, TResult>(this T client, Func<T, TResult> work)
        where T : ICommunicationObject
{
    try
    {
        var result = work(client);

        client.Close();

        return result;
    }
    catch (Exception e)
    {
        client.Abort();

        throw;
    }
}

Atau, hal yang sama (Func<T, TResult>)jika terjadiService<IOrderService>.Use

Ini akan membuat variabel pengembalian lebih mudah.

berbentuk segi empat
sumber
2
+1 @MarcGravell Saya pikir jawaban Anda 'bisa melakukan lebih baik' juga: P (dan tindakan yang dapat diimplementasikan dalam hal Fungsi dengan pengembalian nol). Seluruh halaman ini berantakan - saya akan memformulasikan satu kesatuan dan mengomentari dups jika saya membayangkan menggunakan WCF setiap saat dalam dekade ini ...
Ruben Bartelink
7

Apa ini?

Ini adalah versi CW dari jawaban yang diterima tetapi dengan (apa yang saya anggap lengkap) termasuk penanganan Perkecualian.

Jawaban yang diterima merujuk situs web ini yang tidak lagi ada . Untuk menghemat masalah Anda, saya menyertakan bagian yang paling relevan di sini. Selain itu, saya memodifikasinya sedikit untuk memasukkan penanganan retry pengecualian untuk menangani timeout jaringan yang sial itu.

Penggunaan Klien WCF Sederhana

Setelah Anda menghasilkan proxy sisi klien Anda, ini yang Anda butuhkan untuk mengimplementasikannya.

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
});

ServiceDelegate.cs

Tambahkan file ini ke solusi Anda. Tidak diperlukan perubahan pada file ini, kecuali jika Anda ingin mengubah jumlah coba lagi atau pengecualian apa yang ingin Anda tangani.

public delegate void UseServiceDelegate<T>(T proxy);

public static class Service<T>
{
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock)
    {
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
        bool success = false;


       Exception mostRecentEx = null;
       int millsecondsToSleep = 1000;

       for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
       {
           try
           {
               codeBlock((T)proxy);
               proxy.Close();
               success = true; 
               break;
           }

           // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
           catch (ChannelTerminatedException cte)
           {
              mostRecentEx = cte;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep  * (i + 1)); 
           }

           // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
           // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
           catch (EndpointNotFoundException enfe)
           {
              mostRecentEx = enfe;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }

           // The following exception that is thrown when a server is too busy to accept a message.
           catch (ServerTooBusyException stbe)
           {
              mostRecentEx = stbe;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch (TimeoutException timeoutEx)
           {
               mostRecentEx = timeoutEx;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           } 
           catch (CommunicationException comException)
           {
               mostRecentEx = comException;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch(Exception )
           {
                // rethrow any other exception not defined here
                // You may want to define a custom Exception class to pass information such as failure count, and failure type
                proxy.Abort();
                throw ;  
           }
       }
       if (success == false && mostRecentEx != null) 
       { 
           proxy.Abort();
           throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
       }

    }
}

PS: Saya membuat posting ini sebagai wiki komunitas. Saya tidak akan mengumpulkan "poin" dari jawaban ini, tetapi saya lebih suka Anda meningkatkannya jika Anda setuju dengan implementasi, atau mengeditnya untuk membuatnya lebih baik.

LamonteCristo
sumber
Saya tidak yakin saya setuju dengan karakterisasi Anda atas jawaban ini. Ini adalah versi CW dengan gagasan Anda tentang penanganan pengecualian ditambahkan.
John Saunders
@ JohnSaunders - Benar (konsep saya tentang penanganan pengecualian). Beri tahu saya tentang pengecualian yang saya lewatkan atau salah penanganan.
goodguys_activate
Bagaimana dengan variabel keberhasilan? Perlu menambahkan ke kode sumber: jika (berhasil) kembali; ??
Kiquenet
Jika panggilan pertama melempar dan ke-2 berhasil mostRecentEx tidak akan null sehingga Anda melemparkan pengecualian yang gagal 5 mencoba lagi. atau aku melewatkan sesuatu? Saya tidak melihat di mana Anda menghapus mostRecentEx jika pada percobaan ke-2, ke-3, ke-4 atau ke-5 berhasil. Juga tidak melihat pengembalian o berhasil. Saya harus kehilangan sesuatu di sini, tetapi kode ini tidak akan berjalan selalu 5 kali jika tidak ada pengecualian yang dilemparkan?
Bart Calixto
@ Bart - Saya menambahkan success == falseke final jika pernyataan
goodguys_activate
7

Di bawah ini adalah versi sumber yang disempurnakan dari pertanyaan dan diperluas untuk men -cache beberapa pabrik saluran dan berupaya mencari titik akhir dalam file konfigurasi dengan nama kontrak.

Menggunakan .NET 4 (khusus: contravariance, LINQ, var):

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}
Jesse C. Slicer
sumber
1
Kenapa menggunakan UseServiceDelegate<T>bukan Action<T>?
Mike Mayer
1
Satu-satunya alasan saya dapat berpikir bahwa penulis asli melakukan itu adalah untuk memiliki delegasi yang sangat diketik pengembang akan tahu milik memanggil layanan. Tapi, sejauh yang saya bisa lihat, Action<T>bekerja dengan baik.
Jesse C. Slicer
5

Pembungkus seperti ini berfungsi:

public class ServiceClientWrapper<ServiceType> : IDisposable
{
    private ServiceType _channel;
    public ServiceType Channel
    {
        get { return _channel; }
    }

    private static ChannelFactory<ServiceType> _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
        _channel = _channelFactory.CreateChannel();
        ((IChannel)_channel).Open();
    }

    public void Dispose()
    {
        try
        {
            ((IChannel)_channel).Close();
        }
        catch (Exception e)
        {
            ((IChannel)_channel).Abort();
            // TODO: Insert logging
        }
    }
}

Itu akan memungkinkan Anda untuk menulis kode seperti:

ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
    var request = ...
    response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.

Pembungkus tentu saja dapat menangkap lebih banyak pengecualian jika itu diperlukan, tetapi prinsipnya tetap sama.

Tomas Jansson
sumber
Saya ingat diskusi tentang Buang tidak dipanggil dalam kondisi tertentu ... mengakibatkan kebocoran memori w / WCF.
goodguys_activate
Saya tidak yakin itu mengakibatkan kebocoran memori tetapi masalahnya adalah ini. Ketika Anda memanggil DisposeIChannel, ia bisa melempar pengecualian jika salurannya dalam kondisi rusak, ini merupakan masalah karena Microsoft menetapkan bahwa Disposeseharusnya tidak pernah melempar. Jadi yang dilakukan oleh kode di atas adalah menangani case ketika Closemelempar pengecualian. Jika Abortmelempar itu mungkin ada sesuatu yang salah serius. Saya menulis posting blog tentang hal itu Desember lalu: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson
4

Saya menggunakan proksi dinamis Castle untuk menyelesaikan masalah Buang (), dan juga menerapkan penyegaran otomatis saluran saat berada dalam keadaan tidak dapat digunakan. Untuk menggunakan ini, Anda harus membuat antarmuka baru yang mewarisi kontrak layanan dan IDisposable Anda. Proxy dinamis mengimplementasikan antarmuka ini dan membungkus saluran WCF:

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();

Saya suka ini karena Anda dapat menyuntikkan layanan WCF tanpa konsumen perlu khawatir tentang rincian WCF. Dan tidak ada tambahan cruft seperti solusi lainnya.

Lihatlah kodenya, sebenarnya cukup sederhana: WCF Dynamic Proxy

Jay Douglass
sumber
4

Gunakan metode ekstensi:

public static class CommunicationObjectExtensions
{
    public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
    {
        TResult result;

        try
        {
            result = method(client);
        }
        finally
        {
            try
            {
                client.Close();
            }
            catch (CommunicationException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (TimeoutException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (Exception)
            {
                client.Abort();
                throw;
            }
        }

        return result;
    }
}
Johan Nyman
sumber
4

Jika Anda tidak memerlukan IoC atau menggunakan klien yang di-autogenerasi (Referensi Layanan), maka Anda dapat menggunakan pembungkus sederhana untuk mengelola penutupan dan membiarkan GC mengambil basis klien ketika berada dalam keadaan aman yang tidak akan membuang pengecualian. GC akan memanggil Buang dalam layanan serviceclient, dan ini akan memanggil Close. Karena sudah dibaca tertutup, tidak dapat menyebabkan kerusakan. Saya menggunakan ini tanpa masalah dalam kode produksi.

public class AutoCloseWcf : IDisposable
{

    private ICommunicationObject CommunicationObject;

    public AutoDisconnect(ICommunicationObject CommunicationObject)
    {
        this.CommunicationObject = CommunicationObject;
    }

    public void Dispose()
    {
        if (CommunicationObject == null)
            return;
        try {
            if (CommunicationObject.State != CommunicationState.Faulted) {
                CommunicationObject.Close();
            } else {
                CommunicationObject.Abort();
            }
        } catch (CommunicationException ce) {
            CommunicationObject.Abort();
        } catch (TimeoutException toe) {
            CommunicationObject.Abort();
        } catch (Exception e) {
            CommunicationObject.Abort();
            //Perhaps log this

        } finally {
            CommunicationObject = null;
        }
    }
}

Kemudian ketika Anda mengakses server, Anda membuat klien dan menggunakan usingautodisconect:

var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {

    Ws.Open();

    Ws.Test();
}
Luiz Felipe
sumber
3

Ringkasan

Menggunakan teknik yang dijelaskan dalam jawaban ini orang dapat mengkonsumsi layanan WCF di blok menggunakan dengan sintaks berikut:

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Anda tentu saja dapat menyesuaikan ini lebih jauh untuk mencapai model pemrograman yang lebih ringkas khusus untuk situasi Anda - tetapi intinya adalah bahwa kita dapat membuat implementasi IMyServicereprenting saluran yang mengimplementasikan pola disposable yang benar.


Detail

Semua jawaban yang diberikan sejauh ini mengatasi masalah untuk mengatasi "bug" dalam implementasi WCF Channel IDisposable. Jawaban yang tampaknya menawarkan model pemrograman yang paling ringkas (memungkinkan Anda untuk menggunakan usingblok untuk membuang sumber daya yang tidak dikelola) adalah yang ini - di mana proxy dimodifikasi untuk diimplementasikan IDisposabledengan implementasi bebas bug. Masalah dengan pendekatan ini adalah kemampuan pemeliharaan - kita harus mengimplementasikan kembali fungsi ini untuk proksi yang pernah kita gunakan. Pada variasi jawaban ini kita akan melihat bagaimana kita dapat menggunakan komposisi daripada pewarisan untuk membuat teknik ini generik.

Percobaan pertama

Tampaknya ada berbagai implementasi untuk IDisposableimplementasi, tetapi demi argumen kami akan menggunakan adaptasi yang digunakan oleh jawaban yang saat ini diterima .

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void DoWork();
}

public class ProxyDisposer : IDisposable
{
    private IClientChannel _clientChannel;


    public ProxyDisposer(IClientChannel clientChannel)
    {
        _clientChannel = clientChannel;
    }

    public void Dispose()
    {
        var success = false;
        try
        {
            _clientChannel.Close();
            success = true;
        }
        finally
        {
            if (!success)
                _clientChannel.Abort();
            _clientChannel = null;
        }
    }
}

public class ProxyWrapper : IMyService, IDisposable
{
    private IMyService _proxy;
    private IDisposable _proxyDisposer;

    public ProxyWrapper(IMyService proxy, IDisposable disposable)
    {
        _proxy = proxy;
        _proxyDisposer = disposable;
    }

    public void DoWork()
    {
        _proxy.DoWork();
    }

    public void Dispose()
    {
        _proxyDisposer.Dispose();
    }
}

Berbekal kelas-kelas di atas sekarang kita bisa menulis

public class ServiceHelper
{
    private readonly ChannelFactory<IMyService> _channelFactory;

    public ServiceHelper(ChannelFactory<IMyService> channelFactory )
    {
        _channelFactory = channelFactory;
    }

    public IMyService CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return new ProxyWrapper(channel, channelDisposer);
    }
}

Ini memungkinkan kami untuk menggunakan layanan kami menggunakan usingblok:

ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Membuat ini generik

Yang kami lakukan sejauh ini adalah merumuskan kembali solusi Tomas . Apa yang mencegah kode ini menjadi generik adalah kenyataan bahwa ProxyWrapperkelas harus diimplementasikan kembali untuk setiap kontrak layanan yang kita inginkan. Kita sekarang akan melihat kelas yang memungkinkan kita untuk membuat tipe ini secara dinamis menggunakan IL:

public class ServiceHelper<T>
{
    private readonly ChannelFactory<T> _channelFactory;

    private static readonly Func<T, IDisposable, T> _channelCreator;

    static ServiceHelper()
    {
        /** 
         * Create a method that can be used generate the channel. 
         * This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
         * */
        var assemblyName = Guid.NewGuid().ToString();
        var an = new AssemblyName(assemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);

        var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));

        var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
            new[] { typeof(T), typeof(IDisposable) });

        var ilGen = channelCreatorMethod.GetILGenerator();
        var proxyVariable = ilGen.DeclareLocal(typeof(T));
        var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
        ilGen.Emit(OpCodes.Ldarg, proxyVariable);
        ilGen.Emit(OpCodes.Ldarg, disposableVariable);
        ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
        ilGen.Emit(OpCodes.Ret);

        _channelCreator =
            (Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));

    }

    public ServiceHelper(ChannelFactory<T> channelFactory)
    {
        _channelFactory = channelFactory;
    }

    public T CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return _channelCreator(channel, channelDisposer);
    }

   /**
    * Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
    * This method is actually more generic than this exact scenario.
    * */
    private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
    {
        TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
            TypeAttributes.Public | TypeAttributes.Class);

        var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
            tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));

        #region Constructor

        var constructorBuilder = tb.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName,
            CallingConventions.Standard,
            interfacesToInjectAndImplement);

        var il = constructorBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg, i);
            il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
        }
        il.Emit(OpCodes.Ret);

        #endregion

        #region Add Interface Implementations

        foreach (var type in interfacesToInjectAndImplement)
        {
            tb.AddInterfaceImplementation(type);
        }

        #endregion

        #region Implement Interfaces

        foreach (var type in interfacesToInjectAndImplement)
        {
            foreach (var method in type.GetMethods())
            {
                var methodBuilder = tb.DefineMethod(method.Name,
                    MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
                    MethodAttributes.Final | MethodAttributes.NewSlot,
                    method.ReturnType,
                    method.GetParameters().Select(p => p.ParameterType).ToArray());
                il = methodBuilder.GetILGenerator();

                if (method.ReturnType == typeof(void))
                {
                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);
                    il.Emit(OpCodes.Callvirt, method);
                    il.Emit(OpCodes.Ret);
                }
                else
                {
                    il.DeclareLocal(method.ReturnType);

                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);

                    var methodParameterInfos = method.GetParameters();
                    for (var i = 0; i < methodParameterInfos.Length; i++)
                        il.Emit(OpCodes.Ldarg, (i + 1));
                    il.Emit(OpCodes.Callvirt, method);

                    il.Emit(OpCodes.Stloc_0);
                    var defineLabel = il.DefineLabel();
                    il.Emit(OpCodes.Br_S, defineLabel);
                    il.MarkLabel(defineLabel);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);
                }

                tb.DefineMethodOverride(methodBuilder, method);
            }
        }

        #endregion

        return tb.CreateType();
    }
}

Dengan kelas pembantu baru kami, kami sekarang dapat menulis

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Perhatikan bahwa Anda juga bisa menggunakan teknik yang sama (dengan sedikit modifikasi) untuk klien yang dihasilkan secara otomatis untuk ClientBase<>(alih-alih menggunakan ChannelFactory<>), atau jika Anda ingin menggunakan implementasi berbeda IDisposableuntuk menutup saluran Anda.

Lawrence
sumber
2

Saya suka cara menutup koneksi ini:

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}
Uriil
sumber
1

Saya telah menulis kelas dasar sederhana yang menangani ini. Ini tersedia sebagai paket NuGet dan cukup mudah digunakan.

//MemberServiceClient is the class generated by SvcUtil
public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
{
    public User GetUser(int userId)
    {
        return PerformServiceOperation(client => client.GetUser(userId));
    }

    //you can also check if any error occured if you can't throw exceptions       
    public bool TryGetUser(int userId, out User user)
    {
        return TryPerformServiceOperation(c => c.GetUser(userId), out user);
    }
}
Ufuk Hacıoğulları
sumber
Adakah pembaruan untuk VS2013-.net 4.5.1? ada opsi untuk Coba lagi seperti stackoverflow.com/a/9370880/206730 ? -
Kiquenet
@Kiquenet Saya tidak bekerja di WCF lagi. Jika Anda mengirimi saya permintaan tarik, saya dapat menggabungkannya dan memperbarui paket.
Ufuk Hacıoğulları
1
public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

    public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
    {
        var proxy = (IClientChannel)ChannelFactory.CreateChannel();
        var success = false;
        try
        {
            var result = codeBlock((TChannel)proxy);
            proxy.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }
}

Jadi memungkinkan untuk menulis pernyataan pengembalian dengan baik:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 
Andriy Buday
sumber
1

Saya ingin menambahkan implementasi Layanan dari jawaban Marc Gravell untuk kasus menggunakan ServiceClient alih-alih ChannelFactory.

public interface IServiceConnector<out TServiceInterface>
{
    void Connect(Action<TServiceInterface> clientUsage);
    TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage);
}

internal class ServiceConnector<TService, TServiceInterface> : IServiceConnector<TServiceInterface>
    where TServiceInterface : class where TService : ClientBase<TServiceInterface>, TServiceInterface, new()
{
    public TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage)
    {
        var result = default(TResult);
        Connect(channel =>
        {
            result = channelUsage(channel);
        });
        return result;
    }

    public void Connect(Action<TServiceInterface> clientUsage)
    {
        if (clientUsage == null)
        {
            throw new ArgumentNullException("clientUsage");
        }
        var isChanneldClosed = false;
        var client = new TService();
        try
        {
            clientUsage(client);
            client.Close();
            isChanneldClosed = true;
        }
        finally
        {
            if (!isChanneldClosed)
            {
                client.Abort();
            }
        }
    }
}
PSsam
sumber
1

Bagi yang berminat, inilah terjemahan VB.NET dari jawaban yang diterima (di bawah). Saya telah memperbaikinya sedikit untuk singkatnya, menggabungkan beberapa tips oleh orang lain di utas ini.

Saya akui itu di luar topik untuk tag asal (C #), tetapi karena saya tidak dapat menemukan versi VB.NET dari solusi yang bagus ini, saya berasumsi bahwa orang lain juga akan mencari. Terjemahan Lambda bisa sedikit rumit, jadi saya ingin menyelamatkan seseorang dari masalah.

Perhatikan bahwa implementasi khusus ini menyediakan kemampuan untuk mengkonfigurasi ServiceEndpointsaat runtime.


Kode:

Namespace Service
  Public NotInheritable Class Disposable(Of T)
    Public Shared ChannelFactory As New ChannelFactory(Of T)(Service)

    Public Shared Sub Use(Execute As Action(Of T))
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Sub



    Public Shared Function Use(Of TResult)(Execute As Func(Of T, TResult)) As TResult
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Use = Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Function



    Public Shared ReadOnly Property Service As ServiceEndpoint
      Get
        Return New ServiceEndpoint(
          ContractDescription.GetContract(
            GetType(T),
            GetType(Action(Of T))),
          New BasicHttpBinding,
          New EndpointAddress(Utils.WcfUri.ToString))
      End Get
    End Property
  End Class
End Namespace

Pemakaian:

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Disposable(Of IService).Use(Sub(Client) Jobs = Client.GetJobs(Me.Status))
  End Get
End Property

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Return Disposable(Of IService).Use(Function(Client) Client.GetJobs(Me.Status))
  End Get
End Property
InteXX
sumber
1

Arsitektur sistem kami sering menggunakan Unity IOC kerangka kerja untuk membuat contoh dari ClientBase sehingga tidak ada cara yang pasti untuk menegakkan bahwa pengembang lain bahkan digunakanusing{} blok. Untuk menjadikannya sebagai bukti semudah mungkin, saya membuat kelas khusus ini yang memperpanjang ClientBase, dan menangani penutupan saluran pada saat pembuangan, atau pada penyelesaian jika seseorang tidak secara eksplisit membuang contoh yang dibuat Unity.

Ada juga hal-hal yang perlu dilakukan di konstruktor untuk mengatur saluran untuk kredensial khusus dan hal-hal lain, jadi itu juga ada di sini ...

public abstract class PFServer2ServerClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
{
    private bool disposed = false;

    public PFServer2ServerClientBase()
    {
        // Copy information from custom identity into credentials, and other channel setup...
    }

    ~PFServer2ServerClientBase()
    {
        this.Dispose(false);
    }

    void IDisposable.Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            try
            {
                    if (this.State == CommunicationState.Opened)
                        this.Close();
            }
            finally
            {
                if (this.State == CommunicationState.Faulted)
                    this.Abort();
            }
            this.disposed = true;
        }
    }
}

Maka klien dapat dengan mudah:

internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
{
    public string TestMethod(int value)
    {
        return base.Channel.TestMethod(value);
    }
}

Dan penelepon dapat melakukan semua ini:

public SomeClass
{
    [Dependency]
    public ITest test { get; set; }

    // Not the best, but should still work due to finalizer.
    public string Method1(int value)
    {
        return this.test.TestMethod(value);
    }

    // The good way to do it
    public string Method2(int value)
    {
        using(ITest t = unityContainer.Resolve<ITest>())
        {
            return t.TestMethod(value);
        }
    }
}
CodingWithSpike
sumber
Anda tidak pernah menggunakan parameter yang membuang dalam metode Buang Anda
CaffGeek
@Chad - Saya mengikuti pola desain Finalisasi / Buang umum Microsoft: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx Memang benar bahwa saya tidak menggunakan variabel, karena saya tidak perlu melakukan pembersihan yang berbeda antara pembuangan normal dan penyelesaian. Itu bisa ditulis ulang untuk hanya menyelesaikan Menyelesaikan panggilan Buang () dan memindahkan kode dari Buang (bool) ke Buang ().
CodingWithSpike
Finalizers menambah overhead, dan tidak deterministik. Saya menghindarinya sedapat mungkin. Anda dapat menggunakan pabrik otomatis Unity untuk menyuntikkan delegasi dan menempatkannya menggunakan blok, atau (lebih baik) menyembunyikan perilaku layanan buat / panggil / buang di balik metode pada antarmuka yang disuntikkan. Setiap panggilan ke ketergantungan membuat proksi, memanggilnya, dan membuangnya.
TrueWill
0

Saya merujuk beberapa jawaban pada posting ini dan menyesuaikannya sesuai kebutuhan saya.

Saya ingin kemampuan untuk melakukan sesuatu dengan klien WCF sebelum menggunakannya jadi DoSomethingWithClient()metode.

public interface IServiceClientFactory<T>
{
    T DoSomethingWithClient();
}
public partial class ServiceClient : IServiceClientFactory<ServiceClient>
{
    public ServiceClient DoSomethingWithClient()
    {
        var client = this;
        // do somthing here as set client credentials, etc.
        //client.ClientCredentials = ... ;
        return client;
    }
}

Inilah kelas pembantu:

public static class Service<TClient>
    where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
{
    public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
    {
        TClient client = default(TClient);
        bool success = false;
        try
        {
            client = new TClient().DoSomethingWithClient();
            TReturn result = codeBlock(client);
            client.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success && client != null)
            {
                client.Abort();
            }
        }
    }
}

Dan saya bisa menggunakannya sebagai:

string data = Service<ServiceClient>.Use(x => x.GetData(7));
hIpPy
sumber
Bagaimana dengan konstruktor Klien menggunakan binding dan endpoing? TClient (binding, endpoing)
Kiquenet
0

Saya memiliki pembungkus sendiri untuk saluran yang mengimplementasikan Buang sebagai berikut:

public void Dispose()
{
        try
        {
            if (channel.State == CommunicationState.Faulted)
            {
                channel.Abort();
            }
            else
            {
                channel.Close();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
}

Ini tampaknya bekerja dengan baik dan memungkinkan blok yang digunakan untuk digunakan.

Joe
sumber
0

Pembantu berikut memungkinkan untuk memanggil voiddan metode yang tidak berlaku. Pemakaian:

var calculator = new WcfInvoker<CalculatorClient>(() => new CalculatorClient());
var sum = calculator.Invoke(c => c.Sum(42, 42));
calculator.Invoke(c => c.RebootComputer());

Kelas itu sendiri adalah:

public class WcfInvoker<TService>
    where TService : ICommunicationObject
{
    readonly Func<TService> _clientFactory;

    public WcfInvoker(Func<TService> clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public T Invoke<T>(Func<TService, T> action)
    {
        var client = _clientFactory();
        try
        {
            var result = action(client);
            client.Close();
            return result;
        }
        catch
        {
            client.Abort();
            throw;
        }
    }

    public void Invoke(Action<TService> action)
    {
        Invoke<object>(client =>
        {
            action(client);
            return null;
        });
    }
}
Konstantin Spirin
sumber
0

Mengesampingkan Buang klien () tanpa perlu membuat kelas proxy berdasarkan ClientBase, juga tanpa perlu mengelola pembuatan saluran dan caching ! (Perhatikan bahwa WcfClient bukan kelas ABSTRAK dan didasarkan pada ClientBase)

// No need for a generated proxy class
//using (WcfClient<IOrderService> orderService = new WcfClient<IOrderService>())
//{
//    results = orderService.GetProxy().PlaceOrder(input);
//}

public class WcfClient<TService> : ClientBase<TService>, IDisposable
    where TService : class
{
    public WcfClient()
    {
    }

    public WcfClient(string endpointConfigurationName) :
        base(endpointConfigurationName)
    {
    }

    public WcfClient(string endpointConfigurationName, string remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    protected virtual void OnDispose()
    {
        bool success = false;

        if ((base.Channel as IClientChannel) != null)
        {
            try
            {
                if ((base.Channel as IClientChannel).State != CommunicationState.Faulted)
                {
                    (base.Channel as IClientChannel).Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                {
                    (base.Channel as IClientChannel).Abort();
                }
            }
        }
    }

    public TService GetProxy()
    {
        return this.Channel as TService;
    }

    public void Dispose()
    {
        OnDispose();
    }
}
Murad Duraidi
sumber
0

Metode saya melakukan ini adalah untuk membuat kelas yang diwarisi yang secara eksplisit mengimplementasikan IDisposable. Ini berguna untuk orang-orang yang menggunakan gui untuk menambahkan referensi layanan (Tambah Referensi Layanan). Saya hanya membuang kelas ini di proyek yang membuat referensi layanan dan menggunakannya sebagai ganti klien default:

using System;
using System.ServiceModel;
using MyApp.MyService; // The name you gave the service namespace

namespace MyApp.Helpers.Services
{
    public class MyServiceClientSafe : MyServiceClient, IDisposable
    {
        void IDisposable.Dispose()
        {
            if (State == CommunicationState.Faulted)
            {
                Abort();
            }
            else if (State != CommunicationState.Closed)
            {
                Close();
            }

            // Further error checks and disposal logic as desired..
        }
    }
}

Catatan: Ini hanyalah implementasi sederhana dari buang, Anda dapat menerapkan logika buang yang lebih kompleks jika Anda mau.

Anda kemudian dapat mengganti semua panggilan yang dilakukan dengan klien layanan reguler dengan klien aman, seperti ini:

using (MyServiceClientSafe client = new MyServiceClientSafe())
{
    var result = client.MyServiceMethod();
}

Saya suka solusi ini karena tidak mengharuskan saya untuk memiliki akses ke definisi Antarmuka dan saya dapat menggunakan usingpernyataan seperti yang saya harapkan sambil membiarkan kode saya terlihat kurang lebih sama.

Anda masih perlu menangani pengecualian yang dapat dilontarkan seperti ditunjukkan dalam komentar lain di utas ini.

Aleksandr Albert
sumber
-2

Anda juga bisa menggunakan a DynamicProxyuntuk memperluas Dispose()metode. Dengan cara ini Anda dapat melakukan sesuatu seperti:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Uri Abramson
sumber