login register Sysop! about ME  
qrcode
    최초 작성일 :    2008년 12월 17일
  최종 수정일 :    2008년 12월 17일
  작성자 :    Loner
  편집자 :    Loner (유경상)
  읽음수 :    26,050

강좌 목록으로 돌아가기

필자의 잡담~

이전 WCF 강좌에 이어 이번 강좌는 WCF 클라이언트 설정을 Configuration을 이용하여 설정하는 내용입니다.
더불어, 즐거운 연말 연시 되세요~

Configuration을 이용한 클라이언트 설정

WCF 클라이언트 역시 configuration을 통해 하드 코드를 제거하고 다양한 설정을 코드 수정 없이 수행할 수 없을까? 서비스가 그러한 기능이 가능할 진데 클라이언트만 불가능할 리는 없다. WCF 클라이언트 역시 configuration을 통한 다양한 설정이 가능하다. 클라이언트를 위한 configuration 역시 <system.ServiceModel> 섹션의 하위 요소들을 사용한다. 특히 <client> 요소가 가장 핵심적인 역할을 하며 서비스와 동일하게 바인딩 설정을 하기 위해서는 <bindings> 요소를 사용하기도 한다. 구체적인 configuration 예제를 살펴보기 전에 1장에서 클라이언트가 서비스를 호출하기 위해 어떤 설정들을 필요로 했는지 살펴보도록 하자. [리스트 7]은 1장의 클라이언트 코드의 일부만을 다시 보여주고 있다.

리스트 7. Configuration을 사용하지 않는 클라이언트 코드

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

WCF 클라이언트에서 필요한 작업은 먼저 서비스에 대한 서비스 종점을 기술하는 것이었다. 즉, 서비스의 종점이 어떤 계약을 사용하고 어떤 바인딩을 사용하며 서비스의 주소는 무엇인가를 설명하는 ServiceEndpoint 객체를 생성하는 작업인 것이다. 서비스 종점에 대한 ServiceEndpoint 객체를 생성 했다면 이 객체를 채널 팩토리 객체에게 넘겨주어 서비스 프록시 객체를 생성하는 작업을 수행하는 것이 다음 작업이 되겠다. 일단 서비스 프록시 객체를 구한 다음에는 프록시를 사용하여 서비스를 호출하는 등의 작업만이 남아 있을 뿐이다.

따라서 클라이언트 측에 WCF가 제공하는 설정은 서비스에 대한 종점을 configuration에 기술할 수 있도록 해주는 것과 configuration에 기술된 서비스 종점을 채널 팩토리 객체에 알려 줄 수 있으면 될 것이다. 클라이언트에서 사용할 수 있는 configuration의 예제는 다음과 같다.

<system.serviceModel>
    <client>
        <endpoint name="HttpHelloWorld"
            contract="HelloWorldService.IHelloWorld"
            binding="basicHttpBinding"
            address="http://localhost/wcf/example/helloworldservice"
        />
    </client>
</system.serviceModel>

Configuration의 <client> 요소는 클라이언트가 호출하고자 하는 서비스의 종점들을 나열하는데 사용된다. <endpoint> 요소가 서비스의 종점을 나타내며 서비스의 계약, 바인딩, 주소를 contract 속성, binding 속성, address 속성을 통해 서비스 종점 정보를 명시해야만 한다. 또한 필수 속성으로 name 속성 역시 명시해야만 하는데, <client> 요소 밑에 존재하는 여러 <endpoint> 요소들을 구별하기 위해 유일한(unique) 이름이 사용되어야 한다

앞서 살펴본 [리스트 6]의 설정과 같이 바인딩에 대한 속성 설정 역시 클라이언트 configuration의 <bindings> 요소에 명시할 수 있으며 <bindings> 내에 명시된 바인딩 설정을 참조하는 방법은 <endpoint> 요소의 bindingConfiguration 속성에 바인딩 설정 이름(name)을 명시하면 된다. [리스트 8]은 앞서 작성한 [리스트 5]와 [리스트 6]의 서비스를 호출하기 위한 WCF 클라이언트의 configuration 이다. <endpoint> 요소에 bindingConfiguration을 명시하여 MessageEncoding을 MTOM으로 설정하는 바인딩 설정을 사용하고 있음을 주목하기 바란다.

