WCF Troubleshooting (3) - Error Handler

WCF 2011. 3. 3. 09:00 Posted by 알 수 없는 사용자
오랜만에 인사 드립니다~
해가 바뀌고, 벌써 두 달이 거의 지나가고 있는 시점에 이제야 다시 글을 올리게 되었습니다.
참, 부끄럽기 그지 없군요~ ^^;;
부끄럽긴 하지만 인사는 드려야죠!! 새해 복!! 많이 받으쌉싸리와용~

두어 달 만에 포스팅을 이어가려니 정말 난감하기도 하고, 민망하기도 하고... 
하지만, 이 민망함 그냥 얼굴에 철판 깔고, 시작 해 보렵니다. 어차피 포스팅 기다린 사람도 없었을 테니깐,, ㅋㅋ

이번 포스팅은 Troubleshooting 의 세 번째 포스팅으로, Error Handler 에 대해 알아볼까 합니다.

서비스를 개발할 때, 서비스에서 예외가 발생하는 경우, 이 예외에 대해 어떤 공통적인 동작을 취하게끔 코드를 만들고 싶을 때가 있습니다. 예를 들면, 발생하는 모든 예외에 대한 정보를 로그로 남긴다거나, 클라이언트로 전송되는 fault message의 내용을 동일하게 한다거나, 등등등... 

이럴 때 사용할 수 있는 것이 바로 Error Handler 입니다.
명칭을 봐도 딱! 감이 오지 않습니까? "에러 핸들러!!!" ㅋ

그럼, Error Handler 를 사용하는 방법을 차근 차근 적어내려 가보겠습니다.

WCF의 에러 핸들러를 사용하기 위해 가장 먼저 해야하는 것은 IErrorHandler 인터페이스를 구현하는 서브 클래스를 만드는 것입니다.

IErrorHandler 인터페이스는 다음과 같은 두 개의 메서드를 제공합니다.
 Method  Description 
 HandleError  예외에 대해 어떤 공통적인 동작을 취할 수 있는 메서드입니다.
 (예 : 예외 정보 로깅)
 ProvideFault  클라이언트로 보내질 fault message 를 정의할 수 있는 메서드입니다. 

아하~ 그리 어렵지 않죠? ㅎ

그럼, 직접 한번 구현 해보겠습니다.

namespace ErrorHandler

{

    [DataContract]

    public class MyErrorInfo

    {

        [DataMember]

        public string Message { get; set; }

        [DataMember]

        public string ExceptionInfo { get; set; }

    }

}



public class SampleErrorHandler : IErrorHandler

{

    public bool HandleError(Exception error)

    {
        // 이곳에 발생한 예외에 대한 공통적인 작업을 구현할 수 있습니다.

        return true;

    }

 

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)

    {

        FaultReason reason = new FaultReason("내맘대로 무조건 예외");

        FaultException<MyErrorInfo> faultException = new FaultException<MyErrorInfo>(

                                                  new MyErrorInfo

                                                  {

                                                      Message = error.Message,

                                                      ExceptionInfo = error.ToString()

                                                  },

                                                  reason);

 

        MessageFault messageFault = faultException.CreateMessageFault();

 

        fault = Message.CreateMessage(

            version,

            messageFault,

            faultException.Action);

    }

}


이 예제에서는 HandleError 메서드에 별 다른 코드를 넣지 않았습니다. 앞에서 언급을 했었지만 HandleError 메서드에서는 발생한 예외에 대한 어떤 공통적인 행동에 대한 코드를 구현 해주시면 됩니다.

그리고, HandleError 메서드에서는  bool 형의 값을 반환 해주는 것이 보이네요~ 이 bool 형의 값은 예외가 적절하게 처리되었는지 여부를 나타내준다고 생각하시면 됩니다. 만약, false를 반환하게되면, 예외가 처리되지 않은 것으로 간주하고, 기본 응답이 사용됩니다. 이 경우 디스패처가 모든 세션을 중단하고, InstanceContext를 중단합니다.

MSDN의 HandleError 메서드 설명 페이지에도 나와 있지만, HandleError 메서드는 여러 다른 위치에서 호출될 수 있기 때문에 이 메서드에서 예외를 제대로 처리하지 못했다고 false를 반환하게 되면, 모든 상태가 손상된 것으로 간주되고, 서비스에 존재하는 모든 세션이 중단된다고 생각하시면 됩니다.

따라서, 예외가 발생했을 때 모든 세션이 중단되길 원치 않는다면 위 예제 코드처럼 true를 반환하는 것이 나을 것입니다.

ProvideFault 메서드의 코드도 그리 어려워 보이지 않는군요. 이 전 저의 포스팅을 보셨던 분이라면 같은 생각을 하실 것 같네요~ ^^

ProvideFault의 매개 변수에 대한 설명은 다음과 같습니다.

 Parameter  Description 
 error  서비스 작업 중에 던져지는 Exception 개체입니다.
 version  메시지의 SOAP 버전입니다. 
 fault  클라이언트로 보내지는 Message 개체입니다. 

