VisualStudio/WCF

[WCF] WCF - 프록시 사용이유

usingsystem 2023. 1. 3. 15:14
728x90
클라이언트가 서비스에 대한 메타 데이터를 알아낼 수 만 있다면 서비스 프록시를 만드는데 필요한 서비스 인터페이스를 생성할 수 있다. 왜냐면 서비스 메타 데이터에는 프록시가 요구하는 서비스의 계약, 바인딩, 주소 정보가 모두 포함되어 있기 때문이다. Hello World 서비스를 예로 들어 설명하자면, 서비스 호출에 필요한 것은 IHelloWorld 인터페이스이다. 이를 위해 우리는 서비스의 구현이 포함되었던 HelloWorldService 어셈블리를 클라이언트에서 "참조" 했었다. 이러한 서버 측 구현을 참조하는 것은 서비스 지향적인 관점에 어긋날 뿐만 아니라 닷넷이 아닌 클라이언트를 사용할 수 없다는 단점이 있다는 것은 이미 지적한 바와 같다. 그렇다면 클라이언트가 IHelloWorld 인터페이스에 대해 이미 알고 있고 이를 클라이언트 코드 내에 선언한다면 어떻게 될까?
[리스트 7]의 클라이언트 코드에서 서비스의 구현에 대한 참조를 제거하는 작업을 직접 시도해 보자. 먼저 예제로 작성한 HelloWorldClient 프로젝트에서 HelloWorldService 참조를 제거한다. HelloWorldSerivce 프로젝트에 대한 참조가 제거 되었으므로 클라이언트 코드는 더 이상 IHelloWorld 인터페이스에 대해 알지 못한다. 당연히 컴파일 오류가 발생할 것이다. 따라서 IHelloWorld 인터페이스를 클라이언트 코드 내에 [리스트 15]과 같이 선언해 주도록 하자.
리스트 15. 서비스 구현에 대한 참조 없이 작성한 클라이언트 코드

using System;
using System.ServiceModel;
using System.ServiceModel.Description;


namespace HelloWorldClient2
{
    // 서비스 Contract 선언
   [ServiceContract(Namespace = "http://www.simpleisbest.net/wcf/helloworld")]
    public interface IHelloWorld
    {
        [OperationContract]
        string SayHello();
    }


