Qual é a melhor solução alternativa para o cliente WCF `usando` problema de bloco?

404

Gosto de instanciar meus clientes de serviço WCF dentro de um usingbloco, já que é praticamente a maneira padrão de usar recursos que implementam IDisposable:

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

Mas, conforme observado neste artigo do MSDN , agrupar um cliente WCF em um usingbloco pode mascarar quaisquer erros que resultem no cliente sendo deixado em um estado com falha (como um tempo limite ou um problema de comunicação). Para encurtar a história, quando Dispose () é chamado, o método Close () do cliente é acionado, mas gera um erro porque está em um estado com falha. A exceção original é mascarada pela segunda exceção. Não é bom.

A solução sugerida no artigo do MSDN é evitar completamente o uso de um usingbloco e instanciar seus clientes e usá-los da seguinte maneira:

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

Comparado ao usingbloco, acho isso feio. E muito código para escrever sempre que você precisar de um cliente.

Felizmente, encontrei algumas outras soluções alternativas, como esta no IServiceOriented. Você começa com:

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

O que permite:

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

Isso não é ruim, mas não acho que seja tão expressivo e facilmente compreensível quanto o usingbloco.

A solução alternativa que estou tentando usar foi a que li pela primeira vez no blog.davidbarret.net . Basicamente, você substitui o Dispose()método do cliente onde quer que o use. Algo como:

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

Parece ser capaz de permitir o usingbloco novamente sem o perigo de mascarar uma exceção de estado com falha.

Então, existem outras dicas que eu devo procurar ao usar essas soluções alternativas? Alguém sugeriu algo melhor?

Eric King
fonte
42
O último (que inspeciona isso. Estado) é uma corrida; pode não estar com defeito quando você verifica o booleano, mas pode estar com defeito quando você chama Close ().
Brian
15
Você lê estado; não é culpado. Antes de ligar para Close (), o canal falha. Fechar () joga. Fim de jogo.
9789 Brian
4
O tempo passa. Pode ser um período muito curto, mas tecnicamente, no período entre verificar o estado do canal e solicitar que ele feche, o estado do canal pode mudar.
Eric King
8
Eu usaria em Action<T>vez de UseServiceDelegate<T>. menor.
Hippy
2
Eu realmente não gosto desse auxiliar estático, Service<T>pois complica o teste de unidade (como a maioria das coisas estáticas). Eu preferiria que não fosse estático, para poder ser injetado na classe que o está usando.
Fabio Marreco 16/10

Respostas:

137

Na verdade, embora eu tenha blogado (veja a resposta de Luke ), acho que isso é melhor do que meu invólucro descartável. Código típico:

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

(editar por comentários)

Como os Useretornos são nulos, a maneira mais fácil de lidar com os valores de retorno é através de uma variável capturada:

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
fonte
2
@ MarcGravell Onde eu poderia injetar esse cliente? Suponho que o ChannelFactory crie o cliente e o objeto factory seja novo dentro da classe Service, o que significa que o código deve ser refatorado um pouco para permitir uma fábrica personalizada. Isso está correto ou estou perdendo algo óbvio aqui?
Anttu 21/03/12
16
Você pode modificar facilmente o wrapper para não precisar de uma variável de captura para o resultado. Algo como isto: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris
3
Talvez útil https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/ e https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/ e http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón
Como posso adicionar credenciais usando dessa maneira?
Hippasus
2
Na minha opinião, a solução mais correta seria: 1) Executar o padrão Fechar / Abortar sem uma condição de corrida 2) Lidar com a situação em que a operação de serviço lança exceções 3) Lidar com as situações em que os métodos Fechar e Anular lançam exceções 4) Lidar com exceções assíncronas, como o ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet
88

Dada a escolha entre a solução preconizada pelo IServiceOriented.com e a solução preconizada pelo blog de David Barret , prefiro a simplicidade oferecida pela substituição do método Dispose () do cliente. Isso me permite continuar usando a instrução using () como seria de esperar de um objeto descartável. No entanto, como @Brian apontou, esta solução contém uma condição de corrida, na qual o Estado pode não sofrer falhas quando é verificado, mas pode ser no momento em que Close () é chamado. Nesse caso, a CommunicationException ainda ocorre.

