WCF 클라이언트`사용`블록 문제에 대한 가장 좋은 해결 방법은 무엇입니까?


404

using구현하는 리소스를 사용하는 표준 방법과 거의 비슷하기 때문에 블록 내에서 WCF 서비스 클라이언트를 인스턴스화 하는 것이 좋습니다 IDisposable.

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

그러나이 MSDN 기사 에서 언급 한 것처럼 WCF 클라이언트를 using블록 에 래핑 하면 클라이언트가 오류 상태 (시간 초과 또는 통신 문제 등)로 남아있는 오류를 숨길 수 있습니다. 간단히 말해 Dispose ()가 호출되면 클라이언트의 Close () 메서드가 실행되지만 오류가 발생한 상태에서 오류가 발생합니다. 그런 다음 원래 예외는 두 번째 예외에 의해 가려집니다. 안좋다.

MSDN 기사에서 제안 된 해결 방법은 using블록 사용을 완전히 피하고 대신 클라이언트를 인스턴스화하고 다음과 같이 사용하는 것입니다.

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

using블록과 비교하면 추악하다고 생각합니다. 그리고 클라이언트가 필요할 때마다 쓸 많은 코드가 있습니다.

운 좋게도 IServiceOriented에서 이와 같은 몇 가지 해결 방법을 찾았습니다. 당신은 시작합니다 :

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

그러면 다음이 가능합니다.

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

나쁘지는 않지만 using블록 처럼 표현적이고 쉽게 이해할 수 있다고 생각하지 않습니다 .

현재 사용하려는 해결 방법은 blog.davidbarret.net 에서 처음 읽은 것입니다 . 기본적으로 클라이언트의 Dispose()메소드를 사용하는 모든 위치 에서 대체합니다 . 다음과 같은 것 :

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

이는 using결함이있는 상태 예외를 마스킹 할 위험없이 블록을 다시 허용 할 수있는 것으로 보입니다 .

따라서이 해결 방법을 사용할 때 고려해야 할 다른 문제가 있습니까? 아무도 더 나은 것을 생각해 냈습니까?


42
마지막 것은 (this.State를 검사) 경주입니다. 부울을 확인할 때 오류가 발생하지는 않지만 Close ()를 호출하면 오류가 발생할 수 있습니다.
Brian

15
당신은 상태를 읽습니다; 결함이 없습니다. Close ()를 호출하기 전에 채널에 결함이 있습니다. Close ()가 발생합니다. 게임 끝.
Brian

4
시간이 흐릅니다. 매우 짧은 시간 일 수 있지만 기술적으로는 채널 상태를 확인하고 닫을 것을 요청하는 시간 사이에 채널 상태가 변경 될 수 있습니다.
Eric King

8
Action<T>대신에 사용하고 싶습니다 UseServiceDelegate<T>. 미성년자.
hiPy

2
나는이 정적 도우미를 좋아하지 않습니다. Service<T>(대부분의 정적 작업과 마찬가지로) 단위 테스트가 복잡하기 때문입니다. 정적이 아닌 것을 선호하므로 그것을 사용하는 클래스에 주입 할 수 있습니다.
Fabio Marreco

답변:


137

실제로 블로그를 작성 했지만 ( Luke의 답변 참조 ) 이것이 IDisposable wrapper보다 낫다고 생각 합니다 . 전형적인 코드 :

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

(주석 별 편집)

Use리턴 값이 void 이므로 리턴 값을 처리하는 가장 쉬운 방법은 캡처 된 변수를 사용하는 것입니다.

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 어디서 그 클라이언트를 주입 할 수 있습니까? ChannelFactory가 클라이언트를 생성하고 팩토리 객체가 Service 클래스에 새로 추가되었다고 가정합니다. 즉, 사용자 정의 팩토리를 허용하려면 코드를 약간 리팩터링해야합니다. 이것이 맞습니까? 아니면 여기에 분명한 것이 빠져 있습니까?
Anttu

16
래퍼를 쉽게 수정할 수 있으므로 결과에 캡처 변수가 필요하지 않습니다. 이와 같은 것 : public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris

3
아마 유용 https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón

이 방법으로 자격 증명을 추가하려면 어떻게해야합니까?
Hippasus

2
내 생각에 가장 올바른 해결책은 다음과 같습니다. 1) 경쟁 조건없이 Close / Abort 패턴 수행 2) 서비스 작업에서 예외가 발생하는 상황 처리 3) Close 및 Abort 메서드가 모두 예외를 발생시키는 상황 처리 4) 처리 ThreadAbortException과 같은 비동기 예외 https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

88

IServiceOriented.com에 의해 옹호 된 솔루션과 David Barret의 블로그에 의해 옹호 된 솔루션 중 하나를 선택 하면 클라이언트의 Dispose () 메서드를 재정 의하여 제공되는 단순성을 선호합니다. 이를 통해 일회용 객체에 예상되는 것처럼 using () 문을 계속 사용할 수 있습니다. 그러나 @Brian이 지적 했듯이이 솔루션에는 상태를 확인할 때 오류가 발생하지 않지만 Close ()가 호출 될 때까지 통신 예외가 발생한다는 경쟁 조건이 포함되어 있습니다.

그래서이 문제를 해결하기 위해 두 세계를 모두 혼합 한 솔루션을 사용했습니다.

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

2
관리되지 않는 리소스와 함께 'Try-Finally'(또는 구문 설탕- "using () {}") 문을 사용하는 것이 위험하지 않습니까? 예를 들어, "닫기"옵션이 실패하면 예외가 발생하지 않으며 결국 실행되지 않을 수 있습니다. 또한 finally 문에 예외가 있으면 다른 예외를 숨길 수 있습니다. 그것이 Try-Catch가 선호되는 이유라고 생각합니다.
잭 잔슨

잭, 당신의 물건에 명확하지 않다; 내가 뭘 놓치고 있니? Close 메서드가 예외를 throw하면 예외가 발생하기 전에 finally 블록이 실행됩니다. 권리?
Patrick Szalapski

1
@jmoreno, 편집 내용을 알지 못했습니다. 알다시피, 메소드에는 catch 블록이 전혀 없습니다. 아이디어는 발생 하는 예외 (마지막으로도)가 자동으로 발생하지 않고 발생해야한다는 것입니다.
매트 데이비스

5
@MattDavis 왜 success플래그가 필요한가요? 왜 안돼 try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin

시도 / 캐치를 배치하는 것은 Close(); success = true;어떻습니까? finally 블록에서 예외를 성공적으로 중단 할 수 있으면 예외가 발생하지 않도록합니다. 이 경우 Abort ()가 실패한 경우에만 예외를 throw하고 싶습니다. 이런 식으로 try / catch는 잠재적 인 경쟁 조건 예외를 숨기고 finally 블록에서 연결을 중단 () 할 수있게합니다.
goku_da_master 17 년

32

내가 쓴 고차 기능 이 잘 작동하도록. 우리는 이것을 여러 프로젝트에서 사용했으며 훌륭하게 작동하는 것 같습니다. 이것은 "사용"패러다임 등없이 처음부터 일을해야했던 방식입니다.

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

다음과 같이 전화를 걸 수 있습니다.

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

이것은 당신의 예에서와 거의 같습니다. 일부 프로젝트에서는 강력한 형식의 도우미 메서드를 작성하므로 "Wcf.UseFooService (f => f ...)"와 같은 항목을 작성하게됩니다.

나는 모든 것이 고려 된 것이 매우 우아하다는 것을 알았습니다. 발생한 특정 문제가 있습니까?

이를 통해 다른 유용한 기능을 연결할 수 있습니다. 예를 들어 한 사이트에서 사이트는 로그인 한 사용자 대신 서비스를 인증합니다. (사이트 자체에는 자격 증명이 없습니다.) 자체 "UseService"메서드 도우미를 작성하여 원하는 방식으로 채널 팩토리를 구성 할 수 있습니다. 또한 생성 된 프록시를 사용할 수 없습니다. .