리스트 8. 클라이언트를 위한 WCF Configuration

<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="MtomSetting" messageEncoding="Mtom" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint name="HttpHelloWorld"
                contract="HelloWorldService.IHelloWorld"
                binding="basicHttpBinding"
                bindingConfiguration="MtomSetting"
                address="http://localhost/wcf/example/helloworldservice"
            />
            <endpoint name="TcpHelloWorld"
                contract="HelloWorldService.IHelloWorld"
                binding="netTcpBinding"
                address="net.tcp://localhost/wcf/example/helloworldservice"
            />
        </client>
    </system.serviceModel>
</configuration>

이제 설정된 WCF 설정 값을 클라이언트 코드가 가져다 쓰는 것만 남았다. 채널 팩토리 클래스인 ChannelFactory<T> 클래스의 생성자들 중에는 configuration에 명시된 서비스 종점의 이름을 매개변수로 취하는 생성자가 제공된다. 즉, [리스트 7]에서처럼 ServiceEndpoint 객체를 매개변수로 취하는 대신 configuration에 명시된 서비스 종점의 이름을 대신 매개변수로 취한다는 말이 되겠다.

public class ChannelFactory<TChannel> :
        ChannelFactory, IChannelFactory<TChannel>,
        IChannelFactory, ICommunicationObject
{
    public ChannelFactory(string endpointConfigurationName);
    public ChannelFactory(string endpointConfigurationName,
            EndpointAddress remoteAddress);
    public ChannelFactory(ServiceEndpoint endpoint);
    public TChannel CreateChannel();
    ......
}

이렇게 ServiceEndpoint 객체 대신 종점에 대한 configuration 이름이 제공되면 ChannelFactory<T> 클래스는 configuration에서 해당 이름을 가진 <endpoint> 요소를 찾는다. 이 요소에서 클라이언트가 호출하고자 하는 서비스의 계약, 바인딩, 주소를 알아내어 ChannelFactory<T> 객체를 초기화 하게 되는 것이다. 다음 코드는 서비스 종점에 대한 configuration 이름을 사용하여 채널 팩토리 객체를 초기화하고 생성하는 예를 보여주고 있다.

ChannelFactory<IHelloWorld> factory =
            new ChannelFactory<IHelloWorld>("HttpHelloWorld");
IHelloWorld proxy = factory.CreateChannel();
......

Configuration 을 작성하는 방법과 사용하는 방법을 알았으므로 이젠 클라이언트 코드를 이에 맞도록 수정하기만 하면 된다. [리스트 8]의 configuration을 사용하는 클라이언트 코드는 [리스트 9]와 같다. ChannelFactory<T> 클래스의 인스턴스를 생성할 때 configuration에 명시된 서비스 종점의 이름을 사용한 점과 서비스 종점을 구성하기 위한 코드들이 존재하지 않는다는 점이 [리스트 7]과 다른 점이라고 할 수 있다. [리스트 9]의 코드도 서비스의 주소나 바인딩 설정이 하드코딩 되어 있지 않다는 점에서 유연한 클라이언트라고 할 수 있다. 서비스가 주소를 변경하거나 바인딩 설정을 바꾸더라도 클라이언트 코드를 변경하고 재 컴파일 할 필요가 없으며 단지 configuration의 내용만 수정을 해주어도 되기 때문이다.

리스트 9. Configuration을 사용하는 클라이언트 코드

class Program
{
    static void Main(string[] args)
    {
        InvokeUsingHTTPWithConfig();
        InvokeUsingTCPWithConfig();
    }

    static void InvokeUsingHTTPWithConfig()
    {
        ChannelFactory<IHelloWorld> factory =
            new ChannelFactory<IHelloWorld>("HttpHelloWorld");
        IHelloWorld proxy = factory.CreateChannel();
        string result = proxy.SayHello();
        (proxy as IDisposable).Dispose();
        Console.WriteLine(result);
    }

    static void InvokeUsingTCPWithConfig()
    {
        ChannelFactory<IHelloWorld> factory =
            new ChannelFactory<IHelloWorld>("TcpHelloWorld");
        IHelloWorld proxy = factory.CreateChannel();
        string result = proxy.SayHello();
        (proxy as IDisposable).Dispose();
        Console.WriteLine(result);
    }
}

