REST 서비스 템플릿

2011.05.02 09:00 from .NET/WCF

이번 포스트에선 REST 서비스를 만들 수 있는 간단한 방법~ REST 서비스 템플릿에 대해 얘기를 해볼까 합니다.
이 주제로 세미나(Your Smarter Visual Studio 2010)를 한번 했었는데, 그 때 시간이 짧아 많은 얘기를 전달하지 못한 것 같아 그 때 차마 하지 못했던 얘기들을 이번 포스팅에서 한번 주절거려 보겠습니다. 
세미나를 한 지가 꽤 오래됐는데 이제서야 포스팅을 하네요~ ^^;;;


그럼 시작합니다~ ^^

REST 서비스에 대한 자세한 설명은 과감하게 생략하도록 하겠습니다. 최근엔 REST에 대한 정보가 많아서 글 솜씨가 부족한 제가 굳이 설명하지 않아도 REST에 대해 알기 쉽게 설명한 글들을 큰 힘 들이지 않아도 찾을 수 있으실 겁니다 ㅎ

그래서, 이번 포스팅의 시작은 REST 서비스 템플릿을 다운받는 것부터 시작하겠습니다.

Visual Studio 프로젝트 템플릿을 다운 받는 방법은 크게 두 가지가 있습니다. 첫째는 Visual Studio를 통해서 다운 받는 것이고, 둘째는 직접 템플릿 다운 사이트(Visual Studio 갤러리 사이트)를 방문하여 다운 받는 것입니다.
두 방법 모두 절대 어렵지 않습니다. 여기선 Visual Studio를 사용하는 방법에 대해 알아보겠습니다.

우선,Visual Studio를 열고, 새 프로젝트를 만들기 위한 창을 띄웁니다. 그리고 좌측 메뉴에서 "온라인 템플릿"을 선택합니다. 그러면 잠깐 동안의 검색 이미지가 나타났다가, 아래 그림과 같은 화면이 나타나는데, 우측 리스트에 보면 "WCF REST Service Template 40(CS)" 아이템이 보입니다. 

 
이 아이템을 선택하고 프로젝트를 생성할 이름과 위치를 지정한 후에 확인 버튼을 클릭하면, 해당하는 템플릿을 다운받고, 바로 새로운 프로젝트를 생성해줍니다. 너무 쉽습니다~ 그죠? ㅎ 

REST 서비스 프로젝트가 생성되면 기본 파일들이 함께 생성되는데, 이 파일 중에 Service1.cs 파일이 실제 REST 서비스를 구현하기 위한 파일입니다. 이 파일을 보면 Service1 이라는 이름의 클래스 밑에 다음과 같은 메서드들이 정의되어 있는 것을 보실 수 있으실 겁니다. 

[WebGet(UriTemplate = "")]
public List<SampleItem> GetCollection() { … }

[WebInvoke(UriTemplate = "", Method = "POST")]
public SampleItem Create(SampleItem instance) { … }

[WebGet(UriTemplate = "{id}")]
public SampleItem Get(string id) {… }

[WebInvoke(UriTemplate = "{id}", Method = "PUT")]
public SampleItem Update(string id, SampleItem instance) { … } 

[WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
 
public void Delete(string id) { … }

 
REST 서비스에서 각 작업과의 상호 작용은 고유한 URIHTTP 표준 동사(GET, POST, PUT, DELETE) 를 통해 이루어 집니다. 기본적으로 만들어지는 위와 같은 메서드는 각 HTTP 표준 동사에 대한 메서드 특성 설정에 대한 예시를 보여줍니다.

WebGetAttribute 와 WebInvokeAttribute

WCF REST 서비스는 위와 같이 정의 된 메서드에 접근하기 위해 URI와 HTTP 동사를 메서드에 매핑해 주는데 이때 필요한 것이 WebGetAttributeWebInvokeAttribute 입니다.

WebGetAttribute는 메서드가 HTTP GET 요청에 대해 응답한다는 것을 알려줍니다. WebInvokeAttribute는 기본적으로 POST 요청에 응답하지만, PUT 과 DELETE 요청에 대한 응답도 지원합니다. PUT 및 DELETE 요청에 대해 응답하는 메서드를 정의하기 위해서는 WebInvokeAttribute.Method 속성을 설정해주면 됩니다. 설명이 좀 어려워 보이지만 위 코드를 보면 쉽게 이해할 수 있으실 겁니다. ^^


REST 서비스 구현

기본적인 부분은 모두 언급이 된 것 같습니다. 이제는 이를 이용하여 간단한 REST 서비스를 만들어보겠습니다.
만들려는 서비스는 회원 관리에 대한 것으로 새로운 회원의 입력, 회원 정보 조회, 회원 정보 수정 및 삭제를 할 수 있는 서비스를 구현해보도록 하겠습니다.

우선, 서비스에서 사용할 Member 클래스를 다음과 같이 정의하였습니다.

[DataContract]
public class Member
{
    [
DataMember(Order=0)]
   
public string Id { get; set; }
    [
DataMember(Order=1)]
   
public string Name { get; set; }
    [
DataMember(Order=2)]
   
public string Job { get; set; }
    [
DataMember(Name="PhoneNumber", Order=3)]
   
public string Phone { get; set; }
    [
DataMember(Order=4)]
   
public string Address { get; set; }
}

 
그리고, 실제 서비스의 구현은 다음과 같습니다.

[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MemberService
{
    [WebGet(UriTemplate = "GetAll")]
    public MemberList GetCollection()
    {
        BizMember bizMember = new BizMember();
        return bizMember.GetAll();
    }

 
    [WebInvoke(UriTemplate = "Add", Method = "POST")]
    public void Create(Member instance)
    {
        BizMember bizMember = new BizMember();
        bizMember.Add(instance);
    }

 

    [WebGet(UriTemplate = "{id}", ResponseFormat=WebMessageFormat.Json)]
 
   public Member Get(string id)
    {
        BizMember bizMember = new BizMember();
        return bizMember.Get(id);
    }

 

    [WebInvoke(UriTemplate = "{id}", Method = "PUT")]
 
   public void Update(string id, Member instance)
    {
        BizMember bizMember = new BizMember();
        bizMember.Update(id, instance);
    }
 

    [WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
    public void Delete(string id)
    {
        BizMember bizMember = new BizMember();
        bizMember.Delete(id);
    }
}


코드는 보시면 아시겠지만 그리 어렵지 않습니다. BizMember 라는 클래스에서 여러가지 작업이 이루어지는데 이 클래스 역시 그렇게 어려운 내용이 없기에 자세한 설명은 생략하겠습니다. 대신에 소스를 첨부시켜 놓을 테니 잠깐만 살펴보시면 알 수 있으실 겁니다. ^^;; BizMember 클래스에 대한 설명까지 하려면 내용이 너무 길어질 것 같아 그러니 양해 부탁드립니다~ 흠흠;;; 

앞에서 설명을 하지 않았는데 위 코드를 보면 WebGetAttribute와 WebInvokeAttribute에 UriTemplate 이라는 속성이 보이실겁니다. 이 속성은 서비스로 들어온 요청의 URI와 매핑을 시키는 속성이라고 생각하시면 됩니다.
예를 들어, 위의 코드에서 Create 메서드의 경우 UriTemplate 속성의 값이 "Add" 로 되어 있는데, 만약 이 서비스의 기본 주소가 "http://localhost:2853/MemberService"이라고 할 때, 요청 들어온 URI가 "http://localhost:2853/MemberService/Add" 일 때 Create 메서드를 호출 한다는 것입니다. 물론, Create 메소드의 WebInvokeAttribute에 정의되어 있듯이 "POST" 방식의 요청이어야 하겠죠~?

또한, 위 코드의 Get 메서드처럼 UriTemplate 속성의 값이 "{id}"인 경우가 있는데, 이는 메서드의 파라미터인 id의 값이 URI에 들어가는 형태가 되는 것입니다. 예를 들면, HTTP "GET" 방식으로 "http://localhost:2853/MemberService/ruaa" 의 주소로 요청이 들어오면, 위 코드에서의 Get 메서드가 호출되면서 Get 메서드의 id 파라미터 값으로 ruaa가 입력되는 것입니다.
 
그럼, 서비스의 기본 주소는 어떻게 설정할 수 있을까요? 궁금하지 않으신가요? ㅎ
기존 WCF 서비스에서는 web.config 파일을 사용했었는데, REST 서비스 템플릿은 조금 다릅니다.
global.asax 파일을 열어보시면 그 답을 찾을 수 있으실 겁니다.

private void RegisterRoutes()
{
    // Edit the base address of Service1 by replacing the "Service1" string below
    RouteTable.Routes.Add(new ServiceRoute("MemberService", new WebServiceHostFactory(),
                                            typeof(MemberService)));
}

 
위 코드를 보니 어디서 많이 봤다~ 싶으네요~ ㅎ
ASP.NET 4.0과 ASP.NET MVC 프로젝트를 한 번이라도 구현해보신 분들이라면 저랑 같은 생각을 하셨을겁니다. ASP.NET MVC 때 부터 사용한 라우팅 방법과 같은 방식을 사용하고 있네요~

아무튼, 위 코드 처럼 구현을 할 경우 서비스의 기본 주소는 "http://{서비스가 배포된 기본 주소}/MemberService"가 됩니다.


Help 페이지

이렇게 REST 서비스 템플릿을 이용하여 서비스를 구현하고, 빌드를 하면 기본적으로 서비스에 대한 help 페이지를 만날 수 있습니다. help 페이지의 주소는 서비스의 기본주소에 "/help" 가 붙는 형식입니다.
저 같은 경우, Visual Studio 개발 서버로 돌렸기 때문에 다음 그림에서 보듯이 help 페이지의 경로는 "http://localhost:2853/MemberService/Help" 가 되는 것입니다.


위 그림을 보시면 알겠지만, 각 서비스 끝점에 대한 간단한 설명을 볼 수 있고, 각 서비스 끝점의 메서드를 클릭하면 자세한 내용을 확인할 수 있습니다. 이는 WCF 서비스의 헬프 페이지와 아주 유사하죠~? 

자~ 이제 슬 마무리 지어 볼까요? ^^

이번 포스팅을 통해 REST 서비스의 많은 부분에 대해 알려드리진 못했지만 기본적인 부분은 모두 설명이 된 듯 합니다. 그리고 REST 서비스 템플릿을 이용하면 정말 쉽게 WCF를 이용한 RESTful 서비스를 구현할 수 있다는 것도 알게 되었습니다.

혹시나 추가적인 설명이 필요한 부분이나 질문이 있으신 분들은 댓글 남겨주시면 성심성의껏(?) 답변 해드리겠습니다.
그리고, 첨부한 소스 코드를 참조하시어 테스트 해보시면 이번 포스팅에서 설명한 많은 부분이 이해가 되실 것 같습니다. 

저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 1
오랜만에 인사 드립니다~
해가 바뀌고, 벌써 두 달이 거의 지나가고 있는 시점에 이제야 다시 글을 올리게 되었습니다.
참, 부끄럽기 그지 없군요~ ^^;;
부끄럽긴 하지만 인사는 드려야죠!! 새해 복!! 많이 받으쌉싸리와용~

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

이번 포스팅은 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에서 제공하는 몇 가지 툴들이 있는데 이런 툴들에 대한 설명이 될 것 같습니다.

그럼 다음 포스팅때까지 안녕히~ ^^
저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 2

WCF Troubleshooting (2)

2010.11.29 09:00 from .NET/WCF
일주일 만에 돌아온 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 메시지의 내용과 앞의 예제 코드, 그리고 클라이언트의 결과 화면을 비교해보면 이번 포스팅의 내용이 이해하기 쉬울 같습니다. ^^

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

신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 0

WCF Troubleshooting (1)

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

이번 포스팅 부터는 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 에 대한 설명과 함께 이를 이용해서 에러에 대한 메세지를 정의하고, 이를 클라이언트에 전달하는 방법, 그리고 그외에 에러를 핸들링 하는 방법에 대한 이야기를 써 볼까 합니다.

다음 포스팅을 기대하시는 분은 없으실 거라 예상이 되어 기대해달란 말을 하긴 어려울 것 같지만, 최대한 아주 아주 빠른 시일 내에 업데이트 할 수 있도록 하겠습니다. ^^
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 0
여름의 정점을 지나가고 있는 듯 합니다. 날씨가 무진장 덥네요,,
장마가 끝나면 폭염이라는데,, ㅡ,.ㅡ;;;
식상한 멘트이긴 하겠지만, 이런 날일 수록 정말 건강이 중요한 것 같습니다.
덥다고 에어컨 바람만 쐬면 냉방병 걸리고, 밤에 잘때도 너무 에어컨 틀어놓으면 감기 걸리니깐,,
아프지 않는게 최고죠~ 특히, 저 같이 혼자 자취하는 자취남들은,, ^^
아무쪼록 건강하세요~


이번 아티클의 주제는 Windows Service를 이용한 호스팅입니다.

윈도우즈 서비스에 대해서 모르는 분이 계실까요?
네~ 계실 수 있죠,, 그래서 간략하게 윈도우즈 서비스에 대해 설명을 하고 넘어가도록 하겠습니다.

What is Windows Service?

NT 서비스라고 알려져 있기도 한 윈도우즈 서비스는 자체의 Windows 세션에서 실행되며, 윈도우즈가 구동을 하고 있는 동안 계속 동작을 해야하는 이러한 작업이 필요할 때, 윈도우즈 서비스의 형태로 구현하여 사용할 수 있습니다. 또한, 윈도우즈 서비스는 윈도우즈의 시작과 함께 자동으로 시작할 수 있기 때문에, 서버가 동작함과 동시에 항상 동작해야하는 응용 프로그램이라면 윈도우즈 서비스로 구현하는 것을 고려해 볼 필요가 있을 듯 합니다.

윈도우즈 서비스의 경우는 따로 UI를 가지고 있지 않기 때문에, 윈도우즈 사용자 또는 관리자가 윈도우즈 서비스의 구동 상태를 상세하게 확인할 수는 없습니다. 다만, 윈도우즈에 기본으로 제공되는 서비스 제어 관리자를 통해 서비스의 시작, 중지, 일시정지 등을 컨트롤 할 수 있습니다. (아래 그림은 서비스 제어 관리자의 모습입니다.)


그림을 보니 윈도우즈 서비스가 어떠한 것들을 말하는 것인지 알겠죠? ^^ (역시, 백문이 불여일견,, 엣헴~)

Windows 서비스 응용프로그램에 대한 소개와 개발 방법에 대해 좀 더 자세한 정보를 원하시는 분은 다음 링크를 참고하시기 바랍니다.

"Windows 서비스 응용 프로그램 소개"

이제 본격적으로 이 윈도우즈 서비스를 이용하여 WCF 서비스를 호스팅하는 방법에 대해 적어보겠습니다.
고고씽~

Windows Service를 이용한 WCF 서비스 호스팅

WCF 서비스를 호스팅하는 방법에는 IIS 호스팅, WAS 호스팅, 그리고 셀프 호스팅으로 나뉘어진다고 얘기했었습니다.

그럼, Windows Service를 이용한 호스팅은 어디에 속할까요?
네~!! 당연히 셀프 호스팅에 속합니다. 그리고, 셀프 호스팅에 속한다는 말은 직접 ServiceHost 클래스를 이용하여 호스팅을 구현 해야 한다는 말이기도 합니다.

결국, 여기서 제가 하고 싶은 말은 이것입니다.
 "Windows Service를 이용하여 WCF 서비스를 호스팅하기 위해서는 ServiceHost 클래스를 이용하여 서비스 호스팅하는 부분을 직접 구현해야 한다."
 
WCF 서비스를 호스팅하기 위한 특별한 코드가 필요한 것은 아닙니다. 여타의 다른 윈도우즈 서비스를 개발하는 것과 같이 개발을 하고, WCF 서비스를 호스팅하는 코드만 추가를 해주면 된다는 것입니다~!!
아마, 윈도우즈 서비스를 한번이라도 개발 해 보신 분은 어렵지 않게 개발을 할 수 있을 것 같습니다.

우선, 윈도우즈 서비스 응용프로그램을 만들기 위해 Visual Studio에서 새 프로젝트를 생성할 때, "Windows 서비스" 템플릿을 선택합니다.


프로젝트를 생성하면 Service1.cs 와 Program.cs 파일이 생성되어 있음을 확인할 수 있습니다.
Program.cs 파일은 이 응용 프로그램의 진입점을 가지고 있으며, 실제 서비스가 동작을 할 때의 코드는 Service1.cs 파일에 구현을 하면 됩니다.

그리고, Service1.cs 파일을 확인하면 Service1 클래스가 선언되어 있으며, 이 클래스는 ServiceBase 클래스를 상속 받는 것을 확인할 수 있습니다.
그리고, Service1 클래스에는 기본적으로 OnStart와 OnStop 메소드가 재 정의(override)되어 있는데 메소드의 이름으로 짐작할 수 있겠지만, 각각 서비스가 시작할 때 와 서비스가 멈췄을 때의 동작을 수행하는 메소드입니다.

이 메소드 외에, OnContinue, OnPause, OnShutdown 메소드를 재정의하여 서비스를 일시정지에서 다시 시작했을 때, 서비스를 일시정지 했을 때, 그리고 컴퓨터가 종료될 때의 동작을 구현할 수 있습니다.

이번 아티클의 주제는 Windows 서비스를 이용한 WCF 서비스의 호스팅이니 만큼, Windows 서비스 구현에 대한 자세한 내용은 앞서 걸어놓은 MSDN의 링크로 대신하고 넘어가겠습니다.
(Windows 서비스 응용 프로그램의 구현에 대한 자세한 내용을 모르셔도 아래 내용을 따라 하시면, 아마 무사히 WCF 서비스를 호스팅 할 수 있으실겁니다. )

음,, 일단 우리가 만들 Windows 서비스의 이름을 먼저 변경해보도록 하겠습니다. 이름을 변경하지 않아도 별 상관은 없지만 우리가 만든 서비스란 것을 알아보기 위해서 변경하는게 낫겠죠~ 서비스의 이름을 변경하는 것은 어렵지 않습니다. Service1 클래스의 생성자에서 바꿔줘도 되고, Service1.Designer.cs 파일을 확인하면 Service1 의 partial 클래스가 정의되어 있는데, 이곳에 위치한 InitializeComponent 메소드 내에서 변경해줘도 됩니다. 저는 InitializeComponent 메소드 내에서 다음과 같은 코드로 서비스의 이름을 지정 해주었습니다.

private void InitializeComponent()

{

     components = new System.ComponentModel.Container();

  this.ServiceName = "RuAA WCF Service"; // 서비스의 이름 변경

}


이제 본격적으로 WCF 서비스를 호스팅 해보도록 하겠습니다.
우선, WCF 서비스에서 사용할 ServiceContract와 DataContract 들을 선언해주어야 겠죠. 다음과 같은 인터페이스와 클래스들을 선언해주었습니다.

// ServiceContract 정의
[
ServiceContract]

public interface IProductService

{

    [OperationContract]

    Product GetProductInfo(int id);

}


// DataContract 정의
[
DataContract]

public class Product

{

    [DataMember]

    public int ID;

    [DataMember]

    public string Name;

    [DataMember]

    public string Company;

    [DataMember]

    public int Price;

}

// 서비스 구현

public class ProductService : IProductService

{

    List<Product> productList;

 

    public ProductService()

    {

        productList = new List<Product>();

        productList.Add(new Product {

            ID = 1,

            Name = "ABC Chocolate",

            Company = "RuAA Inc.",

            Price = 5300

        });

    }

 

    public Product GetProductInfo(int id)

    {

        var item = (from p in productList

                    where p.ID == id

                    select p).FirstOrDefault();

 

        return item;

    }

}

항상 그랬지만, 서비스의 역할은 심플합니다. 복잡한 서비스를 만드는게 목표는 아니잖아요~ ㅎ

이제 이렇게 구현된 서비스를 호스팅하는 일 만을 남겨두었습니다.

앞에서도 밝혔듯이 Windows 서비스에서 WCF 서비스를 호스팅 하는 것은 Self Hosting 에 포함되는 것이기 때문에 ServiceHost 클래스를 직접 구현해야 합니다.

근데 이 코드를 어디에 위치해야 할까요? 예상하신 분들이 분명 계실겁니다.
바로 바로 바로~~!! Service1 클래스의 OnStart 메소드입니다. 

또한, 생각해야 할 것이 있습니다.
Windows 서비스가 멈추었을 때, 당연히 WCF 서비스의 호스팅을 멈추어줘야 겠죠. 그래서 OnStop 메소드 내부에 WCF 서비스의 호스팅을 멈추게 하는 코드도 포함이 되어야 할 것입니다.

Service1 클래스를 다음과 같은 코드로 구현해보았습니다.

// ServiceHost 클래스의 인스턴스를 클래스의 멤버로 선언하여 Service1 클래스의 메소드에서 접근이 가능하도록 한다.
private
ServiceHost svcHost;

// 윈도우즈 서비스가 시작할 때의 동작을 구현한다.

protected
override void OnStart(string[] args)

{

    // ServiceHost 인스턴스 생성
    string baseUrl = "http://10.30.101.84:9090/ProductService";

    this.svcHost = new ServiceHost(typeof(ProductService), new Uri(baseUrl));

    // 엔드포인트 추가
   
this.svcHost.AddServiceEndpoint(typeof(IProductService),

        new BasicHttpBinding(),

        "");

           

    // 메타 데이터 엔드포인트를 위한 Behavior 설정
    ServiceMetadataBehavior metaBehavior = new ServiceMetadataBehavior();

    metaBehavior.HttpGetEnabled = true;

    svcHost.Description.Behaviors.Add(metaBehavior);

 

    // 메타 데이터 엔드포인트 추가

    this.svcHost.AddServiceEndpoint(typeof(IMetadataExchange),

        MetadataExchangeBindings.CreateMexHttpBinding(),

        "mex");

 

    svcHost.Open();  // WCF 서비스 오픈

    ServiceEndpoint endpoint = this.svcHost.Description.Endpoints[0];

 

    // 윈도우즈 이벤트 로그에 정보를 남긴다.

    EventLog.WriteEntry(endpoint.Contract.Name + " Started"

        + " listening on " + endpoint.Address

        + " (" + endpoint.Binding.Name + ")",

        System.Diagnostics.EventLogEntryType.Information);

}

 

// 윈도우즈 서비스가 멈췄을 때의 동작을 구현한다.

protected override void OnStop()

{

    this.svcHost.Close();

    EventLog.WriteEntry("RuAA Service Stopping", EventLogEntryType.Information);

}
 
위의 코드에서 그렇게 어려운 점은 보이지 않습니다. Self Hosting을 해보셨던 분이라면 말이죠~
혹시나, Self Hosting을 해보지 못하신 분이 있다면, 첫 WCF 만들기 아티클을 참고해주시기 바랍니다.

각 주요 코드에 주석도 남겨놨으니 따로 긴 설명은 필요없을 듯 합니다. ^^

이제, 서비스의 구현은 모두 끝이 났습니다. 하지만, 우리가 만든 이 Windows 서비스를 컴퓨터에 설치하기 위해서는 설치 관리자가 필요합니다. (설치 관리자에 대한 자세한 설명은 이곳으로~)
Service1.cs 파일의 디자이너 보기에서 마우스 우측 클릭한 후 나타나는 메뉴에서 "설치 관리자 추가"를 선택합니다.


그러면 프로젝트에 ProjectInstaller.cs 라는 파일이 생기는데, 이 클래스의 디자인 뷰를 확인하면,  serviceProcessInstaller1, serviceInstaller1 이라는 이름의 컨트롤들이 포함되어 있는 것을 확인할 수 있습니다.

이 컨트롤들의 속성을 다음 그림과 같이 수정해보겠습니다.



이제서야, 정말 Windows 서비스를 설치할 모든 준비가 끝이 났습니다.
이 Windows 서비스를 설치하기 위하여 Visual Studio 명령 프롬프트를 실행시킵니다. 이때, 관리자 권한으로 실행시켜 주셔야 합니다. 그렇지 않으면 Windows 서비스가 제대로 설치가 되지 않는 경우가 있더라구요~ ㅎ

그리고, 이 프로젝트 폴더의 bin/Debug 폴더로 이동한 후에 다음 그림과 같이, Installutil 이란 명령어를 이용하여 우리가 만든 Windows 서비스를 설치합니다. (Windows 서비스 설치에 대한 자세한 설명은 이곳으로~!!)
참고로, 설치 파일은 프로젝트를 빌드한 후에 생성된 exe 파일입니다.


위의 그림처럼 "트랜잭트 설치가 완료되었습니다." 란 메세지가 떨어졌다면 아무 에러없이 Windows 서비스가 설치되었다는 말입니다. 그럼, 서비스 제어 관리자에서 확인해 보도록 하겠습니다.


네,, Windows 서비스가 올라와있는 것을 볼 수 있네요~ 그럼 시작 버튼을 클릭하여 Windows 서비스를 시작할 수 있습니다. 그리고, Windows 서비스가 시작하면서 우리가 구현한 WCF 서비스가 호스팅되겠죠,, ㅎㅎ

그럼, 이 WCF 서비스가 제대로 호스팅 되고 있는 것인지 확인을 해보아야 겠죠,,
역시나, 지금까지 그래왔듯이 콘솔 어플리케이션을 이용하여 확인해보도록 하겠습니다.

솔루션에 콘솔 어플리케이션 프로젝트를 추가하고, 이 프로젝트에 서비스 참조를 시켜주었습니다. 이때 너무나도 당연하겠지만, 서비스의 주소는 ServiceHost 인스턴스에 추가 시켜준 엔드 포인트의 주소를 넣어주셔야 합니다.ㅎ


위의 그림처럼, 서비스를 제대로 찾으면~ 모든 것이 OK!!! 입니다. ㅎㅎ

이 서비스를 이용한 콘솔 어플리케이션 코드는 생략해도 되겠죠?? ㅎㅎ (이번 글이 너무 길어진 것 같아,, ^^;;;;)
그래서~~~ 결과 화면만 보여드리겠습니다 ㅎㅎ



후아~ 결과가 잘 나오는 군요,, ^^
이로써, 이번 포스팅도 무사히(?) 끝을 낼 수 있게 되었습니다. ㅎㅎㅎ

이번 포스팅은 Windows 서비스에 대한 설명과 구현에 대한 내용을 함께 적다보니 조금 길어진 것 같습니다. 물론 한번이라도 Windows 서비스 응용 프로그램을 구현해보신 분이라면 아는 내용들이겠지만, 혹시나 모르시는 분도 있을 것 같아 자세한 내용까지 설명드리지 못했지만 최소한의 내용을 포함시켰습니다.

조금 내용이 길어졌지만 끝까지 읽어주신 분들께 심심한 감사의 인사를 드리며, 저는 이만 퇴근(?)하겠습니다 ㅎㅎ
저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 1

WCF를 공부하면서 자료의 부족함을 많이 느끼며, 유익한 사이트들을 리스트로 정리해 놓아야 겠다는 생각을 했다.
브라우저의 즐겨찾기로 정리할 수도 있지만 컴퓨터의 자료란 언제 어떻게 날라갈지 모르는 거라서,,, ㅎ

아직 알고 있는 사이트가 적긴 하지만, 알게 되는 대로 계속 업데이트를 할 예정입니다.
WCF에 관심이 있으신 개발자 분들도 한번쯤 확인 해 볼만한 사이트들을 모아 보겠습니다. ^^

저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 0
바야흐로, 여름입니다.
아ㅡ 정말 이놈의 귀차니즘 덕분에 너무 띄엄띄엄 포스팅이 되는 것에 대해 죄송하다는 말씀 먼저 드려야할 것 같습니다.
여름이라 더워서 그렇다고 핑계대지 않을게요, 휴가 시즌이라 놀고 싶어서 그렇다고 핑계대지 않을게요~ ;;;
잡담은 여기서 줄이고, 힘을 내어, 이번 포스팅을 시작해 보겠습니다. 레츠 고우~
 
지난 포스트의 주제는 WAS 호스팅이었습니다. 이번 주제는 조금 다르긴 하지만 지난 포스트에 이어서 Hosting과 관련된 내용을 적어볼까 합니다.

WCF 서비스를 호스팅하기 위해 가장 쉬운 방법이 무엇인지 다들 아시죠?
제 개인적인 생각인지는 모르겠지만, Visual Studio를 사용하여 WCF 서비스를 만든다면, 아마도~ 가장 쉬운 호스팅 방법은 IIS 호스팅일 것입니다. 솔루션 만들고 별 수정없이 바로 호스팅이 가능하니깐요,,

갑자기 왜 IIS 호스팅에 대한 얘기를 꺼내냐구요?
음,, 오늘 제가 꺼낼 이야기가 IIS 호스팅일 때 WCF 서비스에 ASP.NET의 몇 가지 특성을 적용할 수 있는 방법에 대한 내용을 적으려다 보니... 네!! 결국, 제가 하고 싶은 얘기는 이번 포스팅에서 나오는 방법들이 IIS 호스팅을 바탕으로 한다는 것을 명심(?)해 달라는 것입니다. ㅎㅎ

닷넷 웹 서비스 와 WCF 서비스

WCF 서비스 얘기를 할 때, 가장 비교를 많이 하는 것이 아마 .NET 웹 서비스 일 듯 합니다. (지금도 이 닷넷 웹 서비스에 대해 얘기를 하려 하구요~ ㅎ)

닷넷 웹 서비스와 WCF 서비스의 가장 큰 차이는 무엇일까요?
구현하는 방법에 대한 차이도 있겠지만, 그것보단 WCF 서비스가 HTTP 프로토콜 이외의 프로토콜(net.tcp, net.pipe, MSMQ)을 이용하여 접근이 가능하게 호스팅할 수 있다는 점일 것입니다. (WAS를 이용한 호스팅 참조)

닷넷 웹 서비스는 WCF 서비스와는 다르게 HTTP 프로토콜만 지원합니다. 그리고 이는, ASP.NET HTTP 파이프라인을 따르고 있습니다.

"아ㅡ ASP.NET 파이프 라인은 또 뭔가요?" 라고 원망 섞인 소리가 여기까지 들리는 것 같습니다. 저도 아직 실력이 미천한 개발자라 자세히 설명드릴 수는 없습니다.
간단하게 설명 드리자면, ASP.NET 에서 Http 프로토콜을 이용하여 들어오는 요청(request)과 응답(response)을 처리하기 위한 파이프라인입니다. 즉, 어떤 요청에 대해서 어떻게 필터링을 수행하고, 어떤 어플리케이션을 호출할 것인지를 처리하며, 파이프라인을 통해서 그에 대한 응답을 전송하는 것입니다.

자세한 내용을 알고 싶은 분은 다음을 참고 하시면 될 것 같습니다.
Securely Implement Request Processing, Filtering, and Content Redirection with HTTP Pipelines in ASP.NET

닷넷 웹 서비스는 이렇게 ASP.NET HTTP 파이프라인을 사용하기 때문에 많은 ASP.NET 의 특징을 함께 사용할 수 있다는 장점을 가지고 있습니다. 이러한 장점에는 다음과 같은 것들이 포함 됩니다. 

  • Authentication
  • Url/File authorization
  • Impersonation
  • Session state
  • Request cache
  • Globaliztion
  • Etc

목록을 보니 인증과 권한에 대한 것, 그리고 세션과 관련한 것들이 있네요~

이제 WCF 서비스 얘기를 해볼까요?
WCF 서비스는 닷넷 웹 서비스와는 다르게 non-HTTP 프로토콜들을 지원해줍니다. 그리고, 이러한 장점을 위하여 프로토콜에 독립적인 디자인을 사용하게 된 것입니다.

조금 둘러서 얘기를 했지만, 제가 하고 싶은 말은 이것입니다. 
"닷넷 웹 서비스와는 다르게 WCF 서비스는 ASP.NET HTTP 파이프 라인을 따르지 않는다!!"


음,, 그렇군요. WCF 서비스는 ASP.NET HTTP 파이프 라인을 따르지 않는군요... 앗~ 그렇다면 닷넷 웹 서비스의 경우 ASP.NET HTTP 파이프 라인을 따랐기에 여러가지 ASP.NET의 특성을 사용할 수 있었는데,, 그럼, WCF 서비스에서는 ASP.NET의 특성들을 사용할 수 없는걸까요??

네~!! 기본적으로는 그렇습니다.
하!지!만!!! ASP.NET 에는 여러 유용한 특성들이 존재했기에 이를 완전히 버리기는 아까웠을겁니다. WCF 서비스를 위해서 같은 특성들을 다시 만들기 보다는 기존에 있던 것들을 가져다 쓰는 방향으로 개발하고 싶었겠지요~(제 개인적인 생각입니다. 아니면 말구요~ ㅎ) 

어떤 이유인지는 확실치 않지만, 어찌됐든 중요한 것은, WCF 서비스에서도 기존의 ASP.NET 특성들을 (전부는 아니고 일부분의 특성들을) 사용할 수 있는 방법을 제공해주고 있다는 것입니다. 단, HTTP 프로토콜을 사용하는 WCF 서비스에서만요~

이번에도 역시 둘러둘러~ 이제서야 본론으로 들어온 것 같습니다 ^^
그럼, 이제 본격적으로 WCF 서비스에서 ASP.NET의 유용한 기능들을 사용하기 위한 방법에 대해서 얘기해보도록 하겠습니다.


WCF 서비스에서 Session 사용하기

이번 포스팅에서는 ASP.NET의 특징들 중에서 간단하게 Session을 사용하는 예제를 구현해보려 합니다. (차후에 WCF의 보안에 대해 포스팅을 할 때에는 Impersonation 과 관련한 예제도 보여드릴 수 있을 것 같습니다.)
여기서 Session은 WCF 의 인스턴스를 생성할 때의 모드인 InstanceContextMode.Session 과는 전혀 무관합니다. 다들 알고 계시리라 생각하지만 혹시나 싶어서요~ ㅎ

우선, ASP.NET의 특징들을 사용하기 위해서는 크게 두 가지의 설정이 필요합니다.

첫 번째, Application Level 에서의 설정이 필요한데, 이는 WCF 서비스 프로젝트의 web.config에서 <system.serviceModel> 의 자식 요소인 <serviceHostingEnvironment> 요소의 속성 aspNetCompatibilityEnabled 의 값을 true로 명시하여 설정할 수 있습니다. 

두 번째로, Service Level 에서의 설정이 필요합니다. 이는 WCF 서비스를 구현하는 클래스에 AspNetCompatibilityRequirements 특성을 통해 설정을 할 수 있습니다.

코드를 보면서 하나씩 해보도록 하죠~

WCF 솔루션을 하나 만듭니다. 좀 편하게 작업을 하기 위해서 셀프 호스팅보다는 Visual Studio 에서 제공해주는 "WCF 서비스 응용 프로그램" 템플릿을 사용하도록 하겠습니다.

그리고, 다음과 같이 서비스 계약을 위한 interface와 WCF 서비스에서 사용할 개체인 Product 클래스를 정의했습니다.

[ServiceContract]

public interface IProductService

{

    [OperationContract]

    Product GetProduct(string ticker);

}

 

[DataContract]

public class Product

{

    [DataMember]

    public string Name;

    [DataMember]

    public int calls;

    [DataMember]

    public double price;

    [DataMember]

    public string RequestedBy;

}


이 다음에 할 일은 당연히 서비스를 구현하는 것이겠죠.
이 서비스에서는 session을 사용하여 현재 메서드가 호출되는 횟수를 기록, 유지하도록 했습니다.

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]

public class ProductService : IProductService

{

    public Product GetProduct(string ticker)

    {

        Product p = new Product();

        int nCalls = 0;

        // I .

        if (HttpContext.Current.Session["cnt"] != null)

            nCalls = (int)HttpContext.Current.Session["cnt"];

        HttpContext.Current.Session["cnt"] = ++nCalls;

 

        p.Name = "Caramel Latte";

        p.calls = nCalls;

        p.price = 2500;

        p.RequestedBy = "RuAA";

          

        return p;

    }

}


이 코드에서 다시 한번 유의해서 보아야 할 부분은 역시 ProductService 클래스 위에 선언 된 AspNetCompatibilityRequirements 특성입니다. 그 값을 Required 로 주었네요~
이 부분은 앞에서 ASP.NET 특성들을 사용하기 위한 설정 중 Service Level에서의 설정에 해당하는 것이었습니다.

그럼, 이제 Application Level에서의 설정을 해주어야 겠군요. 
web.config 파일을 다음과 같이 수정합니다.

<system.serviceModel>

  <behaviors>

    <serviceBehaviors>

      <behavior>

        <serviceDebug includeExceptionDetailInFaults="true"/>

      </behavior>

    </serviceBehaviors>

  </behaviors>

  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />

</system.serviceModel>


밑줄 쳐 있는 부분을 주목하시면 되겠습니다~ ㅎ
이 부분을 추가함으로써 첫 번째 Application Level 에서의 설정까지 완료한 것입니다.
설정이라고 하기엔 너무 간단한가요? ㅎ

이제, 이 서비스를 검증해보아야 겠죠,, 세션을 잘 유지하는지,,
매번 그래왔듯이 콘솔 어플리케이션을 이용하여 클라이언트를 간단히 만들어 보겠습니다.

서비스 참조를 하고, 다음과 같은 코드를 작성하였습니다. 

static void Main(string[] args)

{

    ProductServiceClient client = new ProductServiceClient();

    Product p = null;

    for (int i = 0; i < 5; i++)

    {

        p = client.GetProduct(i.ToString());

        Console.WriteLine(" : {0}", p.calls.ToString());

        Console.WriteLine(" : {0}", p.Name);

        Console.WriteLine(" : {0} ", p.price.ToString());

        Console.WriteLine(" : {0}", p.RequestedBy);

        Console.WriteLine();

    }

}


이 코드에선 그렇게 중요한 부분이 없습니다. 단순히 서비스의 GetProduct 메서드를 연속해서 5번 호출을 해주는 것 밖엔,, 복잡한건 싫으니깐 이렇게 간단히~ ㅎ

그리고 실행을 해보도록 하죠~
 

앗~ 뭔가 이상합니다. 우리가 예상했던 그런 결과가 나오지 않는군요. 세션이 유지가 되었다면 호출 횟수의 값이 1씩 증가하여 1~5의 값을 보여주어야 할텐데 말이죠.......

이런 결과가 나오는 이유는 바로,, Session을 사용하기 위해서는 클라이언트에서 쿠키를 허용해주어야 하는데,  기본 HTTP 바인딩인 basicHttpBinding 과 wsHttpBinding이 기본적으로 쿠키를 허용하지 않기 때문입니다.
HTTP 바인딩에서 쿠키를 허용해주기 위해선 config 파일에서 binding 태그의 allowCookies 속성의 값을 true 로 바꿔주시면 됩니다. 다음과 같이 말이죠~

<basicHttpBinding>

    <binding name="BasicHttpBinding_IProductService" closeTimeout="00:01:00"

        openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

        allowCookies="true" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"

        maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"

        messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"

        useDefaultWebProxy="true">

    ……

</ basicHttpBinding>


이제는 모든 설정이 완벽하게 끝이 난 것 같군요.
다시 실행을 해 보겠습니다.


아~ 이제 예상했던대로 결과 값이 나오는 것 같군요.. ㅎ

문득, 서비스의 InstanceContextMode의 값이 바뀌면 어떻게 될지 궁금해지지 않으신가요? ㅎ
한번 직접 해보시면 알겠지만 이 세션은 InstanceContextMode의 값(PerSession, PerCall, Single)이 무엇이 되든지 간에 유지됩니다. (다들 한번씩 해보시길~ ^^)

이번 포스팅은 여기까지하고 줄이도록 하겠습니다.

사실 이 포스팅은 제가 월드컵이 시작할 때 같이 시작했었는데, 결국 월드컵이 끝날 때 같이 끝나게 되었네요,,
이 놈의 귀차니즘 덕분에 포스팅이 항상 늦게 올려져서,, 정말 죄송한 마음밖엔 없는 것 같습니다.
일을 하면서 포스팅을 한다는 것 자체가 쉬운 일이 아님을 깨달았습니다.(MVP 분들이 존경스러워 지는군요,,ㅎ) 그래도 포기하면 안되겠죠,, ㅎ

다음 포스팅 때 뵙겠습니다. 꾸벅~
저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 0

WCF - Serialization

2010.04.27 09:00 from .NET/WCF
안녕하세요~ ^^
오늘 너무 오랜만에 WCF 포스팅을 하게 되었습니다. 자주자주 인사를 드려야 하는데 그렇게 하지 못해 너무 죄송합니다.
오늘 감기 기운 덕분에 약을 먹어놨더니 정신이 헤롱헤롱 하네요 ^^;;
글이 조금 이상하더라도 하해(?)와 같은 마음으로 이해해주시길 바랄께요~


이번 글에서는 serialization(직렬화)에 대해 얘기를 해볼까 합니다.

우선, serialization이 무엇인지 알아야 하겠죠?
컴퓨터 용어에서 serialization은 종종 하나의 오브젝트를 바이트의 배열로 전환(converting)시키는 것을 의미합니다. 하지만 WCF 에서의 serialization은 이러한 의미로 사용되지는 않구요, 하나의 오브젝트(or 닷넷 클래스)를 XML Information Set(XML Infoset) 으로 전환시킨다는 의미로 사용됩니다.

아직은 조금 이해하기가 어렵나요? 음,, 아마 이번 포스팅을 끝까지 읽으면 이해가 되실 겁니다. ^^
이제부터 WCF에서의 Serialization에 대해 조금 더 자세하게 얘기해보겠습니다.

WCF에서는 아래와 같은 네 가지의 serialization을 위한 클래스를 제공합니다.

  • DataContractSerializer
    : WCF 에서 사용하는 기본 serializer 를 제공한다.
  • NetDataContractSerializer
    : 타입에 대한 추가적인 정보를 제공하는 serializer
  • XmlSerializer
    : .NET 2.0에서 제공되었던 serialization을 수행한다.
  • DataContractJsonSerializer
    : serialization 포맷으로 JSON을 제공한다.


이번 포스팅에서는 이러한 클래스들이 직렬화를 했을 때 어떤 결과물을 보여주는지에 대해 써볼까 합니다. 이러한 내용들이 WCF 서비스를 만들때 직접적인 도움이 되진 않겠지만, WCF 서비스를 이해하고, 목적에 맞는 서비스를 만들기 위한 커스터마이징을 하기 위해선 도움이 될 수 있을 것입니다.

DataContractSerializer

DataContractSerializer는 WCF에서 사용하는 기본 serialization을 수행합니다. (이후 부터는 serialization 대신 "직렬화"를 사용하겠습니다.) 이 serializer를 사용하기 위해서는 직렬화가 되는 클래스에 [DataContract] 특성을 지정하여 주면 되는 이 방법은 WCF 서비스를 만들때 많이 사용했던 방법이기에 다들 익숙하리라 생각됩니다.

DataContractSerializer 클래스가 어떤 형태로 직렬화를 수행하는지 알아보도록 하겠습니다. 우선, 다음과 같은 클래스를 생성합니다.

[DataContract]

public class Employee

{

    public Employee()

    {

    }

             public Employee(int employeeID, string firstName, string lastName)

             {

        this.EmployeeID = employeeID;

        this.FirstName = firstName;

        this.LastName = lastName;

             }

 

    [DataMember]

    public int EmployeeID { get; set; }

 

    [DataMember]

    public string FirstName { get; set; }

 

    [DataMember]

    public string LastName { get; set; }

}


그리고, 콘솔 어플리케이션에서 다음과 같은 코드를 작성합니다.

using System.Runtime.Serialization;

using System.IO;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            Employee e = new Employee(101, "Tae kyeom", "Oh");

            FileStream writer = new FileStream("sample.xml", FileMode.Create);

 

            DataContractSerializer ser = new DataContractSerializer(typeof(Employee));

            ser.WriteObject(writer, e);

            writer.Close();

        }

    }

}