Então, para contornar isso, empreguei uma solução que combina o melhor dos dois mundos.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
Matt Davis
fonte
2
não é arriscado usar a instrução 'Try-Finalmente' (ou o açúcar sintático - "using () {}") com recursos não gerenciados? Caso em questão, se a opção "Fechar" falhar, a exceção não será detectada e, finalmente, poderá não ser executada. Além disso, se houver uma exceção na instrução finalmente, ela poderá mascarar outras exceções. Eu acho que é por isso que o Try-Catch é o preferido.
Zack Jannsen
Zack, não está claro em seu objeto; o que estou perdendo? Se o método Close lançar uma exceção, o bloco final será executado antes que a exceção seja lançada. Direita?
Patrick Szalapski
11
@jmoreno, eu desfiz sua edição. Se você perceber, não há nenhum bloco de captura no método. A idéia é que qualquer exceção que ocorra (mesmo que finalmente) seja lançada, não capturada silenciosamente.
Matt Davis
5
@MattDavis Por que você precisa de successsinalizador? Por que não try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin
Que tal colocar um try / catch ao redor Close(); success = true;? Eu não gostaria que fosse lançada uma exceção se pudesse abortá-la com sucesso no bloco finalmente. Eu só iria querer uma exceção lançada se o Abort () falhasse nesse caso. Dessa forma, a tentativa / captura ocultaria a exceção potencial de condição de corrida e ainda permitiria que você abortasse () a conexão no bloco final.
Goku_da_master 4/16
32

Eu escrevi uma função de ordem superior para fazê-la funcionar corretamente. Usamos isso em vários projetos e parece funcionar muito bem. É assim que as coisas deveriam ter sido feitas desde o início, sem o paradigma "usar" ou mais.

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

Você pode fazer chamadas assim:

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

Isso é praticamente como você tem no seu exemplo. Em alguns projetos, escrevemos métodos auxiliares de tipo forte, por isso acabamos escrevendo coisas como "Wcf.UseFooService (f => f ...)".

Acho bastante elegante, considerando todas as coisas. Você encontrou algum problema em particular?

Isso permite que outros recursos bacanas sejam conectados. Por exemplo, em um site, o site é autenticado no serviço em nome do usuário conectado. (O site não possui credenciais por si só.) Ao escrever nosso próprio auxiliar de método "UseService", podemos configurar a fábrica de canais da maneira que queremos, etc. Também não somos obrigados a usar os proxies gerados - qualquer interface serve .

MichaelGG
fonte
Estou recebendo a exceção: a propriedade Address no ChannelFactory.Endpoint era nula. O terminal do ChannelFactory deve ter um endereço válido especificado . O que é GetCachedFactorymétodo?
Marshall
28

Esta é a maneira recomendada pela Microsoft de lidar com chamadas de clientes WCF:

Para obter mais detalhes, consulte: Exceções esperadas.

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

Informações adicionais Muitas pessoas parecem estar fazendo essa pergunta no WCF que a Microsoft até criou um exemplo dedicado para demonstrar como lidar com exceções:

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

Baixe o exemplo: C # ou VB

Considerando que existem muitos problemas envolvendo a instrução using , (aquecida?) Discussões e tópicos internos sobre esse assunto, não vou perder meu tempo tentando me tornar um cowboy de código e encontrar uma maneira mais limpa. Vou apenas absorver e implementar clientes WCF desta maneira detalhada (ainda que confiável) para meus aplicativos de servidor.

Falhas adicionais opcionais na captura

Muitas exceções derivam CommunicationExceptione não acho que a maioria dessas exceções deva ser tentada novamente. Examinei cada exceção no MSDN e encontrei uma pequena lista de exceções de nova tentativa (além das TimeOutExceptionanteriores). Informe-me se perdi uma exceção que deve ser repetida.

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