    class Program
    {
        static void Main(string[] args)
        {
            Uri uri =
                new Uri("http://localhost/wcf/example/helloworldservice");
            ServiceEndpoint ep = new ServiceEndpoint(
                ContractDescription.GetContract(typeof(IHelloWorld)),
                new BasicHttpBinding(),
                new EndpointAddress(uri));


            ChannelFactory<IHelloWorld> factory =
                new ChannelFactory<IHelloWorld>(ep);
            IHelloWorld proxy = factory.CreateChannel();
            string result = proxy.SayHello();
            (proxy as IDisposable).Dispose();


            Console.WriteLine(result);
        }
[리스트 7]과 [리스트 15]가 다른 점은 HelloWorldService 어셈블리에 대한 참조가 사라졌기 때문에 네임스페이스를 참조한 using 문장이 사라진 것과 IHelloWorld 인터페이스에 대한 선언이 추가된 것일 뿐이다. IHelloWorld 인터페이스는 클라이언트 측의 WCF 런타임에서 사용할 것이므로 ServiceContract 특성과 OperationContract 특성이 서비스 측의 구현에서 선언된 것과 동일하게 추가 되어 있음에 주목하기 바란다. [리스트 15]은 훌륭하게 잘 컴파일 될 뿐만 아니라 Hello World 서비스를 호출하는데 아무런 문제도 유발하지 않고 매우 잘 작동한다.
필자가 여기서 무엇을 말하고자 하는지 알겠는가? 웹 서비스는 서비스에 대한 계약 인터페이스만 동일하다면 그 선언 및 구현이 닷넷 이건 아니건 닷넷의 어느 어셈블리에 존재하건 상관하지 않는다는 점이 핵심 요소가 되겠다. WCF 서비스와 WCF 클라이언트가 동일한 "인터페이스"를 바라보고 있기 때문에 클라이언트와 서비스가 SOAP 메시지를 주고 받는데 아무런 문제도 유발하지 않는다. 매우, 대단히, 정말로 중요한 개념이므로 잘 이해하고 넘어가도록 하자.
이제 남은 것은 서비스 계약인 IHelloWorld 인터페이스를 [리스트 15]과 같이 개발자가 직접 때려 넣어 주어야 하는 것인가 만이 숙제로 남았다. 웹 서비스는 WSDL을 제공하고 WSDL로부터 서비스 인터페이스를 알아낼 수 있다고 했으므로 WSDL로부터 IHelloWorld 인터페이스를 생성해 낼 수 있지 않을까? 그렇다. 바로 그렇게 하면 되는 것이다. 이미 ASP.NET 웹 서비스를 사용해 본 독자라면 너무나도 당연하게 느끼는 웹 서비스 프록시 생성이란 것을 그대로 WCF에도 적용할 수 있는 것이다. 하지만 WCF는 ASP.NET 웹 서비스에 비해 훨씬 더 다양하고 유연한 방법들을 제공한다. 먼저 svcutil.exe 유틸리티를 이용하여 프록시를 생성하는 방법부터 살펴보자.
Svcutil.exe 이용
Svcutil.exe 유틸리티는 WSDL을 생성하는 것보다는 WSDL 로부터 클라이언트 측의 프록시 코드를 생성하는데 더 많이 사용된다. 사용법은 그다지 어렵지 않다. 커맨드 라인 유틸리티라면 복잡한 옵션 때문에 어렵게만 생각하는 독자들이 많이 있겠지만 커맨드 라인 유틸리티를 잘 다루면 정말 ‘고수’란 소리를 들을 수 있을 뿐만 아니라 커맨드 라인 유틸리티만이 가능한 기능들도 있으므로 가급적 익숙해지도록 노력하는 것이 좋다. Svcutil.exe을 사용하여 프록시를 생성하는 방법은 WSDL을 svcutil.exe 입력으로 주고 출력으로 프록시 코드를 생성하도록 하면 된다. 이 때 WSDL은 svcutil.exe로 생성한 WSDL 파일이거나 WSDL에 대한 URL이어도 상관 없다. 앞서 WSDL 관련 파일들을 다음과 커맨드를 이용하여 생성했다면,
svcutil.exe HelloWorldService.dll
하나의 .wsdl 파일과 두 개의 .xsd 파일이 생성되었을 것이다. 이렇게 생성된 WSDL 관련 파일들을 다음과 같이 svcutil.exe에 입력으로 제공하면 프록시를 위한 코드가 자동으로 생성된다.
svcutil.exe *.wsdl *.xsd /language:C# /out:Proxy.cs
위 명령은 폴더 내의 .wsdl 파일과 .xsd 파일을 입력으로 하여 C# 언어의 프록시 코드를 생성하되 코드의 파일명은 Proxy.cs 가 되도록 하는 것이다. 와일드 카드(*)를 사용하지 않고 구체적으로 파일명을 지정해 주어도 되며, /language 옵션 값에 C# 대신 VB 값을 주어 VB.NET 코드를 생성할 수도 있다.
만약 WSDL 파일 없이 WSDL을 다운로드 받을 URL을 알고 있다면 해당 URL을 명시할 수 도 있다. 우리의 Hello World 서비스에 대한 WSDL을 사용하여 프록시 코드를 생성하는 명령은 다음과 같다.
svcutil.exe http://localhost/wcf/example/helloworldservice?wsdl /language:C# /out:Proxy.cs
WSDL에 대한 URL을 사용하는 경우, WSDL을 svcutil.exe가 다운로드 받을 수 있어야 한다는 점에 주의하자. 다시 말해 WSDL을 제공하는 서비스가 구동 중이어야 하며 WSDL에 대한 HTTP GET을 허용(HttpGetEnabled 속성)해야 한다는 것이다. WCF 서비스가 수행 중이지 않다면 svcutil.exe 는 WSDL을 제공 받을 수 없을 것이며 당연히 오류를 유발할 것이다. 위 명령을 수행하기 위해서는 우리의 HelloWorldHost 프로젝트를 미리 수행시켜 WSDL을 제공할 수 있도록 하자.
프록시 코드 탐험
Svcutil.exe 유틸리티를 통해 생성된 결과는 Proxy.cs 소스 코드와 configuration 설정을 담는 output.config 파일이다. Proxy.cs 파일은 직접 클라이언트 프로젝트 내에 포함시키면 되고, ouput.config 파일의 내용을 복사하여 app.config에 적용하거나, app.config 파일이 이미 존재하지 않는다면 ouput.config 파일의 이름을 app.config로 변경만 해도 된다. 파일 이름을 변경하여 app.config 파일을 생성한 경우 이 app.config 파일도 프로젝트 내에 포함시켜야 한다.
[리스트 16]에 표시한 자동 생성된 Proxy.cs 코드를 살펴보자. 생성된 프록시 코드에서 가장 먼저 눈에 띄는 것은 IHelloWorld 인터페이스에 대한 선언이다. 앞서 우리가 손으로 직접 만들어 넣은 IHelloWorld 인터페이스와 크게 다를 것 없이 ServiceContract 특성이 붙어 있으며 SayHello 메쏘드에도 OperationContract 특성이 붙어 있음을 알 수 있을 것이다. 수작업으로 작성한 인터페이스와 다른 부분은 ServiceContract 과 OperationContract 특성에 추가적으로 붙은 ConfigurationName, Action, ReplyAction 속성들인데 이들은 디폴트 값을 사용하므로 명시하지 않아도 되는 것들이다. 이들에 대해서는 4장에서 서비스 계약에 대해 상세히 다룰 때 다시 설명하기로 하겠다. 중요한 점은 메타 데이터에서 서비스의 계약 인터페이스를 "생성"해 냈다는 점이다.
리스트 16. 자동 생성된 프록시 코드

[ServiceContract (Namespace="...", ConfigurationName="IHelloWorld")]
public interface IHelloWorld
{
    [OperationContractAttribute(Action="...", ReplyAction="...")]
    string SayHello();
}


......


public partial class HelloWorldClient : ClientBase<IHelloWorld>, IHelloWorld
{