이 코드를 실행을 시켜보면~ 프로젝트의 bin\Debug 폴더안에 sample.xml 파일이 생성됩니다.
코드는 간단하기에 이해하는데 큰 어려움은 없을 것 같지만, 간단히 설명하자면,,, Employee 클래스 인스턴스를 생성하고, 이 오브젝트를 XML 형태로 직렬화를 시키고 이에 대한 내용을 sample.xml 파일에 썼습니다. 그리고, 직렬화 방법은 WCF 에서 기본으로 사용하는 DataContractSerializer를 사용했구요~

그럼, sample.xml 파일의 내용을 확인 해보겠습니다.

네~ 다음과 같은 내용을 보여주고 있네요, 딱 봐도 Employee 인스턴스가 가지고 있는 데이터를 XML로 표현해주고 있다는 것을 알 수 있습니다.

여기서 다시 한번 더 되새겨봐야 할 것은 WCF에서 [DataContract] 특성이 적용된 클래스의 경우 서비스에서 클라이언트로 전달될 때, 위 모습과 같은 형태로 전달된다는 것입니다.


XmlSerializer

XmlSerializer는 이 전 닷넷 버전에서도 지원해주었던 클래스이며, 이는 ASP.NET 웹 서비스에서 사용하던 직렬화 방법을 제공해줍니다.
이러한 직렬화 방법을 WCF 에서도 사용할 수 있으며, 이는 ASP.NET 웹 서비스와 호환이 가능하다는 장점이 있죠. 또한, 이것은 웹 서비스를 WCF로의 전환하는 것이 그리 어려운 일이 아니라는 것을 의미합니다.