É certo que este é um pouco de código mundano para escrever. No momento, prefiro essa resposta e não vejo nenhum "hacks" nesse código que possa causar problemas no futuro.

goodguys_activate
fonte
11
O código do exemplo ainda está causando problemas? Eu tentei executar o projeto UsingUsing (VS2013), mas a linha com "Hope this code wasn't important, because it might not happen."ainda é executado ...
janv8000
14

Finalmente encontrei alguns passos sólidos em direção a uma solução limpa para esse problema.

Essa ferramenta personalizada estende o WCFProxyGenerator para fornecer um proxy de manipulação de exceção. Ele gera um proxy adicional chamado ExceptionHandlingProxy<T>herda ExceptionHandlingProxyBase<T>- o último implementa a funcionalidade da funcionalidade do proxy. O resultado é que você pode optar por usar o proxy padrão que herda ClientBase<T>ou ExceptionHandlingProxy<T>encapsula o gerenciamento da vida útil da fábrica e do canal. O ExceptionHandlingProxy respeita suas seleções na caixa de diálogo Adicionar referência de serviço em relação aos métodos assíncronos e tipos de coleção.

O Codeplex possui um projeto chamado Exception Handling WCF Proxy Generator . Basicamente, instala uma nova ferramenta personalizada no Visual Studio 2008 e, em seguida, use essa ferramenta para gerar o novo proxy de serviço (Adicionar referência de serviço) . Possui algumas funcionalidades interessantes para lidar com canais com falha, tempos limite e descarte seguro. Há um excelente vídeo aqui chamado ExceptionHandlingProxyWrapper explicando exatamente como isso funciona.

Você pode usar a Usinginstrução novamente com segurança e, se o canal estiver com falha em qualquer solicitação (TimeoutException ou CommunicationException), o Wrapper reinicializará o canal com falha e tentará novamente a consulta. Se isso falhar, ele chamará o Abort()comando e descartará o proxy e novamente a exceção. Se o serviço lançar um FaultExceptioncódigo, ele será interrompido e o proxy será abortado com segurança, lançando a exceção correta conforme o esperado.

Neil
fonte
@Shimmy Status Beta. Data: sábado, 11 de julho de 2009 por Michele Bustamante . Projeto morto?
Kiquenet
11

Com base nas respostas de Marc Gravell, MichaelGG e Matt Davis, nossos desenvolvedores apresentaram o seguinte:

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

Exemplo de uso:

string result = string.Empty;

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

É o mais próximo possível da sintaxe "using", você não precisa retornar um valor fictício ao chamar um método nulo e pode fazer várias chamadas ao serviço (e retornar vários valores) sem precisar usar tuplas.

Além disso, você pode usar isso com ClientBase<T>descendentes em vez de ChannelFactory, se desejar.

O método de extensão é exposto se um desenvolvedor desejar descartar manualmente um proxy / canal.

TrueWill
fonte
Usar isso faz sentido se eu estiver usando o PoolingDuplex e não fechar a conexão após uma chamada, para que meu serviço ao cliente viva alguns dias e lide com retornos de chamada do servidor. Tanto quanto eu entendo a solução discutida aqui faz sentido para uma chamada por sessão?
SLL
@sll - serve para fechar a conexão imediatamente após o retorno da chamada (uma chamada por sessão).
TrueWill 13/11/2012
@cacho Tornar DisposeSafelyprivado é certamente uma opção e evitaria confusão. Pode haver casos de uso em que alguém queira chamá-lo diretamente, mas não posso inventar um de imediato.
TrueWill
@truewill apenas para documentação, também é importante mencionar que esse método é seguro para threads, certo?
Cacho Santa
11
Na minha opinião, a solução mais correta seria: 1) Executar o padrão Fechar / Abortar sem uma condição de corrida 2) Lidar com a situação em que a operação de serviço lança exceções 3) Lidar com as situações em que os métodos Fechar e Anular lançam exceções 4) Lidar com exceções assíncronas, como o ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet
8

@Marc Gravell

Não seria bom usar isso:

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

Ou, a mesma coisa (Func<T, TResult>)no caso deService<IOrderService>.Use

