WCF istemcisi `` block`` engelleme sorunu için en iyi geçici çözüm nedir?


404

WCF hizmet istemcileri bir usingblok içinde oluşturmayı seviyorum, çünkü bu, kaynakları uygulayan standart bir yöntemdir IDisposable:

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

Ancak, bu MSDN makalesinde belirtildiği gibi , bir WCF istemcisini bir usingbloğa sarmak , istemcinin hatalı durumda (zaman aşımı veya iletişim sorunu gibi) kalmasına neden olan hataları maskeleyebilir. Uzun hikaye kısa, Dispose () çağrıldığında, istemcinin Close () yöntemi tetiklenir, ancak hatalı durumda olduğu için bir hata atar. Orijinal istisna daha sonra ikinci istisna tarafından maskelenir. İyi değil.

MSDN makalesinde önerilen geçici çözüm, bir usingblok kullanmaktan tamamen kaçınmak ve bunun yerine müşterilerinizi somutlaştırmak ve onlara böyle bir şey kullanmaktır:

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

Kıyasladığımızda usingBlok ile , bu çirkin olduğunu düşünüyorum. Ve bir istemciye ihtiyacınız olduğunda yazmak için bir sürü kod.

Neyse ki, IServiceOriented üzerinde bunun gibi birkaç geçici çözüm buldum. Şununla başlıyorsunuz:

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

Hangi sonra sağlar:

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

Bu kötü değil, ama usingblok kadar etkileyici ve kolay anlaşılabilir olduğunu sanmıyorum .

Şu anda kullanmaya çalıştığım geçici çözüm ilk olarak blog.davidbarret.net adresinde okudum . Temel olarak, Dispose()nerede kullanırsanız kullanın istemcinin yöntemini geçersiz kılar . Gibi bir şey:

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

Bu izin verebilir gibi görünüyor using , arızalı bir durum istisnasını maskeleme tehlikesi olmadan bloğa tekrar .

Peki, bu geçici çözümleri kullanmak için dikkat etmem gereken başka varlıklar var mı? Daha iyi bir şey bulan var mı?


42
Sonuncusu (bunu inceleyen devlet) bir yarış; boole'i ​​kontrol ettiğinizde hatalı olmayabilir, ancak Close () öğesini çağırdığınızda hatalı olabilir.
Brian

15
Durumu okudunuz; hatalı değil. Close () öğesini aramadan önce kanal arızalanır. Close () atar. Oyun bitti.
Brian

4
Zaman geçer. Çok kısa bir süre olabilir, ancak teknik olarak, kanalın durumunu kontrol etmek ve kapanmasını istemek arasındaki sürede kanalın durumu değişebilir.
Eric King

8
Bunun Action<T>yerine kullanırdım UseServiceDelegate<T>. minör.
hIpPy

2
Bu statik yardımcıyı gerçekten sevmiyorum Service<T>çünkü ünite testini zorlaştırıyor (çoğu statik şey gibi). Kullanan sınıfa enjekte edilebilir böylece statik olmayan olmasını tercih ederim.
Fabio Marreco

Yanıtlar:


137

Ben her ne kadar Aslında blogged (bkz Luke'un cevabı ), bence bu daha iyi benim ıdisposable sarıcı daha olduğunu. Tipik kod:

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

(yorum başına düzenle)

Yana Usedöner hükümsüz, dönen değerleri işlemek için en kolay yolu yakalanan değişken yoluyladır:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

2
@MarcGravell Bu müşteriyi nereye enjekte edebilirim? ChannelFactory istemci oluşturduğunu ve fabrika nesnesi Service sınıfı içinde yeni olduğunu varsayalım, bu da kodun özel bir fabrikaya izin vermek için biraz yeniden düzenlenmesi gerektiği anlamına gelir. Bu doğru mu, yoksa burada bariz bir şeyi mi kaçırıyorum?
Anttu

16
Sonuç için bir yakalama değişkenine gerek duymamak için sarmalayıcıyı kolayca değiştirebilirsiniz. Böyle bir şey: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris

3
Belki yararlı https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/ ve https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/ ve http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón

Bu yolu kullanarak nasıl kimlik bilgisi ekleyebilirim?
23:15

2
Bence en doğru çözüm: 1) Bir yarış durumu olmadan Kapat / Durdur desenini gerçekleştirin 2) Servis işlemi istisnalar attığında durumu ele alın 3) Hem Kapat hem de İptal yöntemlerinin istisnalar attığı durumları ele alın 4) Kolu ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