XmlSerializer는 public 접근자의 기본 생성자, 필드, 그리고 프로퍼티를 직렬화 시켜줍니다.

Employee 클래스를 다음과 같이 조금 수정해보았습니다.

public class Employee

{
    public int EmployeeID { get; set; }

 

    public string FirstName { get; set; }

 

    public string LastName { get; set; }

 

    public Employee()

    {

    }

 

public Employee(int employeeID, string firstName, string lastName)

{
           
this.EmployeeID = employeeID;

            this.FirstName = firstName;

            this.LastName = lastName;

}

}


그리고, 이 클래스를 XmlSerializer 를 이용하여 직렬화 시켜보겠습니다.


using System.IO;

using System.Xml.Serialization;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            Employee e = new Employee(101, "Tae kyeom", "Oh");

            FileStream writer = new FileStream("sample.xml", FileMode.Create);

 

            XmlSerializer ser = new XmlSerializer(typeof(Employee));

            ser.Serialize(writer, e);

            writer.Close();

        }

    }

}


코드는 아주 간단하죠. DataContractSerializer 클래스를 이용했던 코드와 별반 다를건 없구요.
sample.xml 파일을 확인해보면 아래와 같은 모습을 하고 있는 것을 알 수 있을 것입니다.


xml 파일 역시 DataContractSerializer와 많이 다르진 않지만, 네임스페이스가 조금 바뀐 것을 확인할 수 있습니다.