Isso facilitaria o retorno de variáveis.

retangular
fonte
2
+1 @MarcGravell Acho que sua resposta 'poderia fazer melhor' também: P (e a ação que pode ser implementada em termos de um Func com retorno nulo). Esta página inteira é uma bagunça - eu iria formular uma unificado e comentar sobre dups se eu previsto usando WCF qualquer momento desta década ...
Ruben Bartelink
7

O que é isso?

Esta é a versão CW da resposta aceita, mas com (o que eu considero completo) Tratamento de exceção incluído.

A resposta aceita faz referência a este site que não existe mais . Para evitar problemas, incluo aqui as partes mais relevantes. Além disso, eu o modifiquei um pouco para incluir o tratamento de novas tentativas de exceção para lidar com esses tempos limite de rede irritantes.

Uso simples do cliente WCF

Depois de gerar seu proxy do lado do cliente, é tudo o que você precisa para implementá-lo.

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

ServiceDelegate.cs

Adicione este arquivo à sua solução. Não são necessárias alterações neste arquivo, a menos que você queira alterar o número de tentativas ou quais exceções você deseja manipular.

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: Eu fiz deste post um wiki da comunidade. Não coletarei "pontos" desta resposta, mas prefiro que você a vote novamente se concordar com a implementação ou edite-a para torná-la melhor.

LamonteCristo
fonte
Não tenho certeza se concordo com a sua caracterização desta resposta. É a versão CW com a sua idéia de tratamento de exceções adicionada.
John Saunders
@ JohnSaunders - True (meu conceito de manipulação de exceções). Informe-me de todas as exceções que estão faltando ou que estou lidando mal.
21712
O que é variável de sucesso? Ele precisa ser adicionado ao código-fonte: if (success) return; ??
Kanjet
Se a primeira chamada for lançada e a segunda for bem-sucedida, o MostRecentEx não será nulo; portanto, você está lançando uma exceção que falhou em 5 tentativas de qualquer maneira. Ou eu estou esquecendo de alguma coisa? Não vejo onde você limpa o MostRecentEx se em uma segunda, terceira, quarta ou quinta tentativa tiver sido bem-sucedida. Também não vejo retorno de sucesso. Eu deveria estar faltando alguma coisa aqui, mas esse código não será executado sempre 5 vezes se nenhuma exceção for lançada?
Bart Calixto
@ Bart - eu adicionei success == falseà declaração if final
goodguys_activate
7

Abaixo está uma versão aprimorada da fonte da pergunta e estendida para armazenar em cache várias fábricas de canais e tentar procurar o terminal no arquivo de configuração pelo nome do contrato.

Ele usa o .NET 4 (especificamente: contravariância, 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
fonte
11
Por que usar em UseServiceDelegate<T>vez de Action<T>?
Mike Mayer
11
A única razão pela qual posso pensar que o autor original o fez foi ter um delegado fortemente tipado que o desenvolvedor saberia pertencer a chamar um serviço. Mas, tanto quanto eu posso ver, Action<T>funciona tão bem.
Jesse C. Slicer
5

Um wrapper como este funcionaria:

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

Isso deve permitir que você escreva códigos como:

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

Obviamente, o wrapper pode capturar mais exceções, se necessário, mas o princípio permanece o mesmo.

Tomas Jansson
fonte
Lembro-me da discussão sobre Dispose não ser chamada sob certas condições ... resultando em um vazamento de memória w / WCF.
goodguys_activate
Não sei se isso resultou em vazamentos de memória, mas o problema é esse. Quando você chama Disposeum IChannel, ele pode gerar uma exceção se o canal estiver em um estado com falha, isso é um problema, pois a Microsoft especifica que Disposenunca deve ser lançado. Portanto, o que o código acima faz é lidar com o caso quando Closelança uma exceção. Se Abortjoga, pode ser algo seriamente errado. Eu escrevi um post sobre isso em dezembro passado: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson
4

Usei o proxy dinâmico do Castle para resolver o problema Dispose () e também implementei a atualização automática do canal quando ele estava em um estado inutilizável. Para usar isso, você deve criar uma nova interface que herda seu contrato de serviço e o IDisposable. O proxy dinâmico implementa essa interface e agrupa um canal WCF:

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

Gosto disso, pois você pode injetar serviços WCF sem que os consumidores precisem se preocupar com detalhes do WCF. E não há nenhum acréscimo adicionado como as outras soluções.

Dê uma olhada no código, é realmente muito simples: WCF Dynamic Proxy

Jay Douglass
fonte
4

Use um método de extensão:

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
fonte
4

Se você não precisar de IoC ou estiver usando um cliente gerado automaticamente (Referência de Serviço), poderá usar um wrapper para gerenciar o fechamento e deixar o GC aproveitar a base de clientes quando estiver em um estado seguro que não gerará nenhuma exceção. O GC chamará Dispose no serviço técnico, e isso chamará Close. Uma vez que já está fechado, não pode causar nenhum dano. Estou usando isso sem problemas no código de produção.

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

Então, quando você estiver acessando o servidor, crie o cliente e use usingno autodisconect:

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

    Ws.Open();

    Ws.Test();
}
Luiz Felipe
fonte
3

