Search

'Troubleshooting'에 해당되는 글 3건

  1. 2011.03.03 WCF Troubleshooting (3) - Error Handler 1
  2. 2010.11.29 WCF Troubleshooting (2) 2
  3. 2010.11.19 WCF Troubleshooting (1)

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

WCF Troubleshooting (2)

WCF 2010. 11. 29. 09:00 Posted by 알 수 없는 사용자
일주일 만에 돌아온 RuAA 입니다. 하핫~
이제 곧 올해의 마지막인 12월이네요~ 어느새,, 벌써,, ㅡㅠ
나이만 먹는 것 같아 참 슬퍼지려 합니다.
12월엔 술자리도 많고, 행사도 많아 바빠지는데, 모두 건강 챙기시길 바랍니다.


다른 설명 없이 바로~ 지난 포스트에 이어서, 진행해보도록 하겠습니다~

이번 포스팅에서는 FaultContractAttribute 대해서 잠깐 알아보고, 이를 이용하여 에러 메시지를 직접 정의해보도록 하겠습니다.

그럼, FaultContractAttribute 무엇이냐~? 간단하게아주 간단하게직접 정의한 에러 메시지를 클라이언트로 전달하기 위한 서비스 메서드의 속성이라고 생각하면 됩니다.

닷넷에는 기본적으로 제공하는 여러 종류의 예외 클래스가 존재합니다. 지난 포스트에서 봤듯이 이러한 예외가 발생하였을 때는 그에 맞는 예외 클래스에 정의되어 있는 메시지들이 클라이언트로 전달되었습니다
.
하지만, 이러한 메시지들 이외에
서비스 정책에 맞는 메시지를 따로 정의하고, 이를 클라이언트에 노출하고 싶을 , FaultContractAttribute 사용할 있습니다.

그럼, 이를 이용해,  특정 에러 메시지를 정의하고, 메시지를 클라이언트에 전달하는 예제를 한번 보도록 하겠습니다. 예제 한번 보고 나면 이해가 되실겁니다. 하하

우선, WCF 서비스에서 사용할 있는 새로운 클래스를 정의합니다. 물론, DataContractAttribute 이용해서요~


[
DataContract]

public class ErrorInfo

{
    [
DataMember]

    public string Info { get; set; }

 

    [DataMember]

    public ErrorCode Code { get; set; }

}

 

[DataContract]

public enum ErrorCode

{

    [EnumMember]

    WrongName,

    [EnumMember]

    NotExist

}

 


ErrorInfo 라는 이름의 클래스를 정의 하였습니다. 클래스에는 에러의 정보를 담을 있는 Info 라는 프로퍼티가 있고, 에러의 종류를 나타내는 Code 라는 프로퍼티가 정의되어 있습니다. 또한, 덤으로 에러 종류를 쉽게 분류하기 위하여 ErrorCode 라는 이름의 열거형을 정의하였습니다.

그럼, FaultContractAttribute 어디에 정의하는 걸까요? 다음 코드를 보시면 바로 있습니다~


[
ServiceContract]

public interface IService1

{

[OperationContract]

int Divide(int numerator, int denominator);

 

    [OperationContract]

    [FaultContract(typeof(ErrorInfo))]

    int FindEmployee(string employeeId);

}

 


보이시죠? ~ 바로 OperationContractAttribute 같은 위치에 선언됩니다. 예제에서 FindEmployee 오퍼레이션은 ErrorInfo 타입의 에러 메시지가 발생할 있다는 것을 나타냅니다.

이제, 직접 ErrorInfo 클래스에 에러 메시지를 정의하고, 클라이언트로 전달하는 코드를 보셔야죠~
다음 예제를 보겠습니다.


public
class Service1 : IService1

{

    public int Divide(int numerator, int denominator)

    {

        return numerator / denominator;

    }

 

    public int FindEmployee(string employeeId)

    {

        // 사용자 정의 에러 발생

        FaultReason reason = new FaultReason(string.Format("{0} employee is not exist", employeeId));

        ErrorInfo error = new ErrorInfo

        {

            Info = string.Format("Not Exist Employee, ID : {0}", employeeId),

            Code = ErrorCode.NotExist

        };

 

        throw new FaultException<ErrorInfo>(error, reason);

    }

}

 


FindEmployee 메소드는 무조건 예외를 발생하도록 되어 있습니다. 사실, 에러 메시지를 클라이언트로 전달하는 것이 목적이니깐 다른 코드는 예제에서 생략이 되어도 상관없겠죠~ ^^