88

IServiceOriented.com tarafından savunulan çözüm ile David Barret'in blogu tarafından savunulan çözüm arasında bir seçim yapıldığında , müşterinin Dispose () yöntemini geçersiz kılarak sunulan basitliği tercih ederim. Bu, tek kullanımlık bir nesneden beklendiği gibi using () ifadesini kullanmaya devam etmemi sağlar. Bununla birlikte, @Brian'ın işaret ettiği gibi, bu çözüm, Devlet kontrol edildiğinde hatayla karşılaşılmayabileceği, ancak Close () çağrıldığında başarısız olabileceği bir yarış koşulu içerir, bu durumda CommunicationException hala oluşur.

Bu yüzden, bunun üstesinden gelmek için her iki dünyanın en iyilerini karıştıran bir çözüm kullandım.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}

2
'Try-Last' (veya sözdizimsel şeker - "using () {}") ifadesini yönetilmeyen kaynaklarla kullanmak riskli değil mi? Bu durumda, "Kapat" seçeneği başarısız olursa istisna yakalanmaz ve son olarak çalışmayabilir. Ayrıca, nihayet deyiminde bir istisna varsa, diğer istisnaları maskeleyebilir. Bence Try-Catch bu yüzden tercih ediliyor.
Zack Jannsen

Zack, nesneniz üzerinde net değil; Neyi kaçırıyorum? Close yöntemi bir istisna atarsa, son durum bloğu istisna atılmadan önce yürütülür. Sağ?
Patrick Szalapski

1
@jmoreno, düzenlemenizi çözdüm. Dikkat ederseniz, yöntemde hiç catch bloğu yoktur. Fikir, (nihayetinde bile) meydana gelen herhangi bir istisna atılmalı, sessizce yakalanmamalıdır.
Matt Davis

5
@MattDavis Neden successbayrağa ihtiyacınız var ? Neden olmasın try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin

Denemeye / yakalamaya ne dersiniz Close(); success = true;? Sonunda başarıyla iptal edebilirsem bir istisna atılmasını istemezdim. Yalnızca bu durumda Abort () başarısız olursa istisna fırlatmak isterim. Bu şekilde, try / catch olası yarış koşulu istisnasını gizler ve yine de son blokta bağlantıyı kesmenize () izin verir.
goku_da_master

32

Daha düzgün çalışmasını sağlamak için daha üst düzey bir işlev yazdım . Bunu birkaç projede kullandık ve harika çalışıyor gibi görünüyor. "Kullanmak" paradigması vb. Olmadan işlerin en baştan böyle yapılması gerekirdi.

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

Bunun gibi arama yapabilirsiniz:

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

Bu, tıpkı örneğinizde olduğu gibi. Bazı projelerde, kuvvetle yazılan yardımcı yöntemler yazıyoruz, bu yüzden "Wcf.UseFooService (f => f ...)" gibi şeyler yazıyoruz.

Her şeyi düşündüğümde oldukça zarif buluyorum. Karşılaştığınız belirli bir sorun var mı?

Bu, diğer şık özelliklerin takılmasına izin verir. Örneğin, bir sitede site, oturum açmış olan kullanıcı adına hizmetin kimliğini doğrular. (Sitenin kendisi için herhangi bir kimlik bilgisi yoktur.) Kendi "UseService" yöntem yardımcısını yazarak, kanal fabrikasını istediğimiz şekilde yapılandırabiliriz, vb. .


İstisna alıyorum: ChannelFactory.Endpoint adres özelliği null. ChannelFactory'nin Bitiş Noktası'nda geçerli bir Adres belirtilmelidir . Nedir GetCachedFactoryyöntemi?
Marshall

28

Microsoft'un WCF istemci çağrılarını işlemek için önerilen yolu budur:

Daha fazla ayrıntı için bkz. Beklenen İstisnalar

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

Ek bilgi Pek çok kişi WCF'de bu soruyu soruyor gibi görünüyor, Microsoft'un istisnaları nasıl ele alacağını göstermek için özel bir örnek bile oluşturdu:

c: \ WF_WCF_Samples \ WCF \ Temel \ Client \ ExpectedExceptions \ CS \ istemci

Örneği indirin: C # veya VB

Pek çok sorunlar olduğunu düşünürsek kullanarak ifadeyi içeren , (ısıtmalı?) İç tartışmaları ve konuları bu konuda, ben bir kod kovboy olmak ve daha temiz bir yol bulmaya çalışırken zaman zaman israf edecek değilim. Ben sadece emmek ve sunucu uygulamaları için bu ayrıntılı (henüz güvenilir) şekilde WCF istemcileri uygulamak.

Opsiyonel Ek Arızalar

Birçok istisnadan kaynaklanır CommunicationExceptionve bu istisnaların çoğunun yeniden denenmesi gerektiğini düşünmüyorum. MSDN'deki her bir istisnadan vazgeçtim ve yeniden deneme özel durumlarının kısa bir listesini buldum ( TimeOutExceptionyukarıdakilere ek olarak ). Yeniden denenmesi gereken bir istisnayı kaçırıp kaçırmadığımı bana bildirin.

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

Kuşkusuz, bu yazmak için biraz sıradan kod. Şu anda bu yanıtı tercih ediyorum ve bu kodda yolda sorunlara neden olabilecek herhangi bir "hack" görmüyorum.


1
Örnekteki kod hala sorun yaratıyor mu? UsingUsing projesini (VS2013) çalıştırmayı denedim ama ile hat "Hope this code wasn't important, because it might not happen."hala yürütülüyor ...
janv8000

14

Sonunda bu soruna temiz bir çözüm yolunda bazı sağlam adımlar buldum.

Bu özel araç, bir istisna işleme proxy'si sağlamak için WCFProxyGenerator'ü genişletir. ExceptionHandlingProxy<T>Hangi devralındığı denir ExceptionHandlingProxyBase<T>- ikincisi proxy'nin işlevselliğinin etini uygulayan ek bir proxy oluşturur . Sonuç, kanal fabrikasının ve kanalının ömrünü yönetmeyi devralan ClientBase<T>veya ExceptionHandlingProxy<T>içine alan varsayılan proxy'yi kullanmayı seçebilmenizdir . ExceptionHandlingProxy, Eşzamansız yöntemler ve toplama türlerine göre Hizmet Başvurusu Ekle iletişim kutusundaki seçimlerinize saygı duyar.

Codeplex , WCF Proxy Generator adlı Özel Durum İşleme adlı bir projeye sahiptir . Temel olarak Visual Studio 2008'e yeni bir özel araç yükler, ardından yeni hizmet proxy'sini oluşturmak için bu aracı kullanın (Hizmet başvurusu ekle) . Arızalı kanallar, zaman aşımları ve güvenli imha ile başa çıkmak için bazı güzel işlevlere sahiptir. Burada ExceptionHandlingProxyWrapper adında, bunun nasıl çalıştığını açıklayan mükemmel bir video var .

Usingİfadeyi güvenli bir şekilde tekrar kullanabilirsiniz ve kanal herhangi bir istekte başarısız olursa (TimeoutException veya CommunicationException), Wrapper hatalı kanalı yeniden başlatacak ve sorguyu yeniden deneyecektir. Bu başarısız olursa, Abort()komutu çağırır ve proxy'yi imha eder ve İstisna'yı yeniden oluşturur. Hizmet bir FaultExceptionkod atarsa , yürütmeyi durdurur ve proxy beklendiği gibi doğru özel durumu atarak güvenli bir şekilde iptal edilir.


@Shimmy Durum Beta. Tarih: 11 Tem 2009 , Michele Bustamante . Ölü Proje?
Kiquenet

11

Marc Gravell, MichaelGG ve Matt Davis'in cevaplarına dayanarak, geliştiricilerimiz aşağıdakileri ortaya çıkardı:

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