Sumário

Usando as técnicas descritas nesta resposta, é possível consumir um serviço WCF em um bloco using com a seguinte sintaxe:

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

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

É claro que você pode adaptar isso ainda mais para obter um modelo de programação mais conciso específico para sua situação - mas o ponto é que podemos criar uma implementação de IMyServicereprenting do canal que implementa corretamente o padrão descartável.


Detalhes

Todas as respostas dadas até agora abordam o problema de contornar o "bug" na implementação do canal WCF de IDisposable. A resposta que parece oferecer o modelo de programação mais conciso (permitindo que você use o usingbloco para dispor de recursos não gerenciados) é esta - onde o proxy é modificado para implementar IDisposablecom uma implementação sem erros. O problema com essa abordagem é a manutenção - precisamos reimplementar essa funcionalidade para sempre usar o proxy. Em uma variação dessa resposta, veremos como podemos usar a composição, em vez da herança, para tornar essa técnica genérica.

Primeira tentativa

Parece haver várias implementações para a IDisposableimplementação, mas por uma questão de argumento, usaremos uma adaptação daquela usada pela resposta atualmente aceita .

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

Armado com as classes acima, agora podemos escrever

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

Isso nos permite consumir nosso serviço usando o usingbloco:

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

Tornando este genérico

Tudo o que fizemos até agora é reformular a solução de Tomas . O que impede que esse código seja genérico é o fato de que a ProxyWrapperclasse precisa ser reimplementada para cada contrato de serviço que desejamos. Agora, veremos uma classe que nos permite criar esse tipo dinamicamente usando 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();
    }
}

Com nossa nova classe auxiliar, agora podemos escrever

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

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

Observe que você também pode usar a mesma técnica (com pequenas modificações) para clientes gerados automaticamente que herdam ClientBase<>(em vez de usar ChannelFactory<>) ou se quiser usar uma implementação diferente IDisposablepara fechar seu canal.

Lawrence
fonte
2

Eu gosto desta maneira de fechar a conexão:

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

Eu escrevi uma classe base simples que lida com isso. Está disponível como um pacote NuGet e é bastante fácil de usar.

//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ı
fonte
Alguma atualização para VS2013-.net 4.5.1? alguma opção para Repetir, como stackoverflow.com/a/9370880/206730 ? -
Kiquenet
@Kiquenet Não estou mais trabalhando no WCF. Se você me enviar uma solicitação pull, posso mesclá-la e atualizar o pacote.
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();
            }
        }
    }
}

Por isso, permite escrever instruções de retorno bem:

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

Eu gostaria de adicionar a implementação do Service da resposta de Marc Gravell para o caso de usar o ServiceClient em vez do 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
fonte
1

Para os interessados, aqui está uma tradução em VB.NET da resposta aceita (abaixo). Refinei-o um pouco por questões de concisão, combinando algumas dicas de outras pessoas neste segmento.