FaultReason 이라는 새로운 클래스도 눈에 띄는군요~ 클래스는 해당하는 오류의 간단한 메시지를 작성하기 위한 클래스라고 생각하시면 됩니다.

그리고, 방금 만들었던 ErrorInfo 인스턴스를 생성하고, Info, Code 프로퍼티에 클라이언트로 전달하고 싶은 오류 메시지를 입력하였습니다.
마지막으로 FaultException<T> 이용하여 ErrorInfo 포함한 예외를 발생시켰습니다
.
FaultException
클래스는 SOAP 오류로 변환될 있는 예외 클래스입니다.

이제, 클라이언트에서 에러메시지를 낚아채어(?) 보여주는 예제를 보겠습니다.
콘솔 어플리케이션을 생성하고, 서비스 참조를 후에 다음과 같이 코드를 작성 해보았습니다.


static
void Main(string[] args)

{

    Service1Client proxy = new Service1Client();

 

    try

    {

        proxy.Divide(5, 0);

    }

    catch (FaultException ex)

    {

        Console.WriteLine("Reason : {0}", ex.Reason.ToString());

        Console.WriteLine("Message : {0}", ex.Message);

        Console.WriteLine();

    }

 

    try

    {

        proxy.FindEmployee("RuAA");

    }

    catch (FaultException<ErrorInfo> ex)

    {

        Console.WriteLine("Reason : {0}", ex.Reason.ToString());

        Console.WriteLine("Message : {0}", ex.Message);

        Console.WriteLine("\n<< Detail Info >>");

        Console.WriteLine("Code : {0}", ex.Detail.Code);

        Console.WriteLine("Info : {0}", ex.Detail.Info);

    }

}

 


처음엔 Divide 오퍼레이션을 호출하여 닷넷에서 기본적으로 제공하는 예외를 발생시켰고, 번째 try, catch 문에서 FindEmployee 오퍼레이션을 호출 하였습니다.

위의 코드에서 보듯이, 클라이언트에서 서비스의 예외를 낚아채기(?) 위해선 FaultException 클래스를 사용합니다. FaultException 클래스에 제네릭 형식이 정의되어 있지 않은 경우엔 닷넷에서 제공하는 기본적인 예외 메시지를 catch 있으며, 서비스에 정의 특정 예외 메시지를 catch 하고자 , FaultExcepton 클래스에 제네릭 형식을 지정해주면 됩니다.

그리고, 가지 주목할 점은, ErrorInfo 인스턴스가 FaultException 인스턴스의 Detail 프로퍼티에 입력된다는 것입니다. 이는, 뒤에 보여줄 SOAP Fault 메시지를 확인하면 아마 이해가 쉬우실겁니다.
때문에, (서비스 오퍼레이션에서 정의했던
) ErrorInfo 클래스의 Info, Code 프로퍼티에 입력된 값을 가져오기 위해선 ex.Detail.Code(또는 ex.Detail.Info) 같이 접근 하여야 합니다.

이렇게 처리를 하면, 다음과 같은 클라이언트 결과를 확인할 있습니다.


마지막으로, 서비스에서 클라이언트로 전달되는 SOAP 메시지를 확인해 보겠습니다.

 

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">

  <s:Header>

    <a:Action s:mustUnderstand="1">http://tempuri.org/IService1/FindEmployeeErrorInfoFault</a:Action>

    <a:RelatesTo>urn:uuid:c67dd581-4b6d-4a02-aa35-fba3515a0ccf</a:RelatesTo>

  </s:Header>

  <s:Body>

    <s:Fault>

      <s:Code>

        <s:Value>s:Sender</s:Value>

      </s:Code>

      <s:Reason>

        <s:Text xml:lang="en-US">RuAA employee is not exist</s:Text>

      </s:Reason>

      <s:Detail>

        <ErrorInfo xmlns="http://schemas.datacontract.org/2004/07/Wcf_TroubleShooting"

                   xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

          <Code>NotExist</Code>

          <Info>Not Exist Employee, ID : RuAA</Info>

        </ErrorInfo>

      </s:Detail>

    </s:Fault>

  </s:Body>

</s:Envelope>

 


SOAP 메시지를 확인해 보면 ErrorInfo 라는 엘리먼트가 눈에 ~!! 띄는군요. 하하
이렇게 SOAP 메시지의 내용과 앞의 예제 코드, 그리고 클라이언트의 결과 화면을 비교해보면 이번 포스팅의 내용이 이해하기 쉬울 같습니다. ^^

아직 얘기가 많지만, 다음 포스팅을 기약하면서, 마무리 하도록 하겠습니다.