위 예제 코드에선 ProvideFault 메서드 안에서 매개 변수로 받은 예외 개체(error)를 이용하여 사용자 정의 된 예외 메시지를 정의합니다. 이렇게 정의 된 예외 메시지를 매개 변수 fault에 할당만 해주면 이 메시지는 클라이언트로 전달 됩니다.
FaultException 와 FaultReason 에 관한 내용은 이 전 포스트를 확인 해주세요~ ^^

이제 ErrorHandler 구현은 끝이 났습니다.
이렇게 만든 ErrorHandler를 사용하기 위해서 다음으로 해야 할 것은 WCF 서비스에 사용할 수 있는 새로운 Behavior 를 만드는 것입니다. 
Custom Behavior 를 만들어 WCF를 확장하는 방법에 대해선 이 곳을 참고하시면 좋을 것 같습니다.

그럼 새로운 Behavior를 만들어 볼까요?


public sealed class ErrorBehaviorAttribute : Attribute, IServiceBehavior

{

    private List<Type> _errorHandler;

 

    public List<Type> ErrorHandlerType

    {

        get { return _errorHandler; }

    }

 

    public ErrorBehaviorAttribute(params Type[] errorHandler)

    {

        this._errorHandler = new List<Type>();

        foreach (var item in errorHandler)

        {

            _errorHandler.Add(item);

        }

    }

 

    public void AddBindingParameters(ServiceDescription serviceDescription,

                                        ServiceHostBase serviceHostBase,

                                        Collection<ServiceEndpoint> endpoints,

                                        BindingParameterCollection bindingParameters)

    {           

    }

 

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription,

                                        ServiceHostBase serviceHostBase)

    {

        List<IErrorHandler> errorHandler = new List<IErrorHandler>();

 

        ErrorHandlerType.ForEach(

            (item) =>

            {

                errorHandler.Add((IErrorHandler)Activator.CreateInstance(item));

            });

 

        foreach (ChannelDispatcherBase dispatcherBase in
                                                     serviceHostBase.ChannelDispatchers)

        {

            ChannelDispatcher dsp = dispatcherBase as ChannelDispatcher;

            errorHandler.ForEach(

                (item) =>

                {

                    dsp.ErrorHandlers.Add(item);

                });

        }

    }

 

    public void Validate(ServiceDescription serviceDescription,

                            ServiceHostBase serviceHostBase)

    {           

    }

}

이 코드를 보면 조금 복잡해 보일 것 같습니다. Behavior의 경우 설정파일(.config)에서 설정을 하거나 코드 상에서 설정을 할 수 있는데 서비스 클래스의 Attribute 특성을 사용하여 설정합니다.
그래서 이 클래스는 Attribute 시스템 클래스를 상속합니다. 또한, IServiceBehavior 인터페이스를 상속하여 Behavior 로 사용할 수 있는 클래스를 만듭니다.

위 클래스의 생성자를 보면 하나 이상의 클래스 타입을 매개 변수로 받습니다. 이렇게 받은 타입들을 List<Type> 인스턴스의 전역 변수에 저장을 합니다. 따로 예외 처리를 하진 않았지만 생성자에 매개변수로 넘겨주는 클래스 타입은 반드시 IErrorHandler 인터페이스를 구현한 타입이어야 합니다.

그리고, IServiceBehavior 인터페이스 메서드 중에 ApplyDispatchBehavior 메서드를 구현합니다. 이 메서드에서는 서비스에 존재하는 모든 channel dispatcher 에게 생성자에서 받았던 IErrorHandler 타입들의 인스턴스를 추가시켜줍니다.

말이 조금 어렵나요? ^^;;

참고로, channel dispatcher 는 WCF 서비스를 구현할 때 어떤 Binding을 사용하는냐에 따라 달라집니다. 물론 사용하는 Binding 의 수에 따라 disptcher의 수도 늘어가게 됩니다.
dispatcher에 대해 잘 알지 못하는 분들이 있을 것 같습니다. 정확한 설명을 이 포스팅에서 하기에는 이것 만으로도 내용이 길어질 것 같아 설명하긴 힘들지만, 이 곳(디스패처 확장)의 내용을 확인하시면 이해는 가시리라 생각합니다. ^^

이제 준비 작업은 모두 끝이 났습니다. 앞에서 만든 ErrorHandler 를 사용할 수 있을 것 같네요

ErrorHandler를 사용하기 위해 다음과 같이 간단한 서비스를 만들고 서비스 클래스에 ErrorBehavior 특성을 설정하였습니다.

// Service Contract
[
ServiceContract]

public interface ICalc

{

    [OperationContract]

    [FaultContract(typeof(MyErrorInfo))]

    int Add(int a, int b);

 

    [OperationContract]

    [FaultContract(typeof(MyErrorInfo))]

    int Sub(int a, int b);

 

    [OperationContract]

    [FaultContract(typeof(MyErrorInfo))]

    int Mul(int a, int b);

 

    [OperationContract]

    [FaultContract(typeof(MyErrorInfo))]

    int Div(int a, int b);

}


// Service 구현 클래스
[ErrorBehavior(typeof(SampleErrorHandler))]

public class Calculator : ICalc

{

    public int Add(int a, int b)

