development

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

big-blog 2020. 2. 22. 11:44
반응형

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


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결함이있는 상태 예외를 마스킹 할 위험없이 블록을 다시 허용 할 수있는 것으로 보입니다 .

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


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

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

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

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

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"메서드 도우미를 작성하여 원하는 방식으로 채널 팩토리를 구성 할 수 있습니다. 또한 생성 된 프록시를 사용하지 않아도됩니다. .


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
}

분명히 이것은 쓸만한 평범한 코드입니다. 나는 현재이 답변을 선호하며 , 그 코드에서 길을 문제를 일으킬 수있는 "해킹"을 보지 않습니다.


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

이 사용자 정의 도구는 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합니다.


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 대신 하위 항목 과 함께 사용할 수 있습니다 .

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


@ 마크 그 라벨

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

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

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


이게 뭐야?

이것은 허용 된 답변의 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 );
       }

    }
}

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


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

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

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

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.

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


Castle 동적 프록시를 사용하여 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


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

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

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

요약

이 답변에 설명 된 기술을 사용하면 다음 구문의 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하여 채널을 닫을 수도 있습니다.


나는 연결을 닫는이 방법을 좋아한다.

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

이것을 처리 하는 간단한 기본 클래스작성 했습니다 . 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);
    }
}

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

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

관심있는 사람들을 위해 허용 된 답변 (아래)의 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

우리의 시스템 아키텍처는 종종 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);
        }
    }
}

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

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

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 블록을 사용할 수 있습니다.


다음 헬퍼는 호출 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;
        });
    }
}

채널 생성 및 캐싱관리 할 필요없이 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();
    }
}

이 작업을 수행하는 방법은 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코드가 다소 동일하게 보이도록하면서 예상대로 명령문을 사용할 수 있습니다 .

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


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

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

참고 URL : https://stackoverflow.com/questions/573872/what-is-the-best-workaround-for-the-wcf-client-using-block-issue



반응형