WCF Troubleshooting (1)

WCF 2010. 11. 19. 09:00 Posted by 알 수 없는 사용자
 
와우~ 정말 오랜만에 포스팅을 하네요 ^^;;
여러 가지 일 때문에 포스팅을 하지 못했는데 이제 다시 힘을 내서 그 동안 못했던 포스팅을 해보도록 하겠습니다.
어느새 계절은 겨울이 되어 날씨가 많이 추워졌는데, 모두 건강 주의 하시길 바랍니다.

이번 포스팅 부터는 WCF 를 이용하여 서비스를 구현하는데 있어 발생할 수 있는 여러 가지 예외나 에러를 어떻게 처리할 수 있는지에 대한 얘기를 해 볼까 합니다.

WCF 서비스와 같이 분산 어플리케이션을 개발할 때는 항상 에러나 예외의 원인을 찾거나 디버깅하기가 조금 애매하죠. 로컬에서 개발할 때에는 문제가 없다가도 실제 서버에 배포를 하고 나면 발생하는 예외나 에러는 개발자를 난감하게 만들기도 합니다.

그래서~ 이번 포스팅 부터는 WCF 에서 발생하는 예외를 어떻게 핸들링 할 수 있는지, 그리고 에러에 대한 진단을 하기 위해 어떤 방법을 사용할 수 있는지에 대한 내용으로 진행을 해볼까 합니다.


Fault Message 와 Exception

.NET 어플리케이션은 에러가 발생했을 때, 에러에 대한 내용을 알리기 위해 Exception 클래스를 사용합니다. 이는 물론, WCF에서도 예외가 아닙니다. 다만, 이러한 에러의 내용을 클라이언트에 전달하기 위해서 Fault Message 를 사용한다는 것이 조금 다르긴 하죠.
쉽게 얘기해서, 서비스에서 클라이언트로 어떤 데이터를 넘겨줄 때 직렬화를 사용하는 것처럼, 에러 메시지,, 그러니깐 Exception 오브젝트도 직렬화를 한 후에 클라이언트로 전달한다고 생각하면 된다는 것입니다.

Fault Message는 다음과 같은 XML 형태로 클라이언트로 전달되는데, 이는 표준 SOAP Fault 메시지(Ver 1.2)의 스키마와 같습니다.

<Body>
   <Fault>
      <Code>
         <Value>s:Sender</Value>
      </Code>
      <Reason>
         <Text xml:lang="en-US">…</Text>
      </Reason>
      <Detail>
         <ErrorInfo xmlns="http://Service1">
            <Info>…</Info>
         </ErrorInfo>
      </Detail>
   </Fault>
</Body>