아,, 참고로~ WCF에서 XmlSerializer를 사용할 수 있게끔 하기위해선 서비스 계약에서 [XmlSerializerFormat] 특성을 적용해주면 됩니다.


DataContractJsonSerializer

DataContractJsonSerializer는 직렬화의 결과로 JSON 형태를 제공합니다.
JSON 형태의 경우 XML 보다 데이터의 양이 적은 장점이 있고, 이 직렬화를 사용하면, 자바 스크립트를 이용하여 서비스를 호출할 수 있다는 장점이 있어 웹 어플리케이션에서 서비스를 쉽게 활용할 수 있다는 장점이 있습니다.
WCF 에서는 REST 서비스를 구현하는 경우 이러한 DataContractJsonSerializer를 사용합니다.

앞 예제에서 사용한 Employee 클래스를 DataContractJsonSerializer를 이용하여 직렬화 시켜보도록 하겠습니다.

using System.IO;

using System.Runtime.Serialization.Json;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            Employee e = new Employee(101, "Tae kyeom", "Oh");

            FileStream writer = new FileStream("sample.txt", FileMode.Create);

 

            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Employee));

            ser.WriteObject(writer, e);

            writer.Close();

        }

    }

}


앞 예제와는 다르게 직렬화 결과를 xml 파일이 아닌 텍스트 파일로 썼습니다. 그리고 DataContractJsonSerializer 클래스를 사용하여 직렬화를 수행했습니다.