Kullanım örneği:

string result = string.Empty;

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

Mümkün olduğunca "kullanma" sözdizimine yakındır, bir void yöntemini çağırırken kukla bir değer döndürmeniz gerekmez ve tuples kullanmak zorunda kalmadan hizmete birden çok çağrı yapabilirsiniz (ve birden çok değer döndürebilirsiniz).

Ayrıca, bunu isterseniz ClientBase<T>ChannelFactory yerine torunlarla da kullanabilirsiniz .

Bir geliştirici bunun yerine bir proxy / kanalı el ile atmak istiyorsa uzantı yöntemi ortaya çıkar.


PoolingDuplex kullanıyorsam ve bir çağrıdan sonra bağlantıyı kapatmazsam, bu yüzden müşteri hizmetlerim birkaç gün bile yaşayabilir ve sunucu geri çağrılarını işleyebilirse bu mantıklıdır. Burada tartışılan çözümü anladığım kadarıyla oturum başına bir çağrı için mantıklı geliyor?
sll

@sll - bu, çağrı döndükten hemen sonra bağlantıyı kapatmak içindir (oturum başına bir çağrı).
TrueWill

@cacho DisposeSafelyÖzel yapmak kesinlikle bir seçenektir ve karışıklıktan kaçınır. Birinin doğrudan aramak isteyebileceği kullanım durumları olabilir, ancak bir hazırlıksız gelemem.
TrueWill

@ sadece belge için bu yöntemin iş parçacığı açısından güvenli olduğunu belirtmek önemlidir.
Cacho Santa

1
Bence en doğru çözüm: 1) Bir yarış durumu olmadan Kapat / Durdur desenini gerçekleştirin 2) Servis işlemi istisnalar attığında durumu ele alın 3) Hem Kapat hem de İptal yöntemlerinin istisnalar attığı durumları ele alın 4) Kolu ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

8

@Marc Gravell

Bunu kullanmak uygun olmaz mı:

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

Ya da, aynı şey (Func<T, TResult>)söz konusuService<IOrderService>.Use

Bunlar geri dönen değişkenleri kolaylaştırır.


2
+1 @MarcGravell Sanırım cevabınız 'daha iyisini yapabilirdi': P (ve eylem, geri dönüşü olan bir Func açısından uygulanabilir). Bu sayfanın tamamı bir karmaşa - bu on yılda herhangi bir zamanda WCF kullanarak öngördüğümde birleşik bir formüle ve
dups hakkında yorum yapardı

7

Bu nedir?

Bu, kabul edilen yanıtın CW sürümüdür, ancak (tam olarak düşündüğüm) İstisna işleme dahil.

Kabul edilen cevap , artık çevrede olmayan bu web sitesine başvuruyor . Sana beladan kurtulmak için en alakalı parçaları buraya ekliyorum. Buna ek olarak, bu sinir bozucu ağ zaman aşımlarını işlemek için özel durum yeniden deneme işleme içerecek şekilde biraz değiştirdim .

Basit WCF İstemci Kullanımı

İstemci tarafı proxy'nizi oluşturduktan sonra, bunu uygulamanız yeterlidir.

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

ServiceDelegate.cs

Bu dosyayı çözümünüze ekleyin. Yeniden deneme sayısını veya işlem yapmak istediğiniz istisnaları değiştirmek istemiyorsanız, bu dosyada değişiklik yapmanız gerekmez.

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: Bu gönderiyi bir topluluk wiki'si haline getirdim. Bu cevaptan "puan" toplamayacağım, ancak uygulamayı kabul ediyorsanız veya daha iyi hale getirmek için düzenlerseniz oyunuzu onaylamanızı tercih ederim.


Bu cevabı tanımlamanıza katıldığımdan emin değilim. O ile CW versiyonu da istisna işleme fikrinden ekledi.
John Saunders

@JohnSaunders - True (istisna yönetimi kavramım). Eksik olduğum veya yanlış işlediğim istisnaları bana bildirin.
goodguys_activate

Başarı değişkeni ne olacak? Kaynak koduna eklenmesi gerekir: if (success) return; ??
Kiquenet