각 element에 대한 자세한 설명은 다음의 링크에서 확인하시면 될 것 같습니다. (http://www.w3.org/TR/soap12-part1/#soapfault) 근데, 자료가 영문이라 보기 싫으신 분들 있으실 겁니다. 저도 그렇지만~ 일단, 글이 너무 많으면 부담되서…(그것도 영문… OTL)

그래서, 다음과 같이 간단하게 정리를 해보았습니다.

Element

설명

Fault

Fault 메시지의 root element

Code

예외가 발생한 원인을 나타낸다.

Reason

예외의 자세한 내용을 보여준다.

Detail

예외의 추가적인 정보를 나타낸다.


이 정도만 체크하고, 다음으로 넘어가기로 하죠~ 엣헴~ ;;;;

기본적으로 WCF 서비스에서 예외가 발생하면, 자세한 정보가 담겨있는 Fault 메시지가 전달되는 대신에 다음과 같이 Detail element가 빠져있는 Fault 메시지를 전달합니다.

<s:Fault>
   <s:Code>
      <s:Value>s:Receiver</s:Value>
      <s:Subcode>
         <s:Value xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher">a:InternalServiceFault
         </s:Value>
      </s:Subcode>
   </s:Code>
   <s:Reason>
   <s:Text xml:lang="en-US">The server was unable to process the request due to an internal error. For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the &lt;serviceDebug&gt; configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.</s:Text>
   </s:Reason>
</s:Fault>


Text element의 내용을 자세히 보면, WCF 서비스 개발을 하면서 한번쯤 봤을 법 한 메시지가 적혀있는 것을 확인 할 수 있습니다. 이 Fault 메시지는 닷넷에서 처리할 수 없는 범주의 예외가 발생했거나, 예외 정보를 공개하지 않도록 설정되어 있는 경우에 생성되는 메시지 입니다.

기본적으로 WCF 서비스는 예외 정보를 공개하지 않도록 설정되어 있으므로, 개발을 하는 동안 자세한 예외 정보를 받기를 원한다면 이 설정 값을 바꿔주어야 합니다.

서비스의 환경 설정 파일(web.config/app.config) 에서 behavior element 밑에 있는 serviceDebug element 의 includeExceptionDetailInFaults의 속성값을 true로 바꿔주면 되는 것이죠.

<behaviors>
   <serviceBehaviors>
      <behavior name="MyBehavior">
         <serviceMetadata httpGetEnabled="true"/>
         <serviceDebug includeExceptionDetailInFaults="true"/>
      </behavior>
   </serviceBehaviors>
</behaviors>

 
이와 같이 설정을 한 후에 예외를 발생시켜 볼까요? 서비스에 두 숫자를 받아 나눗셈을 한 결과를 리턴하는 "Divide" 라는 메서드를 만들었습니다. 그리고, 간단하게 예외를 발생시키기 위해서, 0으로 다른 숫자를 나누도록 파라미터를 넘겨주었습니다. 그러면, 당연히 DivideByZeroException 이 발생하겠죠~

그랬더니~ 다음과 같은 Fault Message 를 클라이언트로 전송해 주는 것을 볼 수 있었습니다.
아~ 참고로 이 메시지를 확인하기 위해서 저는 Fiddler(피들러)를 사용했습니다.

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing">
   <s:Header>
      <a:Action s:mustUnderstand="1">
         http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/
dispatcher/fault
      </a:Action>
      <a:RelatesTo>urn:uuid:8e5a89f4-b765-45e3-b343-26c07fde57dd</a:RelatesTo>
   </s:Header>
   <s:Body>
      <s:Fault>
         <s:Code>
            <s:Value>s:Receiver</s:Value>
            <s:Subcode>
               <s:Value xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher">
                 
a:InternalServiceFault
               </s:Value>
           
</s:Subcode>
         </s:Code>
         <s:Reason>
            <s:Text xml:lang="en-US">Attempted to divide by zero.</s:Text>
         </s:Reason>
         <s:Detail>
            <ExceptionDetail xmlns="http://schemas.datacontract.org/2004/07/System.ServiceModel" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
               <HelpLink i:nil="true"/>
               <InnerException i:nil="true"/>
               <Message>Attempted to divide by zero.</Message>
               <StackTrace>
at Wcf_TroubleShooting.Service1.Divide(Int32 numerator, Int32 denominator) in C:\Users\RuAA\documents\visual studio 2010\Projects\Wcf-TroubleShooting\Wcf-TroubleShooting\Service1.svc.cs:line 19&#xD;
at SyncInvokeDivide(Object , Object[] , Object[] )&#xD;
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]&amp; outputs)&#xD;
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&amp; rpc)&#xD;
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&amp; rpc)&#xD;
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(MessageRpc&amp; rpc)&#xD;
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc&amp; rpc)&#xD;
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc&amp; rpc)&#xD;
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc&amp; rpc)&#xD;
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc&amp; rpc)&#xD;
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc&amp; rpc)&#xD;
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc&amp; rpc)&#xD;
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
               </StackTrace>
               <Type>System.DivideByZeroException</Type>
            </ExceptionDetail>
         </s:Detail>
   </s:Fault>
   </s:Body>
</s:Envelope>



딱 봐도 아시겠지만, 이 전에 봤던 Fault Message에 비해 확실히 더 자세한 내용을 담고 있는 것을 볼 수 있습니다.

사실 이러한 내용들을 다 알 필요는 없습니다. 닷넷으로 클라이언트를 개발한다면 다른 예외와 마찬가지로 try/catch 문을 이용하여 예외를 처리할 수 있을 테니까 말이죠~ 하지만, 개인적으로 이런 기본적인 내용들도 중요하다고 생각하는지라 하나의 포스팅을 통해 정리를 해보았습니다.

아직 할 얘기들이 많지만 너무 길어지면 지루해질 것 같아 여기서 줄일려고 합니다.

다음 포스팅에선 FaultContract 에 대한 설명과 함께 이를 이용해서 에러에 대한 메세지를 정의하고, 이를 클라이언트에 전달하는 방법, 그리고 그외에 에러를 핸들링 하는 방법에 대한 이야기를 써 볼까 합니다.

다음 포스팅을 기대하시는 분은 없으실 거라 예상이 되어 기대해달란 말을 하긴 어려울 것 같지만, 최대한 아주 아주 빠른 시일 내에 업데이트 할 수 있도록 하겠습니다. ^^