예외가 발생합니다. ChannelFactory.Endpoint의 Address 속성이 null입니다. ChannelFactory의 엔드 포인트는 유효한 Address를 지정해야합니다 . GetCachedFactory방법 은 무엇입니까 ?
Marshall

28

WCF 클라이언트 호출을 처리하기 위해 Microsoft에서 권장하는 방법입니다.

자세한 내용은 예상 예외를 참조하십시오.

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

추가 정보 많은 사람들이 WCF에 대해이 질문을하는 것처럼 보이며 Microsoft는 예외 처리 방법을 보여주기 위해 전용 샘플을 만들었습니다.

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

샘플 다운로드 : C # 또는 VB

using statement , (heated?) 이 문제에 대한 내부 토론스레드관련된 많은 문제가 있다는 것을 고려할 때 코드 카우보이가되고 더 깨끗한 방법을 찾는 데 시간을 낭비하지 않을 것입니다. 난 그냥 그것을 빨아 내 서버 응용 프로그램에 대한이 자세한 (아직 신뢰할 수있는) 방법으로 WCF 클라이언트를 구현합니다.

선택적인 추가 실패

많은 예외가 파생되어 CommunicationException대부분의 예외를 재 시도해야한다고 생각하지 않습니다. MSDN에서 각 예외를 겪고 재시도 가능한 예외 목록을 발견했습니다 ( TimeOutException위에 추가 ). 재 시도해야 할 예외를 놓친 경우 알려주십시오.

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

분명히 이것은 쓸만한 평범한 코드입니다. 나는 현재이 답변을 선호 하며, 코드에서 길을 잃을 수도있는 "해킹"을 보지 못한다.


1
샘플의 코드로 여전히 문제가 발생합니까? UsingUsing 프로젝트 (VS2013)를 실행하려고했지만 라인 "Hope this code wasn't important, because it might not happen."이 계속 실행됩니다.
janv8000

14

마침내이 문제에 대한 깨끗한 해결책을 향한 확실한 단계를 발견했습니다.

이 사용자 정의 도구는 WCFProxyGenerator를 확장하여 예외 처리 프록시를 제공합니다. ExceptionHandlingProxy<T>상속 되는 추가 프록시를 생성합니다 ExceptionHandlingProxyBase<T>. 후자는 프록시 기능의 고기를 구현합니다. 결과적으로 채널 팩토리 및 채널의 수명 관리 를 상속 ClientBase<T>하거나 ExceptionHandlingProxy<T>캡슐화하는 기본 프록시를 사용하도록 선택할 수 있습니다 . ExceptionHandlingProxy는 비동기 메소드 및 콜렉션 유형과 관련하여 서비스 참조 추가 대화 상자에서 선택한 사항을 존중합니다.

Codeplex 에는 Exception Handling WCF Proxy Generator 프로젝트가 있습니다. 기본적으로 Visual Studio 2008에 새 사용자 지정 도구를 설치 한 다음이 도구를 사용하여 새 서비스 프록시를 생성합니다 (서비스 참조 추가) . 고장난 채널, 타임 아웃 및 안전한 폐기를 처리 할 수있는 훌륭한 기능이 있습니다. 여기 ExceptionHandlingProxyWrapper 라는 훌륭한 비디오가 있는데 이것이 어떻게 작동하는지 정확하게 설명합니다.

Using명령문을 안전하게 다시 사용할 수 있으며 요청에 대해 채널에 결함이있는 경우 (TimeoutException 또는 CommunicationException) 랩퍼는 결함이있는 채널을 다시 초기화하고 쿼리를 재 시도합니다. 실패하면 Abort()명령 을 호출 하고 프록시를 처리하고 예외를 다시 발생시킵니다. 서비스가 FaultException코드를 던지면 실행이 중지되고 프록시가 예상대로 올바른 예외를 안전하게 throw합니다.


@Shimmy Status 베타. 날짜 : 토 2009년 7월 11일 에 의해 미셸 부스타 . 죽은 프로젝트?
Kiquenet