İlk çağrı atar ve 2. başarılı olursa mostRecentEx null olmaz, bu nedenle 5 başarısız yeniden istisna yine de başarısız. yoksa bir şey mi kaçırıyorum? Eğer 2, 3, 4 veya 5. denemede başarılı olduysanız en iyiyi nereden temizlediğinizi göremiyorum. Ayrıca başarılı bir dönüş görmüyorum. Burada bir şey eksik olmalı, ancak hiçbir istisna atılırsa bu kod her zaman 5 kez çalışmaz?
Bart Calixto

@Bart - Ben ekledi success == falsenihai eğer deyimi ile
goodguys_activate

7

Aşağıda, kaynağın sorunun gelişmiş bir sürümü bulunmaktadır ve çok kanallı fabrikaları önbelleğe almak ve yapılandırma dosyasındaki bitiş noktasını sözleşme adıyla aramaya çalışmak için genişletilmiştir.

.NET 4 kullanır (özellikle: kontravaryans, 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.");
    }
}

1
Neden kullanmak UseServiceDelegate<T>yerine Action<T>?
Mike Mayer

1
Orijinal yazarın bunu yaptığını düşünebilmemin tek nedeni, geliştiricinin bir hizmeti çağırmaya ait olduğunu bildiği güçlü bir delege sahip olmaktı. Ama görebildiğim kadarıyla Action<T>çalışıyor.
Jesse C. Slicer

5

Böyle bir sargı işe yarayacaktır:

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

Bu, aşağıdaki gibi bir kod yazmanızı sağlayacaktır:

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

Sarıcı elbette gerekirse daha fazla istisna yakalayabilir, ancak ilke aynı kalır.


Bertarafın belirli koşullar altında çağrılmamasıyla ilgili tartışmayı hatırlıyorum ... WCF ile bellek sızıntısına neden oluyor.
goodguys_activate

Bellek sızıntılarına neden olduğundan emin değilim ama sorun bu. Bir Disposekanalda çağrı yaptığınızda , kanal hatalı durumda ise bir istisna fırlatabilir, bu Microsoft'un Disposeasla atmaması gerektiğini belirttiği için bir sorundur . Dolayısıyla, yukarıdaki kodun yaptığı şey, Closebir istisna atarken durumu ele almaktır. Eğer Abortatar ciddi bir sorun olabilir. Bu konuda geçen Aralık ayında bir blog yazısı yazdım: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson

4

Dispose () sorununu çözmek için Castle dynamic proxy'yi kullandım ve ayrıca kullanılamadığında kanalın otomatik olarak yenilenmesini uyguladım. Bunu kullanmak için, servis sözleşmenizi ve IDisposable'ı devralan yeni bir arayüz oluşturmalısınız. Dinamik proxy bu arabirimi uygular ve bir WCF kanalını sarar:

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

WCF'nin herhangi bir ayrıntısı için endişelenmeye ihtiyaç duymadan WCF hizmetlerini enjekte edebileceğiniz için bunu beğendim. Ve diğer çözümler gibi ek bir hammadde yoktur.

Koda bir göz atın, aslında oldukça basit: WCF Dinamik Proxy


4

Bir uzantı yöntemi kullanın:

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

4

IoC'ye ihtiyacınız yoksa veya otomatik olarak oluşturulmuş bir istemci (Hizmet Başvurusu) kullanıyorsanız, kapatmayı yönetmek için basit bir sarıcı kullanabilirsiniz ve GC'nin herhangi bir istisna atmayacak güvenli bir durumda olduğunda istemci tabanını almasına izin verebilirsiniz. GC, serviceclient'te Dispose olarak adlandırır ve bu çağırır Close. Tamamen kapalı olduğundan herhangi bir hasara neden olamaz. Bunu üretim kodunda problemsiz kullanıyorum.

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

Sonra sunucuya eriştiğinizde, istemciyi oluşturur ve usingautodisconect'te kullanırsınız:

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

    Ws.Open();

    Ws.Test();
}

3

özet

Bu cevapta açıklanan teknikler kullanılarak, bir kullanıcı bloğunda aşağıdaki sözdizimine sahip bir WCF hizmeti tüketilebilir:

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

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