다음은 그 결과입니다.


멋지게 JSON 형태로 결과를 보여주고 있네요. ㅎ

오늘의 포스팅은 여기까지 하겠습니다. 오늘 포스팅의 내용은 WCF 서비스를 만드는 팁도 아니고, 서비스를 만드는데 직접적인 영향을 미치는 것은 아니었던것 같습니다. 하지만, 이 부분은 앞에서도 언급했지만, WCF 서비스에 대한 이해를 위해서 알아두어야 하는 부분이라 생각하기에 포스팅을 했습니다.  ^^

다음 포스팅에서는 WCF 에서 제공하는 기본 직렬화 방법이 적절하지 않을때 커스터마이징을 수행하는 방법에 대해 알아보도록 하겠습니다.

감사합니다.
저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 0
지난 포스팅에 이어서 WCF 서비스의 동시성에 대해 이야기 해보겠습니다.
다른 설명 하지 않고, 지난 포스팅에서 했던 것 처럼 예제를 우선 보고 얘기를 진행해볼까 합니다.

Implementing a Singleton

단 하나의 서비스 인스턴스만이 생성되며, 인스턴스에서 동작하는 스레드 역시, 단 하나만 생성되게 하는 경우에 대해 먼저 살펴보겠습니다.
저번 포스팅에서 사용했던 서비스 클래스의 코드를 다음과 같이 굵은 글씨체 부분만을 수정하여 서비스를 실행해 보시기 바랍니다.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,

            ConcurrencyMode = ConcurrencyMode.Single)]