Admito que não seja o tópico para as tags de origem (C #), mas como não consegui encontrar uma versão VB.NET dessa boa solução, presumo que outras pessoas também estejam procurando. A tradução do Lambda pode ser um pouco complicada, então eu gostaria de salvar alguém do problema.

Observe que essa implementação específica fornece a capacidade de configurar o ServiceEndpointem tempo de execução.


Código:

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

Uso:

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
fonte
1

Nossa arquitetura do sistema geralmente usa a estrutura do Unity IoC para criar instâncias do ClientBase, portanto não há como garantir que os outros desenvolvedores usem using{}blocos. Para torná-lo o mais à prova de idiotas possível, criei essa classe personalizada que estende o ClientBase e lida com o fechamento do canal ao dispor ou ao finalizar caso alguém não descarte explicitamente a instância criada pelo Unity.

Também há coisas que precisam ser feitas no construtor para configurar o canal para credenciais personalizadas e outras coisas, então isso também está aqui ...

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

Então um cliente pode simplesmente:

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

E o chamador pode fazer qualquer um destes:

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
fonte
Você nunca fazer uso do parâmetro de eliminação no seu método de Descarte
CaffGeek
@Chad - Eu estava seguindo o padrão de design Finalize / Dispose da Microsoft: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx É verdade que não estou usando a variável, porque não não é necessário fazer nenhuma limpeza diferente entre uma eliminação normal e uma finalização. Pode ser reescrito para que a chamada Finalize Dispose () seja finalizada e mova o código de Dispose (bool) para Dispose ().
CodingWithSpike
Os finalizadores aumentam a sobrecarga e não são determinísticos. Eu os evito sempre que possível. Você pode usar as fábricas automáticas do Unity para injetar delegados e colocá-las em blocos, ou (melhor) ocultar o comportamento do serviço de criação / chamada / descarte atrás de um método em uma interface injetada. Cada chamada para a dependência cria o proxy, chama e descarta.
TrueWill 24/08/12
0

Consultei algumas respostas neste post e o personalizei de acordo com minhas necessidades.

Eu queria a capacidade de fazer algo com o cliente WCF antes de usá-lo para o DoSomethingWithClient()método.

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

Aqui está a classe auxiliar:

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

E eu posso usá-lo como:

string data = Service<ServiceClient>.Use(x => x.GetData(7));
hIpPy
fonte
O que há no construtor Client usando vinculação e endpoing? TClient (encadernação, endpoing)
Kiquenet 29/10
0

Eu tenho meu próprio wrapper para um canal que implementa Dispose da seguinte maneira:

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

Isso parece funcionar bem e permite que um bloco usando seja usado.

Joe
fonte
0

O ajudante a seguir permite chamar voidmétodos não nulos. Uso:

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

A classe em si é:

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
fonte
0

Substitua Dispose () do cliente sem a necessidade de gerar uma classe de proxy baseada no ClientBase, também sem a necessidade de gerenciar a criação e o cache do canal ! (Observe que WcfClient não é uma classe ABSTRACT e se baseia no 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
fonte
0

Meu método para fazer isso foi criar uma classe herdada que implemente explicitamente IDisposable. Isso é útil para pessoas que usam a GUI para adicionar a referência de serviço (Adicionar referência de serviço). Acabei de soltar essa classe no projeto que faz a referência do serviço e a uso em vez do cliente padrão:

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

Nota: Esta é apenas uma implementação simples de descarte; você pode implementar uma lógica de descarte mais complexa, se desejar.

Em seguida, você pode substituir todas as chamadas feitas pelo cliente de serviço regular pelos clientes seguros, desta forma:

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

Gosto dessa solução, pois ela não exige que eu tenha acesso às definições de interface e posso usar a usinginstrução como seria de esperar, permitindo que meu código parecesse mais ou menos o mesmo.

Você ainda precisará lidar com as exceções que podem ser lançadas conforme indicado em outros comentários neste tópico.

Aleksandr Albert
fonte
-2

Você também pode usar a DynamicProxypara estender o Dispose()método. Dessa forma, você pode fazer algo como:

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