Overriding Configuration

지금까지 살펴본 WCF의 configuration 지원은 알고 보면 그다지 어렵거나 신기한 기술을 사용한 것이 아니다. 서비스의 경우 ServiceHost 클래스의 인스턴스가 생성될 때 configuration을 참고하여 해당 설정이 존재하면 그 설정을 읽어 ServiceHost 객체를 초기화하는 것이며, 클라이언트 역시 ChannelFactory<T> 클래스의 인스턴스가 생성될 때 종점에 대한 설정을 configuration에서 찾아서 ChannelFacotyr<T> 클래스를 초기화는 것이 전부이다. 그 이상도 그 이하도 아닌 말 그대로 서비스 혹은 클라이언트의 관련 객체를 초기화만 하는 것이다.

초기화가 끝난 이후에 ServiceHost 객체 혹은 ChannelFactory<T> 객체에 대해 코드를 사용하여 추가적인 설정을 하면 어떻게 될까? Configuration을 통한 설정이 먼저 수행되었고 코드에 의한 설정이 나중에 수행되므로 최종적으로 ServiceHost 객체 혹은 ChannelFactory<T> 객체에 남는 설정은 코드에 의해 진행된 설정만이 남게 된다. 구체적인 예를 들어 보자. [리스트 6]과 같은 configuration 설정이 제공될 때 다음 [리스트 10]은 configuration에서 설정해 놓은 바인딩의 MessageEncoding을 오버라이드(override) 하여 Text로 바꾸는 결과를 낳는다. 즉, 최종적으로 서비스의 바인딩이 사용하는 MessageEncoding은 MTOM이 아닌 Text라는 것이다.

리스트 10. Configuration 설정을 오버라이드 하는 서비스 예제

// 다음 코드에서 configuration을 읽어 ServiceHost를 초기화 한다.
ServiceHost host = new ServiceHost(typeof(HelloWorldWCFService));
// configuration 설정에 의해 초기화된 설정을 코드에 의해
// 변경할 수 있다.
foreach (ServiceEndpoint ep in host.Description.Endpoints) {
    BasicHttpBinding binding = ep.Binding as BasicHttpBinding;
    if (binding != null) {
        binding.MessageEncoding = WSMessageEncoding.Text;
    }
}
host.Open();
Console.WriteLine("Press Any key to stop the service");
Console.ReadKey();
host.Close();

[리스트 10]에서 ServiceHost 클래스의 Description 속성을 사용하여 호스트에 설정된 종점들을 액세스 하였다. Description 속성은 System.ServiceModel.Description 네임스페이스의 ServiceDescription 타입의 객체를 반환하며 ServiceDescription 타입은 ServiceHost 객체가 호스트 하는 서비스에 대한 서비스 타입, 서비스가 제공하는 종점들, 서비스의 네임스페이스 등에 대한 정보를 반환하는 클래스이다. 이 클래스는 Endpoints 컬렉션을 통해 서비스 호스트에서 제공하는 종점들에 대한 목록을 제공하므로 configuration을 통해 설정된 서비스 종점들의 목록을 알아낼 수 있다.

public class ServiceHost : ServiceHostBase
{
    public ServiceDescription Description { get; }
    ......
}

public class ServiceDescription
{
    public string ConfigurationName { get; set; }
    public ServiceEndpointCollection Endpoints { get; }
    public string Name { get; set; }
    public string Namespace { get; set; }
    public Type ServiceType { get; set; }
    ......
}

public class ServiceEndpoint
{
    public Binding Binding { get; set; }
    ......
}

Endpoints 속성이 반환하는 서비스 종점의 목록은 ServiceEndpoint 클래스에 대한 컬렉션이므로 foreach 문을 통해 각 ServiceEndpoint 객체에 접근이 가능할 뿐만 아니라 이 클래스의 Binding 속성을 통해 각 종점이 사용하는 바인딩이 무엇인지 알아낼 수 있다. 모든 바인딩 클래스들은 직/간접적으로 Binding 클래스에서 파생되었으므로 [리스트 10]과 같이 as 연산자를 통해 구체적인 바인딩이 무엇인지도 알아 낼 수 있다. 결과적으로 configuration에 설정된 BasicHttpBinding의 MessageEncoding 속성을 MTOM에서 Text 값으로 오버라이드 하는 코드가 바로 [리스트 10]인 것이다.