Durumunuza özgü daha özlü bir programlama modeli elde etmek için elbette bunu daha da adapte edebilirsiniz - ancak mesele, IMyServicetek kullanımlık modeli doğru şekilde uygulayan kanalı yeniden canlandırmanın bir uygulamasını oluşturabilmemizdir .


ayrıntılar

Şimdiye kadar verilen tüm cevaplar, WCF Kanalı uygulamasında "hata" dolaşımı sorununu ele almaktadır IDisposable. (Kullanmak için izin en özlü programlama modeli sunmak gibi görünüyor cevap usingyönetilmeyen kaynakları üzerinde imha etme blok) 'dir bu bir vekil uygulamaya modifed edilir - IDisposablebir hata içermeyen uygulamasıyla. Bu yaklaşımdaki sorun sürdürülebilirliktir - kullandığımız proxy'ler için bu işlevi yeniden uygulamalıyız. Bu cevabın bir varyasyonunda, bu tekniği jenerik hale getirmek için kalıtım yerine kompozisyonu nasıl kullanabileceğimizi göreceğiz .

İlk girişim

Uygulama için çeşitli uygulamalar var gibi görünüyor IDisposable, ancak tartışma uğruna şu anda kabul edilen cevap tarafından kullanılanın bir uyarlamasını kullanacağız .

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

Yukarıdaki sınıflarla silahlanmış şimdi yazabiliriz

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

Bu, bloğumuzu kullanarak hizmetimizi tüketmemizi sağlar using:

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

Bunu genel yapmak

Şimdiye kadar yaptığımız tek şey Tomas'ın çözümünü yeniden formüle etmek . Bu kodun genel olmasını engelleyen şey, ProxyWrappersınıfın istediğimiz her hizmet sözleşmesi için yeniden uygulanması gerektiğidir. Şimdi IL'yi kullanarak bu türü dinamik olarak oluşturmamızı sağlayan bir sınıfa bakacağız:

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

Yeni yardımcı sınıfımızla artık

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

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

Otomatik olarak oluşturulan istemciler için ClientBase<>(kullanmak yerine ChannelFactory<>) devralan veya IDisposablekanalınızı kapatmak için farklı bir uygulama kullanmak istiyorsanız aynı tekniği (küçük değişikliklerle) de kullanabileceğinizi unutmayın.


2

Bu bağlantıyı kapatma şeklini seviyorum:

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

1

Bunu ele alan basit bir temel sınıf yazdım . Bir NuGet paketi olarak mevcuttur ve kullanımı oldukça kolaydır.

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

VS2013-.net 4.5.1 için herhangi bir güncelleme var mı? stackoverflow.com/a/9370880/206730 gibi Yeniden Dene için herhangi bir seçenek var mı? -
Kiquenet

@Kiquenet Artık WCF üzerinde çalışmıyorum. Bana bir çekme isteği gönderirseniz, birleştirebilir ve paketi güncelleyebilirim.
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();
            }
        }
    }
}

Böylece iade ifadelerini güzelce yazmanıza izin verir:

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

1

ChannelFactory yerine ServiceClient kullanımı durumunda Marc Gravell'in yanıtından Hizmet uygulamasını eklemek istiyorum .

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

1

İlgilenenler için, kabul edilen cevabın bir VB.NET çevirisi (aşağıda). Ben bu ipucu diğerleri tarafından bazı ipuçlarını birleştirerek kısalık için biraz rafine ettik.