class ProductService : IProductService

{

    ProductService()

    {

        Console.WriteLine("{0}: !!", DateTime.Now);

    }
          ... 생략 ...
}

이 코드를 실행하면 다음과 같은 결과를 확인할 수 있을 것입니다.

[서버]


[클라이언트]


결과가 여러분이 예상했던 것과 일치하나요?? ㅎ

이 경우 역시 서비스의 인스턴스는 서버 측 결과 화면을 통해 클라이언트의 요청의 수와는 관계없이 하나 만이 생성됨을 알 수 있습니다.
그리고, 클라이언트에서 서비스를 호출하는 방식으로 비동기 방식을 사용했던 것 기억하실겁니다. 결과 그림만을 봐서는 잘 모를 수도 있지만, 이 때문에 클라이언트 측 결과 화면을 보면, 지연시간 없이(각 호출에 대한 결과를 받지 않아도) 서비스를 연속적으로 세 번 호출함을 볼 수 있습니다.
하지만, 이 호출에 대한 결과는 각각 5초간의 지연시간을 두고 화면에 출력하는데, 이는 서버측 결과 화면을 보면 그 이유를 알 수 있습니다. 만약, 서비스 인스턴스가 여러 개의 스레드를 만들어 요청을 처리했다면, 클라이언트의 각 요청에 대한 결과를 거의 동시에 받을 수 있겠지만, 이 경우에는 하나의 스레드 만이 동작하기 때문에 각 요청을 한번에 하나씩 처리할 수 있어 이러한 결과를 얻을 수 있는 것이죠. 참고로, 각 요청에 대한 처리는 FIFO(First In First Out)의 순서로 동작합니다.

서비스 인스턴스에서 단 하나의 스레드 만이 동작한다는 것은 서버 측 결과에서 Thread ID 값이 3으로 동일한 것을 봐도 증명이 가능합니다. 가끔, 이 thread id의 값이 각 요청마다 다른 값이 나올 수도 있습니다. 그렇다고 잘못된 결과값은 아닙니다. ConcurrencyMode.Single한번에 하나의 스레드만이 동작한다는 것을 명시하는 거지, 단 하나의 스레드만이 만들어진다라는 의미는 아니거든요~,, 약간 헷갈릴 수도 있는 부분인 것 같으니, 꼭 명심해주세요,, ㅎ

이 경우처럼 싱글 인스턴스, 싱글 스레드는 한번에 하나의 클라이언트 요청만을 처리할 수 있기 때문에 throughput을 감소시킨다는 단점이 있지만, 반면에 시스템 자원(resource)에 동시 접근 같은 문제가 일어나지 않아서, 이에 대한 추가적인 관리가 필요하지 않다는 장점도 있습니다.

Session-Level Instances

이번에는 세션 모드가 적용되었을 때, 서비스의 인스턴스가 어떻게 생성되는지 한번 살펴보도록 하겠습니다.

우선, 서비스에서 세션을 지원하도록 하기 위해 서비스 계약의 특성값을 다음과 같이 수정합니다.

[ServiceContract(Namespace = "http://RuAAService.co.kr/",

                     SessionMode = SessionMode.Required)]

interface IProductService

{

    [OperationContract]

    Product GetProduct();

}


SessionMode에 Required 값을 적용하여 이 서비스가 세션 모드를 지원해준다는 것을 명시해 줍니다.
다음은 ServiceBehavior 특성에서 InstanceContextMode의 값을 PerSession 으로, ConcurrencyMode의 값을 Multiple 로 수정을 해줍니다. 그리고 GetProduct 메서드가 한 인스턴스에서 몇 번 호출이 이루어지는지를 체크하기 위해 n_Calls 라는 이름의 필드를 추가해 주었습니다. lockThis 필드는 n_Calls 필드의 값을 증가시킬 때 다른 스레드에서 동시에 n_Calls의 값을 바꾸지 못하도록 하기 위한 목적으로 선언해주었습니다.


[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,

            ConcurrencyMode = ConcurrencyMode.Multiple)]

class ProductService : IProductService

{

    object lockThis = new object();

    private int n_Calls = 0;

       
       ... 중간 생략 ...

   
    public
Product GetProduct()

    {

        Console.WriteLine("{0} : GetProduct , Thread Id {1}", 
                            DateTime
.Now, Thread.CurrentThread.ManagedThreadId);

        Thread.Sleep(1000);

 

        Product p = new Product();

        p.ProductId = 1234;

        p.ProductName = "ABC Chocolate";

        p.Price = 1500.0;

        p.Company = "Lotteee";

        p.CreateDate = DateTime.Parse("2010-01-22");

 

        lock (lockThis)

        {

            p.calls = ++n_Calls;

        }

 

        return p;

    }

}


코드를 다 수정하셨으면, 결과를 확인해보도록 하겠습니다.

당연히, 서버 측 콘솔 어플리케이션을 실행 시키신 후에, 클라이언트 어플리케이션을 실행시켜야겠죠,,^^
아,, 이런~ 저와 같은 방법으로 수정을 한 후에 실행 시키면, 아마 예외가 발생하실겁니다.

예외의 이유는 간단합니다. Service Contract에서 세션 모드의 값을 Required로 설정을 해놓았는데, 실제 서비스를 호스팅할 때 세션을 지원하지 않는 BasicHttpBinding을 사용했기 때문입니다. 따라서, BasicHttpBinding을 WSHttpBinding 으로 수정해주시면 이 예외는 발생하지 않을 것입니다.

자~ 이 부분 수정을 다 하셨다면, 다시 실행을 해보도록 하겠습니다.

세션모드의 지원을 확인하기 위해서 클라이언트 어플리케이션을 두 개 연속해서 실행을 시킵니다.

다음은 서버 어플리케이션의 실행 화면입니다.


인스턴스가 두 개 생성된 것을 확인할 수 있네요,, 왜 인스턴스가 두 개 생성되었을까요?? 당연히 클라이언트 어플리케이션이 두 개 실행이 되었기 때문입니다. 세션을 지원하는 서비스이니깐요,,

자~ 다음은 두 클라이언트 어플리케이션의 실행화면입니다.





각 클라이언트는 호출한 횟수가 1~3인 것을 확인할 수 있습니다. 이것은 각 클라이언트 마다의 서비스 인스턴스가 따로 데이터를 유지한다는 것을 의미하는 것이죠. 이해 되셨죠?? ^^

이번 포스팅은 이것으로 마무리 하려 합니다.
이번에도 포스팅이 조금 늦었습니다. 바로 하려고 했는데 이게 마음처럼 쉽지가 않네요. ^^;;

어찌됐든, 다음 포스팅때 뵙도록 하겠습니다. ㅎ
저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 0
요즘, 날씨가 심상치 않습니다. 세계적으로 여기저기서 한파, 폭설로 난리도 아니더군요~
몇몇의 과학자들은 지구가 예전 온도를 찾기 위한 자정작용을 하고 있는 것이라 하는데,,
어쨌든 지구가 정상이 아니란 건 사실인 것 같습니다.
설마,, 2012년에 정말 지구가 멸망하는건 아니겠죠? ^^;;;;;

WCF 세번째 이야기를 시작하겠습니다~ ^^
이번에는 지난 포스팅때 다 하지 못한 내용에 대해 이야기를 해볼까 합니다.
지난 포스팅에서는 Console 어플리케이션을 이용하여 간단한 WCF 서비스를 구현하였었죠,,
서비스를 구현하였으니, 이제 이 서비스를 이용할 수 있는 클라이언트를 구현해야 할 때입니다.

클라이언트도 Console 어플리케이션을 이용해보도록 하겠습니다. 지난번 서비스를 만들었던 같은 솔루션내에 새로운 프로젝트를 추가합니다. (새로운 솔루션을 만들어도 상관은 없습니다 ^^) 아래 그림과 같이 저는 "MyServiceClient" 라는 이름의 콘솔 어플리케이션 프로젝트를 추가 하였습니다.