11

Marc Gravell, MichaelGG 및 Matt Davis의 답변을 바탕으로 개발자는 다음을 수행했습니다.

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

사용 예 :

string result = string.Empty;

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

가능한 "사용"구문에 가깝고 void 메소드를 호출 할 때 더미 값을 반환 할 필요가 없으며 튜플을 사용하지 않고도 서비스를 여러 번 호출하고 여러 값을 반환 할 수 있습니다.

또한 ClientBase<T>원하는 경우 ChannelFactory 대신 하위 항목 과 함께 사용할 수 있습니다 .

개발자가 대신 프록시 / 채널을 수동으로 폐기하려는 경우 확장 방법이 노출됩니다.


PoolingDuplex를 사용 중이고 통화 후 연결을 닫지 않으면 클라이언트 서비스가 며칠 동안 살면서 서버 콜백을 처리 할 수 ​​있습니다. 여기에서 논의 된 솔루션을 이해하는 한 세션 당 한 번의 호출에 적합합니까?
sll

@sll-이것은 호출이 반환 된 직후 (세션 당 하나의 호출) 연결을 종료하기위한 것입니다.
TrueWill

@cacho DisposeSafely비공개로 만드는 것은 확실히 옵션이며 혼란을 피할 것입니다. 누군가가 직접 전화를 걸고 싶은 유스 케이스가있을 수 있지만 한 가지 손을 내놓을 수는 없습니다.
TrueWill

@true는 문서화를 위해서만이 방법이 스레드로부터 안전하다는 것을 언급하는 것이 중요합니까?
Cacho Santa

1
내 의견으로는 가장 올바른 해결책은 다음과 같습니다. 1) 경쟁 조건없이 Close / Abort 패턴 수행 2) 서비스 작업에서 예외가 발생하는 상황 처리 3) Close 및 Abort 메서드가 모두 예외를 발생시키는 상황 처리 4) 처리 ThreadAbortException과 같은 비동기 예외 https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

8

@ 마크 그 라벨

이것을 사용하는 것은 좋지 않습니까?

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

또는 같은 (Func<T, TResult>)경우Service<IOrderService>.Use

이렇게하면 변수를 쉽게 반환 할 수 있습니다.


2
+1 @MarcGravell 나는 당신의 대답이 '더 좋을 수 있습니다'라고 생각합니다 : P (그리고 액션은 널 리턴으로 Func의 관점에서 구현 될 수 있습니다). 이 전체 페이지는 엉망입니다. 10 년 중 언제라도 WCF를 사용하여 구상하면 통일 된 문서를 작성하고 딥에 대해 언급 할 것입니다.
Ruben Bartelink

7

이게 뭐야?

이것은 허용 된 답변의 CW 버전이지만 예외 처리가 포함 된 (완전한 것으로 간주되는) 것입니다.

허용 된 답변 은 더 이상 존재하지 않는이 웹 사이트를 참조 합니다 . 문제를 해결하기 위해 가장 관련성이 높은 부분을 여기에 포함 시켰습니다. 또한 예외 재시도 처리 를 포함하도록 약간 수정했습니다. 이러한 성가신 네트워크 시간 초과를 하기 위해 를 .

간단한 WCF 클라이언트 사용법

클라이언트 측 프록시를 생성하면 이것이 프록시 구현에 필요한 전부입니다.

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

ServiceDelegate.cs

이 파일을 솔루션에 추가하십시오. 재시도 횟수 또는 처리하려는 예외를 변경하지 않는 한이 파일을 변경할 필요가 없습니다.

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

    }
}

추신 : 저는이 글을 커뮤니티 위키로 만들었습니다. 이 답변에서 "점"을 수집하지는 않지만 구현에 동의하는 경우 투표하거나 더 나은 결과를 얻으려면 편집하십시오.


이 답변의 특성에 동의하지 않습니다. 예외 처리대한 아이디어가 추가 된 CW 버전입니다 .
John Saunders