Ben kaynak etiketleri (C #) için konu dışı itiraf, ama ben bu ince çözüm bir VB.NET sürümünü bulamadık gibi ben diğerleri de bakacağını varsayalım. Lambda çevirisi biraz zor olabilir, bu yüzden birisini beladan kurtarmak istiyorum.

Bu özel uygulamanın ServiceEndpointçalışma zamanında yapılandırma yeteneği sağladığını unutmayın .


Kod:

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

Kullanımı:

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

1

Sistem mimarimiz , ClientBase örnekleri oluşturmak için genellikle Unity IoC çerçevesini kullanır, bu nedenle diğer geliştiricilerin using{}blokları bile kullanmasını zorlamanın kesin bir yolu yoktur . Mümkün olduğunca kusursuz geçirmez hale getirmek için, ClientBase'i genişleten ve birisinin açıkça Unity oluşturulan örneğini atmadığı durumda kanalı kapatmayı veya sonlandırmayı ele alan bu özel sınıfı yaptım.

Özel kimlik bilgileri ve şeyler için kanalı ayarlamak üzere kurucuda yapılması gereken şeyler de var, bu yüzden burada da ...

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

Sonra bir istemci basitçe:

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

Ve arayan bunlardan herhangi birini yapabilir:

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

Dispose yönteminizde parametre
atamasını

@Chad - Microsoft'un ortak Sonlandırma / Atma tasarım desenini izliyordum: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx Değişkeni kullanmıyorum, çünkü yok normal bir atma ve bir sonlandırma arasında farklı bir temizlik yapmanıza gerek yoktur. Yalnızca Dispose çağrısını sonlandır () ve kodu Dispose (bool) öğesinden Dispose () öğesine taşımak için yeniden yazılabilir.
16'da Kodlama İle

Sonlandırıcılar ek yük ekler ve deterministik değildir. Mümkün olduğunca onlardan kaçınırım. Delegeleri enjekte etmek ve blokları kullanmak için Unity'nin otomatik fabrikalarını kullanabilir veya (daha iyi) oluşturma / arama / atma hizmeti davranışını enjekte edilmiş bir arabirimdeki bir yöntemin arkasına gizleyebilirsiniz. Bağımlılığa yapılan her çağrı proxy'yi oluşturur, çağırır ve çöpe atar.
TrueWill

0

Bu gönderiye birkaç cevap verdim ve ihtiyaçlarıma göre özelleştirdim.

Bu yüzden DoSomethingWithClient()yöntemi kullanmadan önce WCF istemcisi ile bir şeyler yapma yeteneği istedim .

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

İşte yardımcı sınıf:

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

Ve şu şekilde kullanabilirsiniz:

string data = Service<ServiceClient>.Use(x => x.GetData(7));

Bağlayıcı ve uç nokta kullanarak istemci yapıcısı nedir? TClient (ciltleme, bitiş)
Kiquenet

0

Aşağıdaki gibi imha uygulayan bir kanal için kendi sarmalayıcı var:

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

Bu iyi çalışıyor gibi görünüyor ve bir kullanım bloğunun kullanılmasına izin veriyor.


0

Aşağıdaki yardımcı çağrı voidve geçersiz yöntemlere izin verir . Kullanımı:

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

Sınıfın kendisi:

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

0

İstemci'nin Dispose () yöntemini ClientBase tabanlı bir proxy sınıfı oluşturmaya gerek kalmadan, ayrıca kanal oluşturma ve önbelleğe almayı yönetmeye gerek kalmadan geçersiz kılın ! (WcfClient'in bir ABSTRACT sınıfı olmadığını ve ClientBase'i temel aldığını unutmayın)

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

0

Bunu yapma yöntemim açıkça IDisposable uygulayan kalıtsal bir sınıf oluşturmak oldu. Bu, hizmet referansını eklemek için gui'yi kullanan kişiler için yararlıdır (Servis Referansı Ekle). Ben sadece servis başvurusu yaparak projeye bırakın ve varsayılan istemci yerine kullanın:

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

Not: Bu basit bir imha uygulamasıdır, isterseniz daha karmaşık imha mantığı uygulayabilirsiniz.

Daha sonra, normal servis istemcisiyle yapılan tüm çağrılarınızı aşağıdaki gibi güvenli istemcilerle değiştirebilirsiniz:

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

Arayüz tanımlarına erişmemi gerektirmediği için bu çözümü beğendim ve kodumun usingaşağı yukarı aynı görünmesine izin verirken beklediğim gibi ifadeyi kullanabilirim .

Bu konudaki diğer yorumlarda belirtildiği gibi atılabilecek istisnaları yine de ele almanız gerekecektir.


-2

Yöntemi DynamicProxygenişletmek için de a kullanabilirsiniz Dispose(). Bu şekilde şöyle bir şey yapabilirsiniz:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
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.