다음으로 클라이언트 프로젝트에 지난번 만들었던 서비스를 참조 추가해야합니다. 서비스를 참조 추가하는 방법은,, 다들 예상 했겠지만,, 아!주! 쉽습니다. ㅎ
비주얼 스튜디오 Solution Explorer의 클라이언트 프로젝트에서 마우스 오른쪽 클릭을 해주면, 다음과 같은 메뉴가 나타납니다. 여기서 "Add Service Reference"를 선택합니다.


그러면, 서비스를 참조 추가할 수 있는 창이 뜹니다. 이 창의 Address 텍스트 박스에 "http://localhost:8000/ProductService" 를 입력하고, "Go" 버튼을 클릭합니다. (방금 입력한 주소는 서비스 엔드포인트 주소입니다. 기억나시죠?? ㅎ) 이렇게 하면~ 서비스를 찾는 메시지가 뜨고 얼마 뒤 다음 그림과 같이 서비스를 찾았다는 메시지가 뜨는 것을 확인할 수 있습니다.
참~!! 이때, 아주 당연한 얘기지만, 서비스가 실행 중이어야 합니다. ctrl + F5 를 눌러 서비스를 실행하는 콘솔 창을 띄워놓은 상태에서 서비스를 찾아야 합니다.


Namespace 텍스트 박스엔 "ServiceReference1"이라고 적혀있지만, 이는 개발자 마음대로 변경 가능합니다. 그래서, 저는 "MyService"라고 수정 후에 "OK" 버튼을 클릭하였습니다.

이 작업을 모두 완료하면 Solution Explorer의 모습이 다음 그림과 같이 조금 바뀌게 됩니다.


"Service Reference" 라는 폴더가 새로 생겼고, 그 밑에 방금 등록했던 "MyService"가 추가 되어있는 것을 확인할 수 있습니다.
이로써, 클라이언트에서 서비스를 사용할 준비는 거의 끝났습니다. 너무 쉬운가요? ^^ 

그렇습니다. 아주 쉽죠,, 이렇게 개발자가 쉽게 작업을 할 수 있는 건 역시 대부분의 일을 비주얼 스튜디오가 자동으로 해주는 일이 많기 때문입니다.

비주얼스튜디오가 자동으로 해주는 일은 바로, 서비스와 통신할 수 있는 proxy 클래스를 생성 해 주는 것입니다. 이는 예전 닷넷 웹서비스를 참조 했을 때, proxy 클래스를 생성해 주던 것과 아주 비슷합니다. 여기서 이에 대한 자세한 얘기는 생략하려 합니다. 저도 자동으로 생성되는 개체에 대해선 모르는 것이 많기도 하고, 여기서 이런 얘기까지 하는건 조금 초점이 빗나가는 이야기가 될 것 같기도 하거든요,,^^;;

어쨌든, 서비스와 통신을 할 수 있는 준비가 거의 끝났다는데 큰 의미가 있는 것 같습니다. ㅎㅎ

아ㅡ 본격적으로 서비스를 이용하기 전에 한가지 더 얘기를 하고 넘어가야할 것이 있습니다.
비주얼스튜디오에서 쉽게 서비스를 참조 추가할 수 있었던 또 다른 이유,, 바로 Metadata Exchange Endpoint 입니다.

WCF의 Metadata라 함은, WCF 서비스와 통신하는 방식을 설명한 것이라고 간단하게 생각하시면 됩니다. 그러면 MEX(Metadata Exchange Endpoint) 가 무엇인지 대충 감이 오시나요?

네,, 추측하신대로 이러한 메타데이터를 교환하기 위한 엔드포인트를 말합니다. 클라이언트에서 이 MEX로 요청 메시지(request message)를 보내면, MEX 에서는 서비스가 가지고 있는 엔드포인트에 대한 내용과 메시지의 포맷에 대한 내용을 WSDL 형태로 클라이언트에 전송해 주는 것입니다.

비주얼 스튜디오에서 "서비스 참조 추가"를 실행하였을 때, 프록시 클래스와 config 파일을 생성해 주는 것은 모두 MEX에서 보내준 WSDL을 이용한 것이죠~

MEX에 대해서까지 설명을 들으니, 클라이언트와 서비스가 통신하는 방법에 대한 그림이 대충 그려지지 않으시나요?ㅎ

그럼, MEX를 정의하지 않으면 WCF 서비스와 통신하지 못할까요?

꼭 그렇지만은 않습니다. 물론, 비주얼 스튜디오에서 지원해주는 "서비스 참조 추가"를 이용하진 못합니다.
하지만, svcutil.exe 라는 명령어를 이용하여 프록시 클래스를 생성해 줄 수 있고, 이를 통해 서비스와 통신할 수 있습니다. 이 명령어에 대해서도 정리할 수 있는 시간을 가질 예정이지만, 지금 당장은 그렇게 자세히 알 필요는 없을 듯 합니다. 우리에겐 Visual Studio 라는 훌륭한 툴이 있으니깐요~ ㅎ 
(개발자는 너무 툴에 종속적이면 좋지 않다 생각하고는 있지만,,, ^^;;;)

이제, 드디어,, 서비스에 정의 되어 있는 메소드를 호출하고 사용해 볼 시간이 왔습니다.

using MyServiceClient.MyService;

namespace MyServiceClient
{
    class Program
    {
        static void Main(string[] args)
        {
            ProductServiceClient proxy = new ProductServiceClient();
            string strResult = proxy.GetFirstName("WCF");

            Console.WriteLine(strResult);
        }
    }
}

위의 코드는 클라이언트 Console 어플리케이션의 Program.cs 파일 내용입니다.

using 구문을 이용하여 포함시킨 MyServiceClient.MyService 네임스페이스는 비주얼 스튜디오에 의해 생성된 프록시 클래스의 네임스페이스입니다.
이 네임스페이스에서 상위 네임스페이스는 서비스에 정의 된 네임스페이스에 "Client"가 붙은 형태로 생성되며, 하위 네임스페이스는 서비스 참조 추가를 할 때 입력하여 준 것과 동일한 형태로 생성되어 집니다.

그리고, ProductServiceClient 클래스는 비주얼 스튜디오에 의해 생성된 프록시 클래스 입니다. 이 클래스를 이용하면 서비스에 정의되어 있는 메소드들을 클라이언트에서 맘껏 사용할 수 있게 되는 것입니다. 아래와 같은 방식으로 말이죠~ ^^
string strResult = proxy.GetFirstName("WCF");

이로써, 클라이언트에 작성할 것도 모두 다 했습니다.

그럼, 실행을 한번 시켜 보도록 하죠~

현재, 솔루션에서 서비스 프로젝트와 클라이언트 프로젝트가 모두 동작해야 되기 때문에 솔루션의 속성을 조금 변경할 필요가 있습니다. 
아래 그림에서 볼 수 있듯이, 솔루션 속성 창에서 "Multiple startup projects"를 선택하고 두 개의 프로젝트 모두 "Start" 하도록 값을 변경하여 줍니다.


이제 실행만 하면 됩니다. 과감하게~ F5 키를 눌러 실행을 시켜 보죠~
아래와 같은 화면이 나타납니다. 두둥~~!!


결과 화면을 보고 실망하셨나요?? ^^;;
지금까지, 길~게 설명한 것에 비하면 결과는 아주 보잘것 없습니다. 하지만, 실망(?)하지 마시기 바랍니다 ^^;
처음부터 이 서비스는 아주 아주 간단한 서비스라고 말씀 드렸었고, 이 간단한 서비스를 이용해 우리는 WCF 에 대한 큰 그림을 그릴 수 있었으니깐, 결코 실망스러운 결과는 아닙니다.

이렇게 해서 WCF의 기초에 대해선 모두 끝이 났습니다.
글을 이용하지 않고, 옆에서 말로 설명을 했다면, 금방 끝날 수 있는 내용을 두 개의 포스팅에 걸쳐 설명을 했습니다. 
이렇게 하고 나니, 글을 이용해 정보를 전달한다는 것이 결코 쉬운 일이 아니란 것을 새롭게 깨닫게 되면서, 파워 블로거분들이 존경 스러워 지네요 ^^

포스팅을 시작한지 얼마되지 않아 많이 부족하지만, 꾸준히 하다보면 저도 다른 분들처럼 멋진 글을 쓸 수 있는 날이 오겠죠~ㅎㅎ 그때까지 포기하지 않고 열심히 하겠습니다 ^^

그럼, 다음 포스팅때 뵙겠습니다~
저작자 표시 비영리 변경 금지
신고
Posted by 오태겸(RuAA) 트랙백 0 : 댓글 1

티스토리 툴바