    public HelloWorldClient()
    {
    }


    public HelloWorldClient(string endpointConfigurationName) :
            base(endpointConfigurationName)
    {
    }


    public HelloWorldClient(string endpointConfigurationName,
                                string remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
    {
    }


    public HelloWorldClient(string endpointConfigurationName,
                                EndpointAddress remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
    {
    }


    public HelloWorldClient(Binding binding, EndpointAddress remoteAddress) :
            base(binding, remoteAddress)
    {
    }


    public string SayHello()
    {
        return base.Channel.SayHello();
    }
}
그 다음에 주의 깊게 살펴보아야 할 것은 HelloWorldClient 란 클래스이다. 이 클래스는 ClientBase<T> 클래스에서 파생된 클래스로서 채널 팩토리를 직접 사용하는 프록시에 대한 래퍼(wrapper) 클래스로서 보다 편리하게 서비스를 호출하도록 해준다. HelloWorldClient 라는 클래스 이름은 서비스의 계약인 IHelloWorld에서 자동으로 인터페이스 접두사(prefix) 인 I 를 제거하고 Client 접미사(postfix)를 붙인 것이다.


public abstract class ClientBase :
                   ICommunicationObject, IDisposable where TChannel : class
{
    protected ClientBase();
    protected ClientBase(string endpointConfigurationName);
    protected ClientBase(Binding binding, EndpointAddress remoteAddress);
    protected ClientBase(string endpointConfigurationName,
                            EndpointAddress remoteAddress);
    protected ClientBase(string endpointConfigurationName,
                            string remoteAddress);


    protected TChannel Channel { get; }
    ......
}


ClientBase<T> 클래스는 [리스트 7]이나 [리스트 9]와 같이 ChannelFactory<T> 클래스를 직접 사용하여 프록시를 생성하는 코드를 내부적으로 이미 가지고 있기 때문에 더 이상 이 클래스의 인스턴스를 생성하거나 CreateChannel 메쏘드를 호출할 필요가 없다. 예를 들어, configuration의 서비스 종점 설정을 매개변수로 취하는 ClientBase<T>의 생성자(constructor)의 내부 코드는 다음과 같다.
인터페이스의 이름에는 이것이 인터페이스임을 밝히기 위해 I 접두사를 붙이는 것이 닷넷의 프로그래밍 관습이다.


protected ClientBase(string endpointConfigurationName)
{
    this.channel = null;
    if (endpointConfigurationName == null)
    {
        // Error Handling......
    }
    this.channelFactory =
        new ChannelFactory(endpointConfigurationName);
    ......
}


protected TChannel Channel
{
    get
    {
        if (this.channel == null) {
            // thread safe locking... (skip)
            this.channel = this.CreateChannel();
        }
        return this.channel;
    }
}


이 생성자는 [리스트 9]와 같이 매개변수로 주어진 configuration 이름을 사용하여 ChannelFactory<T> 객체를 초기화 하는 것을 알 수 있을 것이다. 또한 Channel 속성은 필요에 따라서 생성해 놓은 ChannelFactory 객체의 CreateChannel 메쏘드를 호출하여 프록시를 생성하고 있고 있다. [리스트 16]의 SayHello 메쏘드 내에서 Channel 속성을 통해 서비스를 호출하는 코드가 어떻게 작동하는지 이제 이해가 될 것이다.
Binding 과 EndpointAddress, 그리고 서비스의 주소를 매개변수로 취하는 ClientBase<T> 클래스의 다른 생성자들도 주어진 매개변수를 통해 다양한 방법으로 ChannelFactory<T> 객체를 초기화 하는 것일 뿐이다. ClientBase<T> 클래스는 ChannelFactory<T> 클래스를 직접적으로 사용하지 않고서도 서비스를 호출하게 해주는 편리한 프록시 래퍼 클래스로 생각하면 이해하기 쉽다. 이제 ClientBase<T>에서 파생된 HelloWorldClient 클래스를 사용하여 서비스 클라이언트 코드는 다음과 같이 작성할 수 있게 되었다.
리스트 17. 생성된 프록시 코드를 사용하는 클라이언트

string url = "http://localhost/wcf/example/helloworldservice";
BasicHttpBinding binding = new BasicHttpBinding();
EndpointAddress address = new EndpointAddress(url);


using (HelloWorldClient proxy = new HelloWorldClient(binding, address)) {
    string result = proxy.SayHello();
    Console.WriteLine(result);
}
[리스트 7]에 비해 [리스트 17]의 코드가 바뀐 부분은 ChannelFactory<T> 클래스를 전혀 사용하지 않았을뿐더러, using 문을 사용하여 보다 깔끔하게 코드를 작성할 수 있다는 것이다. 자…… 여기서 만족하지 말자. [리스트 9]와 같이 configuration을 사용하는 코드 역시 HelloWorldClient 클래스를 통해 작성할 수 있지 않을까? 물론 가능하다. 그리고 더욱 즐거운 것은 svcutil.exe 가 configuration을 작성하는데 쓰라고 output.config 라는 설정파일까지 이미 만들어 주었으니, 이 파일에 포함된 설정 내용은 [리스트 18]과 같다.
리스트 18. 자동으로 생성된 configuration 파일(output.config)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IHelloWorld" ...... >
        ......
        </binding>
      </basicHttpBinding>
      <netTcpBinding>
        <binding name="NetTcpBinding_IHelloWorld" ...... >
        ......
        </binding>
      </netTcpBinding>
    </bindings>
    <client>
      <endpoint
            address="http://localhost/wcf/example/helloworldservice"
            binding="basicHttpBinding"
            bindingConfiguration="BasicHttpBinding_IHelloWorld"
            contract="IHelloWorld"
            name="BasicHttpBinding_IHelloWorld" />
      <endpoint
            address="net.tcp://localhost/wcf/example/helloworldservice"
            binding="netTcpBinding"
            bindingConfiguration="NetTcpBinding_IHelloWorld"
            contract="IHelloWorld"
            name="NetTcpBinding_IHelloWorld">
      ......
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>
실제 output.config 의 내용이 상당히 많아서 기겁을 할 지도 모르겠다. 걱정할 필요 없다. 이 책을 읽어 가는 동안 금방 익숙해 질 것이며 설정 내용의 대부분은 바인딩의 디폴트 값을 명시적으로 언급한 것일 뿐이다. Svcutil.exe 입장에서 보았을 때 바인딩의 속성 값들이 어떤 것이 디폴트 값이고 어떤 것이 서비스에서 설정한 값인가를 판단하기 어렵기 때문에 서비스 메타 데이터에서 제시하는 모든 값들을 명시적으로 표현한 것일 뿐이다. 그래서 바인딩의 속성들이 복잡하게 보이지만 사실은 거의 대부분의 값들이 디폴트임을 알아두자.
주목할 부분은 <endpoint> 요소에 주어진 종점 설정의 이름이다. ClientBase<T> 클래스의 생성자에서 취하는 endpointConfigurationName 매개변수란 것이 바로 <endpoint> 요소에 나타난 name 속성의 값이란 얘기가 되겠다. Output.config 파일의 내용을 클라이언트의 app.config 파일에 복사해 넣으면, [리스트 17]의 코드는 다음과 같이 간략하게 코딩 할 수도 있다는 것이다.


using (HelloWorldClient proxy =
                new HelloWorldClient("BasicHttpBinding_IHelloWorld")) {
    string result = proxy.SayHello();
    Console.WriteLine(result);
}


위 코드는 BasicHttpBinding_IHelloWorld 란 이름을 가진 <endpoint> 요소를 찾아, 필요한 바인딩, 바인딩 설정, 종점 주소 등의 정보를 사용하여 프록시를 초기화하고 프록시 인스턴스를 생성하게 된다. 물론 이 때 사용하는 바인딩은 BasicHttpBinding이며, 만약 NetTcpBinding을 사용하고자 한다면 configuration 이름을 BasicHttpBinding_IHelloWorld 가 아닌 NetTcpBinding_IHelloWorld 이란 이름을 사용하면 될 것이다.
설정된 Configuration을 오버라이드 하는 것 역시 어렵지 않게 가능하다. 다음 예제 코드는 configuration에서 설정된 메시지 인코딩(만약 존재 한다면)을 오버라이드 하여 항상 MTOM이 메시지를 인코딩 하는데 사용하도록 강요하고 있다.


using (HelloWorldClient proxy =
                  new HelloWorldClient("BasicHttpBinding_IHelloWorld")) {
    BasicHttpBinding binding = proxy.Endpoint.Binding as BasicHttpBinding;
    if (binding != null) {
        binding.MessageEncoding = WSMessageEncoding.Mtom;
    }
    string result = proxy.SayHello();
    Console.WriteLine(result);
}


또 하나, HelloWorldClient 클래스를 생성할 때 생성자에 매개변수를 전혀 주지 않고 디폴트 생성자(default constructor)를 사용하는 방법도 있다. 이 디폴트 생성자를 사용하면 클라이언트가 호출하고자 하는 서비스의 계약 인터페이스를 사용하는 종점을 configuration에서 찾아 적용하고자 시도한다. 즉, IHelloWorld 인터페이스를 계약으로 사용하는 <endpoint> 요소를 찾는다는 말이 되겠다. 만약 IHelloWorld 인터페이스를 계약으로 사용하는 종점 설정을 발견하지 못하거나, 이 인터페이스를 계약으로 사용하는 종점이 2개 이상이 발견되면 InvalidOperationException 이 발생하므로, 2개의 종점을 제공하는 우리의 Hello World 예제에는 적용할 수 없다.
Visual Studio의 서비스 참조 기능
지금까지 svcutil.exe 유틸리티를 사용하여 클라이언트 프록시 코드를 생성하고 관련된 configuration까지 사용하는 방법을 살펴 봤다. 하지만 이 방법은 상당히 손이 많이 가는 귀찮은 작업이다. 우리 개발자들이 Copy & Paste 와 같은 단순한 반복은 곧 잘 하지만, 서비스 프록시 코드를 생성하고 생성된 코드를 프로젝트에 추가하는 등의 작업은 상당히 싫어하는 편이다. 커맨드 프롬프트를 구동시키고, svcutil.exe 명령을 때려 넣고, 생성된 코드를 다시 프로젝트에 추가해야 하니 척 봐도 좀 귀찮아 보이지 않는가?
그래서…… Visual Studio 내부에서 직접 프록시 코드와 configuration 설정을 해주는 "서비스 참조 추가(Add Service Reference)" 기능을 사용할 수 있다([그림 2] 참조). 이 메뉴를 선택하면 [그림 3]과 같은 서비스 참조 추가 대화 상자가 나타나고 여기에 서비스의 주소를 명시할 수 있다. Visual Studio 2008의 서비스 참조 기능은 서비스의 주소에서 경험적인 방법을 통해 서비스의 메타 데이터를 구하는 URL을 검색한다. 즉, 서비스 주소로부터 MEX(Metadata Exchange) 종점을 유추해 낸다던가 서비스 주소 뒤에 ?wsdl 을 붙인 주소로부터 WSDL을 읽으려고 시도한다던가 하는 시도를 여러 차례 반복하여 메타데이터를 구하려고 시도한다는 것이다.
주어진 서비스의 주소로부터 다양한 시도를 수행하여 WSDL을 구했다면 [그림 3]과 같이 서비스 종점에서 제공하는 서비스 계약(들)과 서비스 계약 내의 메쏘드들이 표시된다. Visual Studio를 사용하여 서비스 프록시를 생성할 때는 항상 네임스페이스를 입력하도록 되어 있는데, 이 네임스페이스는 생성될 서비스 프록시 클래스 및 인터페이스의 네임스페이스가 되며 프로젝트 내에서 참조한 서비스를 다시 재 참조할 때도 사용된다. Visual Studio 에서 생성되는 프록시는 항상 이 네임스페이스를 갖는 클래스로서 표현됨을 기억해 두자.

그림 2. Visual Studio 2008의 서비스 참조 추가


그림 3. 서비스 참조 추가 대화 상자

서비스 참조가 성공적으로 추가되면 [그림 4]와 같이 프로젝트에는 필요한 폴더 구조와 몇몇 파일들이 생성된다. 먼저, 클라이언트 프록시 코드를 담는 소스 코드(Reference.cs)가 생성되는데 이 코드는 svcutil.exe을 통해 생성된 코드와 거의 동일 하다([리스트 16] 참조). 또한 자동으로 어플리케이션 설정 파일인 app.config가 생성되어 필요한 WCF 설정들이 포함되거나, 이미 app.config 파일이 존재하는 경우 이 파일의 내용을 업데이트 하기도 한다. App.config 파일이 새로이 추가 되었건 기존 파일이 변경되었건 상관 없이 설정 파일의 내용은 [리스트 18]의 설정 내용과 다를 것이 없다. 이외에도 .wsdl 파일과 .xsd 파일들 그리고 몇몇 파일들이 생성되는데, 이 파일들은 서비스의 WSDL 및 XML 스키마에 대한 캐시 파일이며 서비스의 메타데이터를 읽은 URL 등의 정보들이 포함되어 있다. 이들 파일들은 서비스 참조를 갱신(update) 할 때 사용되어 서비스 메타데이터가 어떻게 변경되었는가를 추적하는데도 사용된다. 이 정보가 훼손되면 서비스 참조는 업데이트될 수 없다. 따라서 이들 파일의 내용을 수정하려고 시도해서는 안 된다.

그림 4. 서비스 참조 후 생성되는 폴더 구조와 관련 파일들

자, 이렇게 서비스에 대한 프록시를 자동 생성했다면 이젠 클라이언트 코드는 Visual Studio가 생성해 준 클래스를 사용하기만 하면 된다. [리스트 19]는 클라이언트 코드 전체를 보여주고 있다. Visual Studio가 생성하는 프록시 코드는 항상 네임스페이스를 사용하여 코드를 생성하므로 using 문장이 추가되었음에 유의하자. 물론 svcutil.exe를 사용할 때도 /namespace 옵션을 통해 다음과 같이 생성되는 코드의 네임스페이스를 설정할 수 있다. 이 옵션은 WSDL 상에 명시된 XML 네임스페이스를 닷넷 네임스페이스에 어떻게 매핑 시킬 것인가를 명시하는데, 다음 예는 WSDL 상의 모든 네임스페이스를 localhost 라는 닷넷 네임스페이스에 매핑 시키는 것을 보여주고 있다. 이러한 방식의 네임스페이스 매핑은 Visual Studio 의 기본 행동이기도 하다.
svcutil.exe http://localhost/wcf/example/helloworldservice?wsdl /language:C# /namespace:*,localhost /out:Proxy.cs
리스트 19. Visual Studio가 생성해 준 프록시를 사용하는 클라이언트 전체 코드

using System;
using HelloWorldClient4.localhost;


namespace HelloWorldClient4
{
    class Program
    {
        static void Main(string[] args)
        {
            using (HelloWorldClient proxy =
                    new HelloWorldClient("NetTcpBinding_IHelloWorld")) {
                string result = proxy.SayHello();
                Console.WriteLine(result);
            }
        }
    }
}


수동으로 프록시 만들기
Svcutil.exe 나 Visual Studio를 통해 ClientBase<T> 클래스 기반의 프록시 코드를 생성하는 방법 외에도 이 클래스를 직접 코딩 할 수도 있다. 유틸리티나 개발도구가 생성해주는 코드는 고정적인 코드를 생성하기 때문에 어떤 변환 작업을 수행하기 어려울 때가 많다. 예를 들어 클라이언트가 서비스를 호출하기 전에 로그를 남겨야 한다고 가정해 보자. 자동으로 생성된 프록시 코드(Reference.cs 혹은 proxy.cs)를 직접 수정할 수도 있겠다. 하지만 나중에 서비스의 인터페이스가 바뀌게 되어 서비스 참조를 업데이트 해야만 한다면 프록시 코드가 다시 생성되기 때문에 이전에 작성해 놓은 코드는 모조리 사라져 버리게 된다. 매우 극단적인 예지만 이러한 상황은 실제 프로젝트 상에서 종종 발생하는 일이므로 직접 수작업으로 프록시 코드를 생성해야 하는 경우도 생긴다는 말이다.
[리스트 20]은 수작업으로 작성한 프록시 코드를 보여주고 있다. 어셈블리 참조에 의해서건, 직접 코드를 작성했건, 자동으로 생성된 프록시 코드에서 빌려오건, 일단 서비스에 대한 계약 인터페이스를 구할 수만 있다면 ClientBase<T> 클래스에서 파생된 프록시 클래스를 생성할 수 있으며 이 클래스는 구미에 맞게 마음대로 작성할 수 있다. [리스트 20]은 configuration 을 통해 바인딩과 서비스 주소를 초기화 하는 생성자 만을 선언하였으며, 서비스 호출 전에 디버그 메시지를 출력하고 있다.
리스트 20. 수작업으로 작성한 프록시 코드 예제

public class MyProxy : ClientBase<IHelloWorld>, IHelloWorld
{
    public MyProxy(string configurationName)
        : base(configurationName)
    {
    }


    public string SayHello()
    {
        System.Diagnostics.Debug.WriteLine(“Invoke SayHello()");
        return base.Channel.SayHello();
    }
}

 출처 - http://taeyo.net/columns/View.aspx?SEQ=347&PSEQ=23&IDX=5 

728x90