[리스트 10]과 같은 configuration 오버라이드에서 주의할 점은 일단 호스트가 리스닝을 시작한 후, 즉 Open 메쏘드가 호출된 후에는 설정을 변경할 수 없다는 것이다. 서비스 호스트가 Open 되어 수행된 이후에는 어떤 설정 변경도 효과를 발휘하지 않으며 때로는 InvalidOperationException 예외를 유발하므로 Open 전에 필요한 설정 변경을 수행해야 함을 잊지 말자.

클라이언트 역시 configuration 설정을 오버라이드 할 수 있다. 앞서 클라이언트 configuration 설정을 사용하는 방법에 대해 설명할 때 ChannelFactory<T> 클래스의 인스턴스가 생성될 때 configuration을 참조하여 채널 팩토리를 초기화 한다고 하였으므로, 초기화 이후에 코드를 통해 configuration 설정을 오버라이드 하는 것이 충분히 가능한 것이다. [리스트 11]는 configuration 설정을 오버라이드 하는 예제 코드를 보여주고 있다. 클라이언트는 서비스에 비해 좀 더 간단하게 오버라이드가 가능한데 ChannelFactory<T> 객체는 하나의 서비스 종점에 대한 정보만을 가지고 있기 때문이다. 서비스의 configuration 오버라이드와 마찬가지로 클라이언트의 설정 역시 ChannelFactory<T> 객체로부터 프록시가 생성되기 전(CreateChannel 메쏘드 호출 전)에 필요한 설정을 마쳐야 한다는 점에 주의하자. 프록시가 이미 생성된 이후에 설정되는 사항들은 이전에 생성된 프록시에는 적용되지 않는다.

리스트 11. Configuration 설정을 오버라이드 하는 클라이언트 예제
// 여기에서 configuration을 읽어 ChannelFactory 객체를 초기화한다.
ChannelFactory<IHelloWorld> factory =
    new ChannelFactory<IHelloWorld>("HttpHelloWorld");
// 다음과 같은 코드를 통해 configuration에 설정된 사항을
// 오버라이드 할 수 있다.
BasicHttpBinding binding = factory.Endpoint.Binding as BasicHttpBinding;
if (binding != null) {
    binding.MessageEncoding = WSMessageEncoding.Text;
}

IHelloWorld proxy = factory.CreateChannel();
string result = proxy.SayHello();
(proxy as IDisposable).Dispose();
Console.WriteLine(result);

[리스트 10]이나 [리스트 11]와 같이 configuration의 설정을 오버라이드 하는 코드가 필요한 이유가 무엇일까? 때때로 시스템 관리자나 운영자가 configuration에 의해 서비스 간의 통신 설정을 바꾸는 것이 문제를 유발할 수도 있다. 개발자는 이렇게 관리자가 잘못된 설정을 하는 것을 원천적으로 봉쇄하기 위해 configuration의 설정을 강제로 오버라이드 하는 코드를 삽입하여 서비스와 클라이언트가 정상적으로 작동하도록 강요할 수 있는 것이다. 필자가 살펴본 예제에서는 서비스와 클라이언트가 configuration 설정에 무관하게 항상 Text 메시지 인코딩을 사용하도록 개발자 수준에서 강요하는 코드가 사용되었으며 이렇게 함으로써 개발자는 개발자대로 원하는 설정을 강요하고 관리자 혹은 운영자는 개발적인 요소에 무관하게 필요할 설정을 수행할 수 있게 된다. 이처럼 WCF는 개발적인 요소와 관리/운영적인 요소를 독립적으로 진행할 수 있는 유연함을 제공한다.


authored by

  hskim618
  2009-01-03(18:54)
캐릭 이미지
다음 강좌는 언제 올라오나요? ^^

 
 
.NET과 Java 동영상 기반의 교육사이트

로딩 중입니다...

서버 프레임워크 지원 : NeoDEEX
based on ASP.NET 3.5
Creative Commons License
{5}
{2} 읽음   :{3} ({4})