@JohnSaunders-True (제 예외 처리 개념). 내가 빠졌거나 잘못 다루고있는 예외를 알려주십시오.
goodguys_activate

성공 변수는 무엇입니까? 소스 코드에 추가해야합니다. if (성공) return; ??
Kiquenet

첫 번째 호출이 발생하고 두 번째 호출이 성공하면 mostRecentEx는 null이 아니므로 5 번의 재 시도에 실패한 예외가 발생합니다. 아니면 뭔가 빠졌습니까? 두 번째, 세 번째, 네 번째 또는 다섯 번째 시도에서 성공한 경우 mostRecentEx를 지우는 위치를 알 수 없습니다. 또한 성공을 보지 못합니다. 여기에 뭔가 빠져 있어야하지만 예외가 발생하지 않으면이 코드가 항상 5 번 실행되지는 않습니까?
바트 칼리 크토

@Bart-추가 success == false 마지막 if 문에
goodguys_activate

7

아래는 질문 에서 소스의 향상된 버전입니다 여러 채널 팩토리를 캐시하고 계약 이름으로 구성 파일에서 엔드 포인트를 검색하도록 확장되었습니다.

.NET 4를 사용합니다 (구체적으로 분산, 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
UseServiceDelegate<T>대신에 사용 Action<T>합니까?
Mike Mayer

1
내가 원저자가 그렇게했다고 생각할 수있는 유일한 이유는 개발자가 알고있는 강력한 형식의 대리자가 서비스 호출에 속하는 것입니다. 그러나 내가 볼 수 Action<T>있는 한 잘 작동합니다.
Jesse C. Slicer 14 년

5

이와 같은 래퍼가 작동합니다.

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

그러면 다음과 같은 코드를 작성할 수 있습니다.

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

래퍼는 필요한 경우 더 많은 예외를 잡을 수 있지만 원칙은 동일합니다.


특정 조건에서 Dispose가 호출되지 않는 것에 대한 토론을 기억합니다 .WCF에서 메모리 누수가 발생합니다.
goodguys_activate

메모리 누수가 발생했는지 확실하지 않지만 문제는 이것입니다. DisposeIChannel 을 호출 할 때 채널에 결함이있는 상태 인 경우 예외가 발생할 수 있습니다. 이는 Microsoft Dispose가 절대로 throw하지 않도록 지정하기 때문에 발생하는 문제 입니다. 따라서 위의 코드 Close는 예외가 발생 했을 때 처리 하는 것입니다. 경우 Abort발생이 심각하게 뭔가 잘못 될 수 있습니다. 지난 12 월 블로그 게시물을 썼습니다 : blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson

4

캐슬 동적 프록시를 사용하여 Dispose () 문제를 해결하고 채널을 사용할 수없는 상태 일 때 자동 새로 고침을 구현했습니다. 이를 사용하려면 서비스 계약 및 IDisposable을 상속하는 새 인터페이스를 작성해야합니다. 동적 프록시는이 인터페이스를 구현하고 WCF 채널을 래핑합니다.

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

소비자가 WCF의 세부 사항에 대해 걱정할 필요없이 WCF 서비스를 주입 할 수 있기 때문에 이것을 좋아합니다. 그리고 다른 솔루션들과 마찬가지로 추가 된 부분이 없습니다.

코드를 살펴보십시오. 실제로는 매우 간단합니다. WCF Dynamic Proxy


4

확장 방법을 사용하십시오.

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 가 필요하지 않거나 자동 생성 된 클라이언트 (서비스 참조)를 사용하는 경우 래퍼를 사용 하여 클로저 를 관리하고 예외가 발생하지 않는 안전한 상태에있을 때 GC 가 clientbase를 사용하도록 할 수 있습니다. GC는 serviceclient에서 Dispose를 호출하고을 호출 Close합니다. 이미 닫혀 있으므로 손상을 입을 수 없습니다. 프로덕션 코드에서 문제없이 이것을 사용하고 있습니다.

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

그런 다음 서버에 액세스 할 때 클라이언트를 작성 using하고 자동 검색에서 사용 합니다.

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

    Ws.Open();

    Ws.Test();
}