    {

        throw new InvalidOperationException("잘못된 Add 메서드 호출입니다.");

    }

 

    public int Sub(int a, int b)

    {

        throw new InvalidOperationException("잘못된 Sub 메서드 호출입니다.");

    }

 

    public int Mul(int a, int b)

    {

        throw new InvalidOperationException("잘못된 Mul 메서드 호출입니다.");

    }

 

    public int Div(int a, int b)

    {

        throw new InvalidOperationException("잘못된 Div 메서드 호출입니다.");

    }

}

모든 메서드를 호출하면 아~무 이유없이 예외를 던지고 있군요~!! ㅎ

이제 이 서비스를 빌드하고 테스트를 해보야겠죠.
이번에는 따로 Console 어플리케이션을 만들지 않고 WcfTestClient.exe를 사용해보도록 하겠습니다.
이 간단한 프로그램은 WCF 서비스를 테스트하기 위한 클라이언트 툴입니다.

Visual Studio 명령 프롬프트를 실행시키고 "WcfTestClient"를 치고 엔터를 클릭하면 실행시킬 수 있습니다. 또는 Visual Studio 에서 WCF 서비스 프로젝트를 F5 를 이용하여 실행해도 역시 이 툴을 사용할 수 있습니다.

이 툴을 실행시키면 다음과 같은 모습을 하고 있죠.


여기에서 간단히 호출하고자 하는 메서드를 왼쪽 창에서 마우스로 더블 클릭 "톡! 톡!" 해주시면 실행할 수 있습니다. 이건 너무 직관적인거라 자세한 설명을 하지 않더라도 모두 사용하실 수 있으실겁니다 ^^

아무 메서드를 하나 실행시키면 예외가 발생했다는 내용을 담고 있는 창이 뜨는 것을 보실 수 있습니다. 여기서 오류정보를 보면 위에 SampleErrorHandler 의 ProvideFault 메서드에서 정의한 내용들이 들어가 있는 것을 확인할 수 있습니다.

더 자세한 내용을 보고 싶다면, 예외 창을 닫고 오른쪽 창 밑에 있는 "XML" 탭을 클릭해보세요~ 그럼 다음과 같은 화면을 보실 수 있으실 겁니다.


응답에 있는 XML을 다시 보여드려볼까요?

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">

  <s:Header />

  <s:Body>

    <s:Fault>

      <faultcode>s:Client</faultcode>

      <faultstring xml:lang="ko-KR">내맘대로 무조건 예외</faultstring>

      <detail>

        <MyErrorInfo xmlns="http://schemas.datacontract.org/2004/07/ErrorHandler" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

          <ExceptionInfo>

            System.InvalidOperationException: 잘못된 Sub 메서드 호출입니다.

            위치: ErrorHandler.Calculator.Sub(Int32 a, Int32 b) 파일 D:\Dev\Learning\WCF\ErrorHandler\ErrorHandler\Service1.svc.cs: 21

            위치: SyncInvokeSub(Object , Object[] , Object[] )

            위치: System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]&amp; outputs)

            위치: System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&amp; rpc)

            위치: System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&amp; rpc)

            위치: System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(MessageRpc&amp; rpc)

            위치: System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc&amp; rpc)

            위치: System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc&amp; rpc)

            위치: System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc&amp; rpc)

            위치: System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc&amp; rpc)

            위치: System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc&amp; rpc)

            위치: System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc&amp; rpc)

            위치: System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

          </ExceptionInfo>

          <Message>잘못된 Sub 메서드 호출입니다.</Message>

        </MyErrorInfo>

      </detail>

    </s:Fault>

  </s:Body>

</s:Envelope>



네~ XML 내용을 확인하니 좀 더 확실해 졌네요. 클라이언트가 받은 메시지에 ProvideFault 메서드에서 정의한 내용들이 들어가 있다는 것을요~ ㅎ

자~ 그럼 마무리 하겠습니다!!

이번 내용은 뭔가 조금 복잡했던 것 같지만 사실 그렇게 복잡하지 않습니다. 이 포스트를 찬찬히 되새기면서, 그리고 인터넷을 통해 다른 부가적인 내용들도 알아가면서 학습을 하시면 그리 어렵지 않다는 것을 느끼게 되실겁니다.
예외를 처리하는 방법은 실무에 꽤 많이 쓰일 수 있는 내용이니깐 제대로 알고 가는건 좋을 것 같습니다.

다음 포스팅에선 계속해서 Troubleshooting 에 관한 내용으로 찾아 뵙도록 하겠습니다.
WCF에서 제공하는 몇 가지 툴들이 있는데 이런 툴들에 대한 설명이 될 것 같습니다.

그럼 다음 포스팅때까지 안녕히~ ^^

'WCF' 카테고리의 다른 글

REST 서비스 템플릿  (3) 2011.05.02
WCF Troubleshooting (2)  (2) 2010.11.29
WCF Service Configuration Editor  (0) 2010.11.24
WCF Troubleshooting (1)  (0) 2010.11.19
WCF Security (2) - 전송 계층에서의 메세지 인증 (사용자 지정 인증)  (0) 2010.09.06