3

요약

이 답변에 설명 된 기술을 사용하면 다음 구문으로 using 블록에서 WCF 서비스를 사용할 수 있습니다.

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

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

물론 상황에 따라 더 간결한 프로그래밍 모델을 달성하기 위해 이것을 더 조정할 수도 있습니다. 그러나 요점은 IMyService일회용 패턴을 올바르게 구현하는 채널을 다시 작성하는 구현을 만들 수 있다는 것 입니다.


세부

지금까지 제공된 모든 답변은의 WCF 채널 구현에서 "버그"를 해결하는 문제를 해결합니다 IDisposable. (당신이 사용할 수 있도록 가장 간결한 프로그래밍 모델을 제공 할 것 대답 using하는 관리되지 않는 리소스에 처분하는 블록)는 이 하나의 프록시 구현하기 위해 약간 변형되어 - IDisposable버그 무료 구현. 이 접근 방식의 문제점은 유지 관리 성입니다. 사용하는 프록시마다이 기능을 다시 구현해야합니다. 이 답변의 변형에서 상속 대신 컴포지션 을 사용 하여이 기술을 일반화 하는 방법을 알 수 있습니다 .

첫번째 시도

구현에 대한 다양한 구현이있는 것처럼 보이지만 IDisposable인수를 위해 현재 승인 된 답변에 사용 된 조정을 사용할 입니다.

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

위의 클래스로 무장하여 이제 작성할 수 있습니다

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

이를 통해 using블록을 사용하여 서비스를 사용할 수 있습니다 .

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

이것을 일반적인 것으로 만들기

우리가 지금까지 한 일은 Tomas의 솔루션 을 재구성하는 입니다. 이 코드가 일반화되지 못하게하는 것은 ProxyWrapper원하는 모든 서비스 계약에 대해 클래스를 다시 구현해야 한다는 사실입니다 . 이제 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();
    }
}

새로운 헬퍼 클래스로

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

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

를 사용하는 ClientBase<>대신 상속하는 자동 생성 된 클라이언트에 대해 ChannelFactory<>또는 약간의 수정을 통해 동일한 기술 을 사용하거나 다른 구현을 사용 IDisposable하여 채널을 닫을 수도 있습니다.


2

나는 이런 방식으로 연결을 닫는 것을 좋아한다.

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

1

이것을 처리 하는 간단한 기본 클래스 를 작성 했습니다 . NuGet 패키지 로 제공되며 사용이 매우 쉽습니다.

//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에 ​​대한 모든 업데이트? stackoverflow.com/a/9370880/206730 과 같은 재시도 옵션은 무엇입니까? –
Kiquenet

@Kiquenet 더 이상 WCF에서 작업하지 않습니다. 풀 요청을 보내면 병합하여 패키지를 업데이트 할 수 있습니다.
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();
            }
        }
    }
}

따라서 return 문을 멋지게 작성할 수 있습니다.

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

1

ChannelFactory 대신 ServiceClient를 사용하는 경우 Marc Gravell의 답변 에서 Service 구현을 추가하고 싶습니다 .

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

관심있는 사람들을 위해 허용 된 답변 (아래)의 VB.NET 번역이 있습니다. 이 스레드에서 다른 사람들의 팁을 결합하여 간결하게하기 위해 약간 수정했습니다.

원래 태그 (C #)에 대해서는 주제가 맞지 않다는 것을 인정하지만이 훌륭한 솔루션의 VB.NET 버전을 찾을 수 없으므로 다른 사람들도 볼 것이라고 가정합니다. Lambda 번역은 약간 까다로울 수 있으므로 문제를 해결하고 싶습니다.

이 특정 구현은 ServiceEndpoint런타임시 구성 기능을 제공합니다 .


암호:

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

용법:

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

우리의 시스템 아키텍처는 종종 Unity IoC 프레임 워크를 사용하여 ClientBase의 인스턴스를 생성하므로 다른 개발자들도 사용하도록 강요 할 방법이 없습니다using{} 블록을 . 가능한 한 완벽하게 만들기 위해 ClientBase를 확장하고 처리시 채널 종료를 처리하거나 누군가가 Unity 생성 인스턴스를 명시 적으로 처리하지 않는 경우 마무리하는이 사용자 정의 클래스를 만들었습니다.

사용자 정의 자격 증명 및 항목에 대한 채널을 설정하기 위해 생성자에서 수행해야 할 사항도 있으므로 여기에도 있습니다.

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

그러면 고객은 간단히 다음을 수행 할 수 있습니다.

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

발신자는 다음 중 하나를 수행 할 수 있습니다.

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 메서드에 배치 된 매개 변수를 사용하지 마십시오
CaffGeek

@Chad-Microsoft의 일반적인 Finalize / Dispose 디자인 패턴을 따르고 있습니다. msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx 변수를 사용하지 않는 것은 사실입니다. 일반 처분과 마무리 사이에 다른 정리를 할 필요가 없습니다. Finalize에서 Dispose ()를 호출하고 Dispose (bool)에서 Dispose ()로 코드를 이동하도록 다시 작성할 수 있습니다.
CodingWithSpike

종료자는 오버 헤드를 추가하고 결정적이지 않습니다. 가능할 때마다 피합니다. Unity의 자동 팩토리를 사용하여 델리게이트를 주입하고 블록을 사용하여 델리게이트를 삽입하거나 주입 된 인터페이스의 메소드 뒤에 서비스 작성 / 호출 / 처리 동작을 숨길 수 있습니다. 종속성에 대한 각 호출은 프록시를 작성하고 호출하여 처리합니다.
TrueWill

0

이 게시물에 대한 답변이 거의 없으며 내 필요에 따라 사용자 정의했습니다.

DoSomethingWithClient()방법을 사용하기 전에 WCF 클라이언트로 무언가를 할 수있는 기능을 원했습니다 .

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

헬퍼 클래스는 다음과 같습니다.

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

그리고 나는 그것을 다음과 같이 사용할 수 있습니다 :

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

바인딩과 엔도 핑을 사용하는 클라이언트 생성자는 어떻습니까? TClient (바인딩, 엔도 잉)
Kiquenet

0

Dispose를 다음과 같이 구현하는 채널에 대한 자체 래퍼가 있습니다.

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

이것은 잘 작동하는 것으로 보이며 using 블록을 사용할 수 있습니다.


0

다음 헬퍼는 호출 void및 비 공백 메소드를 허용 합니다. 용법:

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

수업 자체는 다음과 같습니다.

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

채널 생성 및 캐싱관리 할 필요없이 ClientBase를 기반으로 프록시 클래스를 생성 할 필요없이 클라이언트의 Dispose ()를 재정의하십시오 . WcfClient는 ABSTRACT 클래스가 아니며 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();
    }
}

0

이 작업을 수행하는 방법은 IDisposable을 명시 적으로 구현하는 상속 된 클래스를 만드는 것입니다. 이것은 GUI를 사용하여 서비스 참조를 추가하는 사람들에게 유용합니다 (서비스 참조 추가). 서비스를 참조하는 프로젝트 에서이 클래스를 삭제하고 기본 클라이언트 대신 사용합니다.

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

참고 : 이것은 단순한 처분의 구현입니다. 원하는 경우 더 복잡한 처분 논리를 구현할 수 있습니다.

그런 다음 일반 서비스 클라이언트로 이루어진 모든 통화를 다음과 같이 안전한 클라이언트로 교체 할 수 있습니다.

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

인터페이스 정의에 액세스 할 필요가 없으므로이 솔루션을 좋아하며 using코드가 다소 비슷하게 보이도록하면서 명령문을 사용할 수 있습니다 .

이 스레드의 다른 주석에서 지적한 것처럼 예외를 처리해야합니다.


-2

a DynamicProxy를 사용하여 Dispose()방법 을 확장 할 수도 있습니다 . 이 방법으로 다음과 같은 작업을 수행 할 수 있습니다.

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.