응용 프로그램의 메모리 부족 현상이 발생하지 않도록 다양한 처리량 설정을 제어


하려면 ServiceThrottlingBehavior 클래스를 사용합니다.

MaxConcurrentCalls 속성은 ServiceHost에서 현재 처리되는 메시지 수를 제한합니다.

MaxConcurrentInstances 속성은 ServiceHost에서 한 번에 실행하는 InstanceContext 개체 수를 제한합니다.

MaxConcurrentSessions 속성은 ServiceHost 개체에서 수락할 수 있는 세션 수를 제한합니다.

런타임 부하 분산 기능을 사용하려면 응용 프로그램 실행 경험이 필요하므로, 응용 프로그램 구성 파일을 통해 ServiceThrottlingBehavior를 사용하는 것이 서비스 성능을 극대화하도록 실행을 수정하는 가장 일반적인 방법입니다

image

 

<configuration>
  <appSettings>
    <!-- use appSetting to configure base address provided by host -->
    <add key="baseAddress" value="http://localhost:8080/ServiceMetadata" />
  </appSettings>
  <system.serviceModel>
    <services>
      <service 
        name="Microsoft.WCF.Documentation.SampleService"
        behaviorConfiguration="Throttled" >
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/SampleService"/>
          </baseAddresses>
        </host>
        <endpoint
          address=""
          binding="wsHttpBinding"
          contract="Microsoft.WCF.Documentation.ISampleService"
         />
        <endpoint
          address="mex"
          binding="mexHttpBinding"
          contract="IMetadataExchange"
         />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior  name="Throttled">
          <serviceThrottling 
            maxConcurrentCalls="1" 
            maxConcurrentSessions="1" 
            maxConcurrentInstances="1"
          />
          <serviceMetadata 
            httpGetEnabled="true" 
            httpGetUrl=""
          />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

 

MSDN Duplex Services

http://msdn.microsoft.com/en-us/library/ms731064.aspx 

 

WCF : Duplex 패턴을 이용한 비동기 호출 구현

http://www.ensimple.net/enSimple/show.aspx?cnum=407&b_id=study_netfr3&page=1

 

InstanceContextMode

서비스 인스턴스가 언제 생성될지를 명시하는 속성으로서, 인스턴스의 생명주기 유형을 결정한다.
InstanceContextMode는 PerCall, PerSession, Single 이렇게 세게의 옵션이 존재한다.
PerCall : 클라이언트가 호출 할 때마다 (새로운) 서비스 인스턴스가 생성되고 호출이 완료될 때, 소멸한다.
호출 마다 새로운 인스턴스가 생성되므로 호출 간에 서비스 인스턴스 내에서(메모리 상에서) 상태 정보를 유지하는 것은 불가하다.
PerSession : 세션(프록시가 생성되고, Close 될 때 까지의 시간) 마다 새로운 서비스 인스턴스가 생성된다. 세션이 종료(Close) 되면 소멸한다. 세션 내에서 일어나는 호출들 간에 서비스 인스턴스 내에서(메모리 상에서) 서로 상태 정보를 공유할 수 있다.
Single : 오직 하나의 서비스 인스턴스 만이 모든 클라이언트로 부터의 호출을 처리한다.
서비스 인스턴스가 소멸되기 전 까지 모든 클라이언트로부터의 모든 호출은 서로 상태 정보를 공유할 수 있다.
** 첨부된 샘플에 간단한 상태 정보 유지에 대한 처리가 포함되어 있다. PerCall, PerSession에 대한 차이를 확인해 보기 바란다.
** 이와 관련해서 아주 훌륭한 글이 있다. 참조 바란다. 유경상 님의 블로그 : http://www.simpleisbest.net/articles/1731.aspx

 

ConcurrecyMode


ConcurrencyMode는 서비스 인스턴스가 멀티스레드로 작동할 것이지 여부를 지정하는 속성이다.
Multiple, Single, Reentrance 이렇게 세가지 모드를 지원한다.
Multiple : Multi-threaded 이다. 그래서 여러 호출을 동시에 처리할 수 있다. 그래서 여러 호출이 하나의 상태 정보를 동시에 공유할 수도 있다. 이때 그 상태 정보의 동기화는 개발자의 책임이다.
Single : Single-threaded이다. 한번에 하나의 호출만 처리한다. 서비스 인스턴스가 호출을 처리 중이면, 다른 호출은 이전 호출이 완료될 때까지 대기하게 될 것이다.
Reentrance : Single-threaded이다. 그러나 서비스 인스턴스가 호출 처리 중에 다른 호출이 요청되면 이를 수용할 수 있는 방식이다.
호출 처리 중에 다시 다른 호출을 처리하도록 하는 것이다. 별도의 스레드로 다른 호출을 처리하는 것이 아니라 하나의 스레드 내에서 여러 호출을 처리하는 것이므로 호출 간에 상태정보를 동기화하는 것이 중요할 수 있으며 역시 개발자 책임이다.
이는 서비스 내에서 다른 서비스를 호출하는 경우에 사용하게 되는데,
클라이언트에서 서비스1 호출 ▷서비스1에서 서비스2 호출 ▷서비스2에서 다시 서비스1을 호출(Call back) 과 같은 경우, 서비스 1이 Reentrance가 아니면 Dead lock이 발생하게 될 것이다..-_-

 

출처: http://www.microsoft.com/korea/msdn/msdnmag/issues/06/10/WCFEssentials/default.aspx#S1

목차
단방향 작업
콜백 작업
클라이언트 콜백 설정
콜백 재입력
이벤트
게시-구독 프레임워크
영구 구독자 관리
이벤트 게시
영구 구독자 관리
대기 중인 게시자 및 구독자
결론

기존의 개체 및 구성 요소 지향 프로그래밍 모델은 클라이언트에서 메서드를 호출하는 방법을 하나만 제공합니다. 클라이언트가 호출을 발행하고 호출이 진행되는 동안에는 차단된 다음, 해당 메서드가 반환되면 실행을 계속하는 방식입니다. WCF(Windows® Communication Foundation)는 이러한 기존 호출 모델을 지원하는 동시에 두 가지 추가 작업 유형도 기본으로 지원합니다. 즉, 실행 후 더 이상 추적하지 않는 형태의 작업을 위한 단방향 호출과 서비스에서 클라이언트로의 콜백이 가능한 이중 콜백을 지원합니다.

Windows Presentation Foundation 응용 프로그램 모델은 독립 실행형 응용 프로그램과 브라우저 응용 프로그램이라는 두 가지 응용 프로기본적으로 Windows Communication Foundation 작업은 요청-응답 형식입니다. 이는 클라이언트가 메시지 형식으로 요청을 발행한 다음 응답 메시지를 받을 때까지 차단되는 방식입니다. 기본 시간 제한인 1분 이내에 서비스에서 응답하지 않으면 클라이언트는 TimeoutException을 받게 됩니다. 또한 통신 또는 서비스 쪽에서 예외가 발생한 경우 프록시에서 클라이언트 쪽에 예외를 일으킵니다. NetPeerTcpBinding 및 NetMsmqBinding을 제외한 모든 바인딩에서 요청-응답 작업을 지원합니다.

먼저 Windows Communication Foundation 작업 호출 방법 및 관련 디자인 지침을 제시한 다음, 이벤트를 게시하고 구독하기 위한 사용자 지정 프레임워크의 구성 요소로 이러한 작업 형식을 사용하는 방법에 대해 설명합니다. 이 과정에서 다양한 고급 Microsoft® .NET Framework 및 Windows Communication Foundation 프로그래밍 기법을 소개합니다.

단방향 작업

작업에 반환되는 값이 없고 클라이언트에서 호출의 성공 또는 실패에 신경 쓰지 않는 경우가 있습니다. Windows Communication Foundation에서는 이처럼 실행 후 더 이상 추적하지 않는 형식의 호출을 지원하기 위해 단방향 작업을 제공합니다. 클라이언트에서 호출을 발행하면 Windows Communication Foundation에서 요청 메시지를 생성하지만 이와 연결된 어떤 응답 메시지도 클라이언트에 반환되지 않습니다. 따라서 단방향 작업에서는 값이 반환될 수 없으며 서비스 쪽에 발생한 예외도 클라이언트로 보내지지 않습니다. 단방향 호출이 비동기 호출과 동일한 것은 아닙니다. 단방향 호출은 서비스에 도달할 때 모든 호출이 한 번에 발송되지 않고 서비스 쪽의 대기열에 있으면서 해당 서비스에 구성된 동시성 모드 동작 및 세션 모드에 따라 한 번에 하나씩 발송됩니다. 단방향 또는 요청-응답에 관계없이 서비스에서 대기열에 포함하는 메시지의 수는 구성된 채널과 안정성 모드에 따라 결정됩니다. 대기 중인 메시지 수가 대기열의 용량을 초과하는 경우 단방향 호출을 발행하는 경우라도 클라이언트가 차단됩니다. 일단 호출이 대기열에 포함되면 클라이언트 차단이 해제되어 계속 실행할 수 있으며, 서비스에서는 백그라운드로 해당 작업을 처리합니다. 이러한 특징으로 인해 비동기 호출과 비슷한 것처럼 보이게 됩니다. 모든 Windows Communication Foundation 바인딩은 단방향 작업을 지원합니다.

OperationContract 특성은 IsOneWay 부울 속성을 제공합니다. 이 속성의 기본값은 false로, 이는 요청-응답 작업을 의미합니다. 그러나 다음과 같이 IsOneWay 속성을 true로 설정하면 해당 메서드가 단방향 작업으로 구성됩니다.

[ServiceContract]
interface IMyContract
{
   [OperationContract(IsOneWay = true)]
   void MyMethod()
}

단방향 작업에 연결된 응답이 없기 때문에 반환되는 값이나 결과를 갖는 지점이 존재하지 않으며 작업은 보내는 매개 변수가 없는 void 반환 형식을 가져야 합니다. Windows Communication Foundation에서는 언어 컴파일러에 대한 연결이 없고 컴파일 시 올바른 사용법을 확인할 수 없으므로 런타임 시 호스트를 로드할 때 및 일치하지 않을 경우 InvalidOperationException을 발생시킬 때 메서드 서명을 확인하여 이를 적용합니다.

클라이언트가 호출의 결과에 신경쓰지 않는다는 것이 호출이 발생했는지에 대해 전혀 관심을 갖지 않는다는 의미는 아닙니다. 일반적으로 단방향 호출에 대해서도 서비스에 대한 안정성을 설정해야 합니다. 이렇게 하면 요청이 서비스로 배달되도록 보장하게 됩니다. 그러나 단방향 호출의 경우 클라이언트에서 단방향 작업의 호출 순서에 신경쓸 수도 있고 신경쓰지 않을 수도 있습니다. 이는 Windows Communication Foundation을 사용하여 순서에 따른 배달 및 실행을 설정하는 것과 안정적인 배달을 설정하는 것을 분리할 수 있도록 한 주요 이유 중 하나입니다.

 

콜백 작업

Windows Communication Foundation은 서비스가 클라이언트를 다시 호출할 수 있도록 지원합니다. 콜백하는 동안에는 많은 점에서 양상이 바뀌게 됩니다. 즉, 서비스가 클라이언트가 되고 클라이언트가 서비스가 됩니다(그림 1 참조).

그림 1 콜백 작업
그림 1 콜백 작업

또한 클라이언트는 콜백 개체를 쉽게 호스팅할 수 있도록 해야 합니다. 모든 바인딩이 콜백 작업을 지원하는 것은 아닙니다. HTTP는 기본적으로 비연결형 프로토콜이기 때문에 콜백에 사용할 수 없으므로 BasicHttpBinding 또는 WSHttpBinding에서 콜백을 사용할 수 없습니다. Windows Communication Foundation은

NetTcpBinding 및 NetNamedPipeBinding에 대한 콜백을 지원하는데, 이는 기본 전송이 양방향이기 때문입니다. Windows Communication Foundation에서는 HTTP를 통한 콜백을 지원하기 위해 실제로 두 개의 HTTP 채널을 설정하는 WSDualHttpBinding을 제공합니다.

한 채널은 클라이언트에서 서비스로의 호출을 위한 것이며, 다른 한 채널은 서비스에서 클라이언트로의 호출을 위한 것입니다.

서비스 계약에는 최대 하나의 콜백 계약이 있을 수 있습니다. 일단 콜백 계약이 정의되면 클라이언트에서 콜백을 지원해야 하고 모든 호출에서 서비스에 대한 콜백 끝점도 제공해야 합니다. ServiceContract 특성은 Type 형식의 CallbackContract 속성을 제공합니다. 이 속성을 콜백 계약 형식으로 설정하고 다음과 같이 콜백 계약의 정의를 제공해야 합니다.

interface IMyContractCallback
{
   [OperationContract] 
   void OnCallback();
}

[ServiceContract(CallbackContract = typeof(IMyContractCallback))] 
interface IMyContract
{
   [OperationContract] 
   void DoSomething();
}

콜백 계약은 ServiceContract 특성을 내포하고 있으므로 이 특성으로 표시될 필요가 없습니다.

클라이언트 쪽의 가져온 콜백 인터페이스의 이름이 원래의 서비스 쪽 정의의 이름과 같을 필요는 없습니다. 대신 Callback이라는 접미사가 붙은 서비스 계약 인터페이스 이름을 갖게 됩니다.

 

클라이언트 콜백 설정

콜백 개체를 호스트하고 콜백 끝점을 노출하는 것은 클라이언트에 달려 있습니다. 서비스 인스턴스의 실행 범위에서 가장 안쪽은 다음과 같은 인스턴스 컨텍스트입니다.

public sealed class InstanceContext : CommunicationObject, ...
{
   public InstanceContext(object implementation);
   ... // 기타 멤버 
}

클라이언트에서 콜백 개체를 호스트하기 위해 해야 할 일은 다음과 같이 해당 콜백 개체를 인스턴스화하고 이를 둘러싼 컨텍스트를 만드는 것입니다.

class MyCallback : IMyContractCallback 
{
   public void OnCallback() {...}
}
IMyContractCallback callback = new MyCallback();
InstanceContext context = new InstanceContext(callback);

콜백 계약을 정의하는 서비스 끝점의 계약과 상호 작용할 때마다 클라이언트는 프록시를 사용하여 양방향 통신을 설정하고 서비스에 콜백 끝점 참조를 전달해야 합니다. 클라이언트에서 사용하는 프록시는 그림 2에서 볼 수 있는 DuplexClientBase<T> 특수 프록시 클래스에서 파생되어야 합니다.

클라이언트는 DuplexClientBase<T> 생성자에게 일반 프록시에서처럼 서비스 끝점 정보뿐만 아니라 콜백 개체를 호스트하는 인스턴스 컨텍스트도 제공합니다. 프록시는 콜백 컨텍스트 주변의 끝점을 만드는 한편 서비스 끝점 구성에서 콜백 끝점의 세부 정보를 유추합니다. 콜백 끝점 계약은 서비스 계약 콜백 형식에 의해 정의됩니다. 콜백 끝점에는 보내는 호출과 동일한 바인딩(실제로는 전송)이 사용됩니다. Windows Communication Foundation에서는 주소에 클라이언트의 컴퓨터 이름을 사용하며 HTTP를 사용할 때 포트까지 선택합니다. 인스턴스 컨텍스트를 이중 프록시에 전달하고 이 프록시를 사용하여 서비스를 호출함으로써 해당 클라이언트 쪽 콜백 끝점을 노출할 수 있습니다.

SvcUtil 또는 Visual Studio® 2005를 사용하여 프록시 클래스를 생성하는 경우 이 도구에서는 그림 3과 같이 DuplexClientBase<T>에서 파생되는 클래스를 생성합니다. 클라이언트는 콜백 인스턴스를 만들어 컨텍스트에서 이 인스턴스를 호스트하고, 프록시를 만든 다음 서비스를 호출함으로써 콜백 끝점 참조를 전달합니다.

class MyCallback : IMyContractCallback 
{
   public void OnCallback() {...}
}
IMyContractCallback callback = new MyCallback();
InstanceContext context = new InstanceContext(callback);

MyContractClient proxy = new MyContractClient(context);
proxy.DoSomething();

클라이언트는 콜백을 기다리고 있는 동안에는 프록시를 닫을 수 없습니다. 이 경우 프록시를 닫으면 콜백 끝점이 닫히므로 서비스에서 콜백을 시도할 때 서비스 쪽에서 오류가 발생하게 됩니다. 클라이언트 자체에서 종종 콜백 계약을 구현하기도 하는데, 이 경우 대개 클라이언트는 그림 4와 같이 프록시에 멤버 변수를 사용하며 클라이언트가 삭제될 때 이를 닫습니다.

클라이언트 쪽 콜백 끝점 참조는 클라이언트에서 서비스로 보내는 모든 호출과 함께 전달되며 들어오는 메시지의 일부입니다. OperationContext 클래스는 GetCallbackChannel<T> generic 메서드를 통해 서비스에서 콜백 참조에 쉽게 액세스할 수 있도록 합니다. 서비스에서 콜백 참조를 사용하여 수행하는 작업과 콜백 참조를 사용하도록 결정하는 시기는 서비스의 재량에 달려 있습니다. 서비스는 작업 컨텍스트에서 콜백 참조를 추출한 다음 나중에 사용하도록 저장하거나 서비스 작업 중에 이 참조를 사용하여 클라이언트로 콜백할 수 있습니다. 그림 5는 첫 번째 옵션을 보여 줍니다.

앞에서 본 것과 같은 콜백 계약 정의를 사용할 경우 서비스에서 정적 generic 목록을 사용하여 IMyContractCallback 형식의 인터페이스에 대한 참조를 저장합니다. 서비스에서는 어느 클라이언트가 호출하고 있는지, 또 해당 클라이언트가 이미 서비스를 호출했는지 여부를 알 수 없으므로 모든 호출을 검사하여 해당 콜백 참조가 이미 목록에 있는지 확인합니다. 목록에 참조가 없으면 서비스에서 해당 콜백을 목록에 추가합니다. 서비스 클래스도 정적 메서드인 CallClients를 제공합니다. 호스트 쪽에서는 다음과 같이 이 메서드만 사용하여 클라이언트로 콜백할 수 있습니다.

MyService.CallClients();

이렇게 호출되는 경우 호출하는 측에서는 콜백 호출에 일부 호스트 쪽 스레드를 사용합니다. 이러한 스레드는 들어오는 서비스 호출을 실행하는 스레드와는 관계가 없습니다.

 

콜백 재입력

서비스에서는 전달된 콜백 참조를 호출해야 하거나 계약 작업을 실행하는 동안 콜백 목록을 호출해야 할 수도 있습니다. 그러나 서비스 클래스는 기본적으로 단일 스레드 액세스로 구성되므로 이러한 호출은 허용되지 않습니다. 서비스 인스턴스는 잠금과 연결되며, 한 번에 하나의 스레드만 잠금을 소유하여 해당 서비스 인스턴스에 액세스할 수 있습니다. 작업 중에 클라이언트를 호출하는 경우 콜백을 호출하는 동안 서비스 스레드를 차단해야 합니다. 그러나 문제는 콜백이 반환되면 클라이언트에서 응답 메시지를 처리할 때 동일한 잠금에 대한 소유권이 필요하게 되고 따라서 교착 상태가 발생한다는 점입니다.

Windows Communication Foundation에서는 교착 상태를 방지하기 위해 단일 스레드 서비스 인스턴스에서 클라이언트로 콜백하려고 시도할 때 InvalidOperationException을 발생시킵니다. 가능한 해결 방법은 세 가지입니다. 첫 번째 방법은 다중 스레드 액세스에 대한 서비스를 구성하는 것입니다. 이 경우 잠금과 연결되지 않으므로 콜백이 허용됩니다. 그러나 이렇게 하면 서비스에 동기화를 제공해야 하므로 서비스 개발자의 부담이 가중됩니다.

두 번째 해결 방법은 재입력에 대해 서비스를 구성하는 것입니다. 서비스 인스턴스는 재입력에 대해 구성되는 경우에도 여전히 잠금과 연결되며 단일 스레드 액세스만 허용됩니다. 그러나 서비스가 클라이언트로 콜백하는 경우 Windows Communication Foundation에서 먼저 자동으로 잠금을 해제합니다.

다음 코드에서는 재입력에 대해 구성된 서비스를 보여 줍니다. 서비스는 작업 실행 중에 작업 컨텍스트에 도달하여 콜백 참조를 가져온 다음 호출합니다. 컨트롤은 콜백이 반환된 후에만 서비스에 반환되며 서비스 자체의 스레드에서 잠금을 다시 확보해야 합니다.

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
class MyService : IMyContract
{
   public void DoSomething()
   {
      IMyContractCa llback callback = OperationContext.Current.
         GetCallbackChannel<IMyContractCallback>();
      callback.OnCallback();
   }
}

서비스가 안전하게 클라이언트로 콜백할 수 있도록 해 주는 세 번째 방법은 콜백 계약 작업을 단방향 작업으로 구성하는 것입니다. 이렇게 하면 잠금을 얻기 위해 경합하는 응답 메시지가 없기 때문에 동시성이 단일 스레드로 설정된 경우에도 서비스에서 콜백이 가능하게 됩니다.

 

이벤트

기본적으로 이중 콜백은 이벤트와 함께 사용합니다. 이벤트를 사용하면 서비스 쪽에 발생한 상황을 클라이언트에 알릴 수 있습니다. 이벤트는 직접적인 클라이언트 호출로 인해 발생하거나 서비스의 모니터링 대상으로 인해 발생할 수 있습니다. 그림 6과 같이 이벤트를 발생시키는 서비스를 게시자라고 하며, 이벤트를 받는 클라이언트를 구독자라고 합니다.

그림 6 게시자와 구독자
그림 6 게시자와 구독자

Windows Communication Foundation의 이벤트는 콜백 작업에 지나지 않지만 본질적인 특성 탓에 일반적으로 클라이언트와 서비스 간의 관계보다 더 느슨한 게시자와 구독자 간의 관계를 의미합니다. 서비스는 이벤트를 처리할 때 대개 여러 구독자에게 동일한 이벤트를 게시합니다. 일반적으로 게시자는 구독자의 호출 순서, 또는 이벤트를 처리할 때 구독자에게 발생할 수 있는 오류에 대해 신경 쓰지 않습니다. 또한 서비스는 구독자로부터 반환되는 결과에 대해서도 신경 쓰지 않습니다. 따라서 이벤트 처리 작업은 void 반환 형식을 가져야 하며 보내는 매개 변수가 없어야 하고 단방향 작업으로 표시되어야 합니다. 이벤트를 개별 콜백 계약으로 분류하고, 동일한 계약에서 이벤트와 일반 콜백을 혼합하지 않는 것이 좋습니다.

interface IMyEvents
{
   [OperationContract(IsOneWay = true)]
   void OnEvent1();

   [OperationContract(IsOneWay = true)]
   void OnEvent2(int number);

   [OperationContract(IsOneWay = true)]
   void OnEvent3(int number, string text);
}

그러나 이벤트에 이중 콜백을 그대로 사용하는 경우 종종 게시자와 구독자 간에 너무 많은 연결이 발생하게 됩니다. 구독자는 응용 프로그램에서 게시되고 있는 모든 서비스의 위치를 알고 해당 서비스에 연결해야 합니다. 구독자가 알지 못하는 게시자는 해당 구독자에게 이벤트를 알릴 수 없습니다. 이 경우 이미 배포된 응용 프로그램에 새 구독자를 추가하거나 기존 구독자를 제거하기가 어렵게 됩니다. 구독자가 응용 프로그램에서 특정 형식의 이벤트가 발생할 때마다 자신에게 알리도록 요청할 방법은 없습니다. 또한 구독자는 구독하거나 구독을 취소하기 위해서는 각 게시자에 대한 호출을 여러 번 수행해야 하는데, 여기에는 많은 비용이 소요될 수 있습니다. 여러 명의 게시자가 동일한 이벤트를 발생시키지만 이들이 제공하는 구독 및 구독 취소 방법은 조금씩 다를 수 있습니다. 이 경우 당연히 구독자는 이러한 메서드에 연결됩니다.

마찬가지로, 게시자는 자신이 알고 있는 사항만 구독자에게 알릴 수 있습니다. 게시자가 어떤 이벤트를 받기를 원하는 모든 구독자에게 해당 이벤트를 전달할 방법은 없으며 이벤트를 브로드캐스트할 수도 없습니다. 또한 모든 게시자에게는 구독자 목록과 게시 작업을 직접 관리하는 데 필요한 코드가 있어야 합니다. 이 코드는 서비스가 해결해야 하는 비즈니스 문제와는 거의 관계가 없으며, 동시 게시와 같은 고급 기능을 사용하려는 경우 상당히 복잡하게 될 수 있습니다.

더욱이 이중 콜백에는 게시자와 구독자의 수명선 간에 연결이 발생하게 됩니다. 구독자는 이벤트를 구독하고 받으려면 가동되어 실행 중이어야 합니다. 구독자는 이벤트가 발생하면 응용 프로그램에서 구독자의 인스턴스를 만들어 해당 이벤트를 처리하도록 요청할 수 없습니다.

끝으로 구독은 프로그래밍 방식으로 설정해야 합니다. 시스템이 실행 중일 때 응용 프로그램에서 구독을 구성하거나 구독자의 기본 설정을 변경하기 위한 손쉬운 관리 방법은 없습니다.

이러한 문제에 대한 해결 방법은 그림 7에서 볼 수 있듯이 게시-구독 디자인 패턴을 사용하여 주변을 디자인하고 그 사이에 전용 구독 서비스와 전용 게시 서비스를 도입하여 구독자와 게시자를 분리하는 것입니다.

그림 7 게시-구독 시스템
그림 7 게시-구독 시스템

이벤트를 구독하려는 구독자는 구독 서비스에 등록하며, 구독 서비스는 구독자 목록을 관리하고 구독 취소에서도 이와 비슷한 기능을 제공합니다. 마찬가지로, 모든 게시자는 게시자 서비스를 통해 이벤트를 발생시키며 구독자에게 이벤트가 직접 전달되지 않도록 합니다. 가입 및 게시 서비스는 시스템을 분리하는 우회 계층을 제공합니다. 구독자는 게시자 ID에 대해 더 이상 알 필요가 없습니다. 구독 메커니즘이 모든 게시자에 대해 일정하기 때문에 구독자는 이벤트 형식을 구독하여 게시자로부터 이벤트를 받을 수 있습니다. 실제로 게시자는 구독 목록을 관리하지 않아도 되며 구독자가 누구인지 알지도 못 합니다. 게시자는 관심 있는 구독자가 받을 수 있도록 이벤트를 게시 서비스에 배달합니다.

임시 구독자와 영구 구독자 두 가지 종류의 구독자를 정의할 수 있습니다. 임시 구독자는 메모리에 있는 실행 중인 구독자이며, 영구 구독자는 디스크에 존속하는 구독자로 이벤트 발생 시에 호출할 서비스를 나타냅니다. 임시 구독자의 경우 실행 중인 서비스에 콜백 참조를 전달하기 위한 간단한 방법으로 이중 콜백 메커니즘을 사용할 수 있습니다. 영구 구독자의 경우 구독자 주소만 참조로 기록하면 됩니다. 이벤트가 발생하면 게시 서비스에서 영구 구독자 주소를 호출하여 이벤트를 배달하게 됩니다. 두 가지 구독 형식 간의 또 다른 중요한 차이점은 영구 구독의 경우 디스크나 데이터베이스에 저장할 수 있다는 것입니다. 이렇게 저장하면 응용 프로그램 종료 또는 시스템 충돌 및 재시작 시에도 구독이 유지되므로 관리자가 구독을 구성할 수 있게 됩니다. 응용 프로그램 종료 시에는 임시 구독을 저장하지 못하므로 응용 프로그램을 시작할 때마다 명시적으로 임시 구독을 설정해야 합니다.

 

게시-구독 프레임워크

이 기사에서 제공되는 소스 코드에는 완전한 게시-구독 예제가 포함되어 있습니다. 필자는 샘플 게시-구독 서비스 및 클라이언트뿐만 아니라, 이러한 서비스의 구현과 응용 프로그램에 대한 지원 추가를 자동화하는 범용 프레임워크도 제공하도록 이 코드를 작성했습니다. 프레임워크를 만들 때 첫 번째 단계는 게시-구독 관리 인터페이스를 분류하고 임시 구독과 영구 구독에 대해 각각 별도의 게시 계약을 제공하는 것이었습니다.

임시 구독 관리를 위해 다음과 같이 ISubscriptionService 인터페이스를 정의했습니다.

 [ServiceContract]
public interface ISubscriptionService
{
   [OperationContract]
   void Subscribe(string eventOperation);

   [OperationContract]
   void Unsubscribe(string eventOperation);
}

ISubscriptionService는 구현하는 끝점에서 예상되는 콜백 계약을 식별하지 않습니다. 콜백 인터페이스는 다음과 같이 ISubscriptionService로부터 파생되어 원하는 콜백 계약을 지정함으로써 응용 프로그램에서 제공됩니다.

[ServiceContract(CallbackContract = typeof(IMyEvents))]
interface IMySubscriptionService : ISubscriptionService {}

ISubscriptionService의 하위 인터페이스는 작업을 추가하지 않아도 됩니다. 임시 구독 관리 기능은 ISubscriptionService에 의해 제공됩니다. Subscribe 또는 Unsubscribe에 대한 각각의 호출에서 구독자는 구독하거나 구독 취소할 작업(이벤트)의 이름을 제공해야 합니다. 호출자는 모든 이벤트에서 구독하거나 구독 취소하려면 빈 문자열이나 null 문자열을 전달하면 됩니다.

필자의 프레임워크는 다음과 같이 SubscriptionManager<T> generic 추상 클래스에 ISubscriptionService의 메서드에 대한 구현을 제공합니다.

public abstract class SubscriptionManager<T> where T : class
{
   public void Subscribe(string eventOperation);
   public void Unsubscribe(string eventOperation);
   ... // 기타 멤버 
}

SubscriptionManager<T>에 대한 generic 형식 매개 변수는 이벤트 계약입니다. SubscriptionManager<T>는 ISubscriptionService에서 파생되지 않습니다.

응용 프로그램에서는 ISubscriptionService의 특정 하위 인터페이스를 지원하는 끝점 형식으로 자체의 임시 구독 서비스를 노출해야 합니다. 이렇게 하려면 응용 프로그램은 SubscriptionManager<T>에서 파생되는 서비스 클래스를 제공하고 콜백 계약을 형식 매개 변수로 지정하고 ISubscriptionService의 특정 하위 인터페이스에서 파생되어야 합니다.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MySubscriptionService : 
   SubscriptionManager<IMyEvents>,IMySubscriptionService {}

IMySubscriptionService는 새로운 작업을 추가하지 않고 SubscriptionManager<T>가 ISubscriptionService의 메서드를 이미 구현하고 있으므로 MySubscriptionService에는 코드가 필요하지 않습니다.

끝으로 응용 프로그램에서 IMySubscriptionService에 대한 끝점을 정의해야 합니다.

<services>
   <service name ="MySubscriptionService">
      <endpoint
         address = "..."
         binding = "..."
         contract= "IMySubscriptionService"
      />
   </service>
</services>

그림 8은 SubscriptionManager<T>에서 임시 구독을 관리하는 방식을 보여 줍니다. SubscriptionManager<T>는 다음과 같이 m_TransientStore라고 하는 정적 generic 사전에 임시 구독자를 저장합니다.

static Dictionary<string,List<T>> m_TransientStore;

사전의 각 항목에는 이벤트 작업의 이름과 해당되는 모든 구독자가 연결된 목록 형식으로 들어 있습니다. SubscriptionManager<T>의 정적 생성자는 리플렉션을 사용하여 콜백 인터페이스(SubscriptionManager<T>에 대한 형식 매개 변수)의 모든 작업을 가져오고 사전을 초기화하여 모든 작업의 목록이 비어 있도록 합니다. Subscribe 메서드는 작업 호출 컨텍스트에서 콜백 참조를 추출합니다. 호출자가 작업 이름을 지정하면 Subscribe에서 AddTransient 도우미 메서드를 호출하여 저장소에서 해당 이벤트에 대한 구독자 목록을 검색합니다. 목록에 구독자가 없으면 AddTransient가 추가합니다.

호출자가 작업 이름으로 빈 문자열이나 null 문자열을 지정한 경우 Subscribe는 콜백 계약의 각 작업에 대해 AddTransient를 호출합니다. 구독 취소도 이와 비슷한 방식으로 수행됩니다. 호출자는 모든 이벤트를 구독한 다음 특정 이벤트를 구독 취소할 수 있습니다.

 

영구 구독자 관리

영구 구독자를 관리하기 위해 그림 9의 IPersistentSubscriptionService 인터페이스를 정의했습니다.

영구 구독자를 추가하려면 호출자가 PersistSubscribe를 호출하여 구독자 주소, 이벤트 계약 이름 및 관련 이벤트 작업을 직접 제공해야 합니다. 구독을 취소하려면 이와 동일한 정보로 PersistUnsubscribe를 사용합니다. IPersistentSubscriptionService는 어떤 방식으로도 서비스 쪽에서의 구독자 위치를 나타내지 않습니다. 이는 구현 세부 사항입니다.

앞에서 나온 SubscriptionManager<T> 클래스도 IPersistentSubscriptionService의 메서드를 구현합니다.

public abstract class SubscriptionManager<T> where T : class
{
   public void PersistUnsubscribe(
      string address, string eventsContract, string eventOperation);
   public void PersistSubscribe(
      string address, string eventsContract, string eventOperation);
   ... // 기타 멤버 
}

SubscriptionManager<T>는 SQL Server™에 영구 구독자를 저장합니다. SubscriptionManager<T>는 IPersistentSubscriptionService에서 파생되지 않습니다. 사용하는 응용 프로그램에서는 자체 영구 구독 서비스를 노출해야 하지만 콜백 참조가 필요하지 않으므로 IPersistentSubscriptionService에서 새 계약을 파생시킬 필요는 없습니다. 응용 프로그램은 SubscriptionManager<T>에서 파생되어 형식 매개 변수로 이벤트 계약을 지정하고 IPersistentSubscriptionService의 파생물을 추가하면 됩니다.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MySubscribtionService : SubscriptionManager<IMyEvents>,
   IPersistentSubscriptionService {} 

SubscriptionManager<T>에서 이미 IPersistentSubscriptionService의 메서드를 구현하고 있으므로 MySubscriptionService에는 코드가 필요하지 않습니다.

끝으로 응용 프로그램에서 IPersistentSubscriptionService에 대한 끝점을 정의해야 합니다.

<services>
   <service name ="MySubscriptionService">
      <endpoint
         address = "..."
         binding = "..."
         contract= "IPersistentSubscriptionService"
      />
   </service>
</services>

그림 10에서는 SubscriptionManager<T>의 IPersistentSubscriptionService 메서드 구현을 보여 주는데, 이는 구독자가 사전의 메모리 내부가 아닌 SQL Server에 저장된다는 것을 제외하고는 그림 8과 비슷합니다.

응용 프로그램에서 동일한 이벤트 계약에 대해 임시 구독자와 영구 구독자를 모두 지원해야 하는 경우 ISubscriptionService의 특수 하위 인터페이스 및 IPersistentSubscriptionService에서 구독 서비스 클래스를 파생시키고 일치하는 두 끝점을 노출하기만 하면 됩니다.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MySubscriptionService : SubscriptionManager<IMyEvents>,
   IMySubscriptionService,IPersistentSubscriptionService {}

 

이벤트 게시

필자의 프레임워크를 사용하면 게시 서비스를 손쉽게 구현할 수 있습니다. 이 서비스는 구독자와 동일한 이벤트 계약을 지원해야 하고 응용 프로그램의 게시자에게 알려지는 유일한 계약 지점이어야 합니다. 게시 서비스가 끝점의 이벤트 계약을 노출하기 때문에 이벤트 계약을 서비스 계약으로 표시해야 합니다. 이는 임시 구독자와의 이중 콜백을 위해서만 이벤트 계약을 사용하는 경우에도 마찬가지입니다.

[ServiceContract]
interface IMyEvents {...

게시-구독 프레임워크에는 다음과 같이 정의된 PublishService<T> 도우미 클래스가 있습니다.

public abstract class PublishService<T> where T : class
{
   protected static void FireEvent(params object[] args);
}

PublishService<T>에는 형식 매개 변수로서 이벤트 계약 형식이 필요합니다. 자신만의 게시 서비스를 제공하려면 PublishService<T>에서 파생시키고 FireEvent 메서드를 사용하여 임시 또는 영구 구독자에 관계없이 모든 구독자에게 이벤트를 배달합니다.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyPublishService : PublishService<IMyEvents>,IMyEvents
{
   public void OnEvent1() { FireEvent(); }
   public void OnEvent2(int number) { FireEvent(number); }
   public void OnEvent3(int number,string text) {
      FireEvent(number,text); 
   }
}

params 개체 배열을 사용하므로 매개 변수에 상관없이 FireEvent를 통해 모든 형식의 이벤트를 발생시킬 수 있습니다.

끝으로 응용 프로그램은 이벤트 계약이 있는 게시 서비스에 대한 끝점을 노출해야 합니다.

<services>
   <service name ="MyPublishService">
      <endpoint
         address = "..."
         binding = "..."
         contract="IMyEvents"
      />
   </service>
</services>

그림 11은 PublishService<T>의 구현을 보여 줍니다.

FireEvent 메서드는 게시 서비스의 이벤트 발생을 간소화하기 위해 구독자에게 전달할 매개 변수를 받아들이지만, 해당 호출자는 구독자에서 호출할 작업의 이름을 제공하지 않습니다. 이러한 이유로 FireEvent는 스택 프레임에 액세스하여 호출하는 메서드의 이름을 추출합니다. 그런 다음 PublishPersistent 도우미 메서드를 사용하여 모든 영구 구독자에 게시하고 PublishTransient 도우미 메서드를 사용하여 모든 임시 구독자에 게시합니다. 두 가지 게시 메서드는 거의 동일한 방식으로 작동합니다. 즉, SubscriptionManager<T>에 액세스하여 각각의 구독자 목록을 검색한 다음 Publish 메서드를 사용하여 이벤트를 발생시킵니다. 다음으로 구독자에 대한 프록시 배열 형식으로 구독자가 반환됩니다. 이 배열은 Publish 메서드에 전달됩니다.

Publish는 이 시점에서 단순히 구독자를 호출할 수도 있습니다. 그러나 필자는 미숙한 구독자가 이벤트를 처리하는 데 많은 시간을 소모하는 경우 이로 인해 다른 구독자가 적시에 이벤트를 받는 데 지장이 없도록 하기 위해 이벤트의 동시 게시를 지원하고자 했습니다. 이벤트 작업이 단방향으로 표시되어 있다고 해서 비동기 호출이 보장되는 것은 아닙니다. 아울러 필자는 이벤트 작업이 단방향으로 표시되어 있지 않은 경우에도 동시 게시를 지원하고자 했습니다.

게시 메서드는 두 가지 익명 메서드를 정의합니다. 첫 번째 익명 메서드는 Invoke 도우미 메서드를 호출하여 제공된 개별 구독자에게 이벤트를 발생시킨 다음, 프록시를 닫도록 지정된 경우 프록시를 닫습니다. Invoke는 특정 구독자 형식에 대해 컴파일되지 않았기 때문에 호출에 대해 리플렉션 및 후기 바인딩을 사용합니다. 또한 Invoke는 게시 쪽에 관심이 없기 때문에 호출에서 발생된 모든 예외도 표시하지 않습니다. 두 번째 익명 메서드는 스레드 풀의 스레드에 의해 실행될 첫 번째 익명 메서드를 대기열에 넣습니다. 끝으로 Publish는 제공된 배열에 속한 모든 구독자에 대해 두 번째 익명 메서드를 호출합니다.

PublishService<T>가 구독자를 동일하게 취급하는 방식에 유의합니다. 구독자가 임시 구독자인지 영구 구독자인지는 거의 문제가 되지 않습니다. 유일한 차이점은 영구 구독자에게 게시한 후에는 프록시를 닫아야 한다는 것입니다. 이러한 일률성은 SubscriptionManager<T>의 GetTransientList 및 GetPersistentList 도우미 메서드에 의해 확보됩니다. 두 메서드 중 GetTransientList 메서드가 더 간단합니다.

public abstract class SubscriptionManager<T> where T : class
{
   internal static T[] GetTransientList(string eventOperation)
   {
      lock(typeof(SubscriptionManager<T>))
      {
         List<T> list = m_TransientStore[eventOperation];
         return list.ToArray();
      }
   }
   ... // 기타 멤버 
}

GetTransientList는 지정한 작업에 대한 모든 구독자를 임시 저장소에서 찾아 배열로 반환합니다. GetPersistentList는 난제에 직면하게 됩니다. 영구 구독자에 대해 준비된 프록시 목록이 없다는 것입니다. 이들에 대해 알려진 것이라고는 주소가 전부입니다. 따라서 GetPersistentList는 그림 12와 같이 영구 구독자 프록시를 인스턴스화해야 합니다.

각 구독자에 대한 프록시를 만들기 위해 GetPersistentList에는 구독자의 주소, 바인딩 및 계약이 필요합니다. 여기에서 계약은 물론 SubscriptionManager<T>에 대한 형식 매개 변수입니다. 주소를 얻기 위해 GetPersistentList는 GetSubscribersToContractEventOperation을 호출하여 데이터베이스를 쿼리하고, 지정한 이벤트를 구독하는 영구 구독자의 모든 주소를 배열로 반환합니다. 이제 GetPersistentList에 필요한 것은 각 구독자가 사용하는 바인딩입니다. 바인딩을 얻기 위해 GetPersistentList는 GetBindingFromAddress 도우미 메서드를 호출하여 주소 스키마에서 사용할 바인딩을 유추합니다. GetBindingFromAddress는 모든 HTTP 주소를 WSHttpBinding으로 취급합니다.

 

영구 구독자 관리

그림 9에서 볼 수 있는 IPersistentSubscriptionService의 메서드를 사용하여 런타임 시 영구 구독을 추가하거나 제거할 수 있지만, 이러한 구독은 영구적 특성을 갖기 때문에 관리 도구를 통해 관리하게 되는 경우가 많습니다. 이를 위해 IPersistentSubscriptionService는 그림 13과 같이 구독자 저장소의 다양한 쿼리에 응답하는 추가 작업을 정의합니다.

이러한 관리 작업에는 구독자의 주소, 구독 계약 및 이벤트를 포함하는 PersistentSubscription이라는 간단한 데이터 구조가 이용됩니다. 필자의 게시-구독 프레임워크에는 그림 14에서 볼 수 있는 Persistent Subscription Manager라는 샘플 영구 구독 관리 도구가 포함되어 있습니다.

그림 14 Persistent Subscription Manager
그림 14 Persistent Subscription Manager (작게 보려면 이미지를 클릭하십시오.)

그림 14 Persistent Subscription Manager
그림 14 Persistent Subscription Manager (크게 보려면 이미지를 클릭하십시오.)

이 도구는 IPersistentSubscriptionService를 사용하여 구독을 추가하거나 제거합니다. 새 구독을 추가하려면 도구에 이벤트 계약 정의의 메타데이터 교환 주소를 제공해야 합니다. 영구 구독자는 다형적이기 때문에 영구 구독자 자체의 메타데이터 교환 주소 또는 게시 서비스의 메타데이터 교환 주소를 사용할 수 있습니다. MEX Address 입력란에 메타데이터 교환 기준 주소를 입력하고 Lookup 단추를 클릭합니다. 도구에서 프로그래밍 방식으로 이벤트 서비스의 메타데이터를 검색하여 Contract 및 Event 콤보 상자를 채우게 됩니다.

구독하려면 영구 구독자의 주소를 입력하고 Subscribe 단추를 클릭합니다. 그러면 Persistent Subscription Manager에서 구독 서비스, 즉 지금까지 예제에서 설명한 MySubscriptionService 서비스를 호출하여 해당 구독을 추가합니다. 구독 서비스의 주소는 Persistent Subscription Manager 구성 파일에 유지됩니다.

페이지 맨 위로페이지 맨 위로

대기 중인 게시자 및 구독자

이벤트 게시 또는 구독에 동기 바인딩을 사용하는 대신 NetMsmqBinding을 사용할 수 있습니다. 대기 중인 게시-구독 이벤트는 느슨하게 연결된 시스템의 이점과 연결이 끊어진 실행의 유연성을 결합합니다. 대기 중인 이벤트를 사용하는 경우 계약의 모든 이벤트는 당연히 단방향 작업으로 표시되어야 합니다. 그림 15와 같이 어느 쪽 끝에서든 독립적으로 대기열을 사용할 수 있습니다.

그림 15 대기 중인 게시-구독
그림 15 대기 중인 게시-구독

대기 중인 게시자 및 연결된 동기 구독자가 있을 수 있습니다. 대기 중인 구독자에 게시하는 연결된 게시자, 또는 대기 중인 게시자와 대기 중인 구독자가 모두 있을 수 있습니다. 그러나 이중 콜백에는 MSMQ 바인딩이 지원되지 않기 때문에 대기 중인 임시 구독은 있을 수 없습니다. 앞에서 살펴본 것처럼 관리 도구를 사용하여 구독자를 관리할 수 있으며 관리 작업은 여전히 동기적이고 연결되어 있습니다.

대기 중인 게시자를 이용하려면 게시 서비스에서 MSMQ 바인딩을 사용하여 대기 중인 끝점을 노출해야 합니다. 대기 중인 게시자에서 이벤트를 발생시킬 때 게시 서비스가 오프라인이 되거나 게시 클라이언트 자체가 연결이 끊어질 수 있습니다. 대기 중인 게시 서비스에 두 이벤트를 게시할 때는 배달 순서 및 이러한 이벤트에 대한 최종 구독자의 처리가 보장되지 않습니다. 이벤트 계약이 한 세션에 대해서만 구성된 경우, 그리고 단일 게시 서비스를 처리하는 경우에 한해 게시의 순서를 추측만 할 수 있습니다.

대기 중인 구독자를 배포하려면 영구 구독 서비스가 대기 중인 끝점을 노출해야 합니다. 이렇게 하면 게시자가 온라인 상태인 경우에도 구독자는 오프라인 상태가 될 수 있습니다. 구독자는 다시 연결될 때 대기 중인 모든 이벤트를 받게 됩니다. 또한 어떠한 이벤트도 손실되지 않으므로 대기 중인 구독자는 게시 서비스 자체의 연결이 끊어진 경우에 적합합니다. 대기 중인 단일 구독자에 여러 이벤트가 발생하는 경우 이벤트의 배달 순서가 보장되지 않습니다. 구독자는 이벤트 계약이 한 세션에 대해서 구성된 경우 게시의 순서를 추측만 할 수 있습니다. 물론 대기 중인 게시자와 대기 중인 구독자 둘 다 있는 경우에는 동시에 오프라인으로 작동할 수 있습니다.

페이지 맨 위로페이지 맨 위로

결론

Windows Communication Foundation은 요청-응답, 단방향 작업 또는 이중 콜백을 사용하여 강력하고 유용한 프로그래밍 모델을 기본적으로 지원합니다. 작업을 이해하고 올바른 호출 모드를 선택하는 것은 최우선적으로 수행해야 하는 가장 중요한 디자인 결정 사항 중 하나이지만, 각 모드를 적용하는 작업은 무척 쉽습니다. 이러한 작업 형식을 서비스 지향 응용 프로그램에 있는 그대로 사용하거나 게시-구독 패턴처럼 높은 수준의 추상화를 독자적으로 구성할 수 있습니다.

페이지 맨 위로페이지 맨 위로

image

ServiceModel 파일참조 필수

메소드 인터페이스정의

Creating Service Contracts

[ServiceContract]

public interface IService1

 [OperationContract]

 string GetData(int value);

 [OperationContract]

주고받을 데이터정의

Creating Data Contracts

[DataContract]

public class CompositeType


 bool boolValue = true;

 string stringValue = "Hello ";


 [DataMember]

 public bool BoolValue

 {

   get { return boolValue; }

   set { boolValue = value; }

 }


 [DataMember]

 public string StringValue

 {

   get { return stringValue; }

   set { stringValue = value; }

 }

}

프로젝트생성

image

 

windows service 프로젝트 생성

 

서비스등록을 위한 Installer 파일생성

image

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.ServiceProcess;

namespace WindowsServiceTest
{
    [RunInstaller(true)]
    public partial class Installer1 : System.Configuration.Install.Installer
    {
        public Installer1()
        {
            InitializeComponent();

            CreateInstall();
        }

        private void CreateInstall()
        {
            ServiceInstaller si = new ServiceInstaller();
            ServiceProcessInstaller spi = new ServiceProcessInstaller();
            si.ServiceName ="WindowsServiceTest";  // 실행파일과 이름같아야함!
            si.DisplayName = "테스트모드";
            this.Installers.Add(si);
            spi.Account = ServiceAccount.LocalSystem;
            spi.Password = null;
            spi.Username = null;
            this.Installers.Add(spi);
        }
    }
}

 image

si.ServiceName ="WindowsServiceTest";  // 실행파일과 이름같아야함!

 

 

 

주의! windows 7 또는 비스타에서 CMD 를 관리자 권한으로 실행해주세요

 

서비스등록 하기

 image

C:\Windows\Microsoft.NET\Framework\v2.0.50727 안에 있는

설치 관리자 도구(Installutil.exe)를 이용해서 설치합니다.

일단 Visual Studio Command 사용하니깐 path가 잡혀있나 봅니다 걍 되네요

 

 image 실행

 

설치완료

image

서비스 등록된 화면

image

 

 

제거하기

image

 

설치 관리자 도구(Installutil.exe)

http://msdn.microsoft.com/ko-kr/library/50614e95(VS.80).aspx

설치 관리자 도구를 사용하면 특정 어셈블리에서 설치 관리자 구성 요소를 실행하는 방법으로 서버 리소스를 설치하고 제거할 수 있습니다. 이 도구는 System.Configuration.Install 네임스페이스의 클래스와 함께 작동합니다.

image

http://msdn.microsoft.com/ko-kr/library/ms730158.aspx

서비스 호스팅

서비스가 활성화되려면, 서비스를 만들고 서비스의 컨텍스트 및 수명을 제어하는 런타임 환경에서 서비스가 호스팅되어야 합니다. WCF(Windows Communication Foundation) 서비스는 관리 코드를 지원하는 Windows 프로세스에서 실행되도록 디자인되었습니다.

WCF는 서비스 기반 응용 프로그램을 만드는 데 사용되는 통합 프로그래밍 모델을 제공합니다. 이 프로그래밍 모델은 일관적이며 서비스가 배포되는 런타임 환경에 독립적입니다. 즉, 서비스 코드는 사실상 서비스 호스팅 옵션에 관계없이 거의 동일합니다.

서비스 호스팅 옵션은 콘솔 응용 프로그램 내에서 실행하는 경우부터 서버 환경에 이르기까지 다양합니다. 서버 환경의 예를 들면 IIS(인터넷 정보 서비스) 또는 WAS(Windows Process Activation Service)에서 관리하는 작업자 프로세스 내에서 실행되는 Windows 서비스가 있습니다. 개발자는 서비스의 배포 요구 사항을 만족하는 호스팅 환경을 선택합니다. 이러한 요구 사항은 응용 프로그램이 배포되는 플랫폼, 메시지를 보내고 받는 데 필요한 전송, 충분한 가용성을 보장하는 데 필요한 프로세스 재활용 유형 및 기타 프로세스 관리 유형, 기타 관리 또는 안정성 요구 사항에 따라 달라집니다. 다음 단원에서는 호스팅 옵션에 대한 정보 및 지침을 제공합니다.

 

호스팅 옵션

관리되는 응용 프로그램에서의 자체 호스팅

WCF 서비스는 관리되는 응용 프로그램에서 호스팅할 수 있습니다. 이 경우 배포하는 데 있어 최소한의 인프라를 필요하므로 가장 유연한 옵션입니다. 관리되는 응용 프로그램 코드 내에 서비스 코드를 포함시킨 다음 서비스를 사용할 수 있도록 ServiceHost의 인스턴스를 만들고 엽니다. 자세한 내용은 방법: 관리되는 응용 프로그램에서 WCF 서비스 호스팅을 참조하십시오.

이 옵션을 사용하면 두 가지 일반적인 호스팅 시나리오가 가능합니다. 하나는 콘솔 응용 프로그램 내에서, 다른 하나는 WPF(Windows Presentation Foundation) 또는 WinForms(Windows Forms)를 기반으로 하는 응용 프로그램과 같은 리치 클라이언트 응용 프로그램 내에서 실행되는 WCF 서비스입니다. 일반적으로 콘솔 응용 프로그램 내에서 WCF 서비스를 호스팅하면 응용 프로그램의 개발 단계에서 유용합니다. 이 경우 쉽게 디버깅을 수행할 수 있으며, 응용 프로그램 내에서 발생하는 상황을 확인하기 위한 추적 정보를 손쉽게 얻을 수 있으며, 새 위치에 서비스를 복사하여 이동하기 용이합니다. 이 호스팅 옵션을 사용하면 리치 클라이언트 응용 프로그램(예: WPF 및 WinForms 응용 프로그램)에서 외부와 쉽게 통신할 수 있습니다. 예를 들면 사용자 인터페이스에 대해 WPF를 사용하고 또한 WCF 서비스를 호스팅하여 다른 클라이언트에서 해당 서비스에 연결하여 정보를 공유할 수 있도록 하는 피어 투 피어 공동 작업 클라이언트가 있습니다.

관리되는 Windows 서비스

이 호스팅 옵션은 WCF 서비스를 호스팅하는 응용 프로그램 도메인(AppDomain)을 관리되는 Windows 서비스(이전의 NT 서비스)로 등록하여 프로세스 수명이 Windows 서비스의 SCM(서비스 제어 관리자)에 의해 제어되도록 하는 옵션입니다. 자체 호스팅 옵션과 같이 이러한 유형의 호스팅 환경에서는 일부 호스팅 코드를 응용 프로그램의 일부로 작성해야 합니다. 서비스를 WCF 서비스 계약 인터페이스에서뿐 아니라 ServiceBase 클래스에서 상속시켜 서비스가 Windows 서비스와 WCF 서비스로 구현됩니다. 그러면 ServiceHost가 만들어지고, 재정의된 OnStart 메서드 내에서 열리고 재정의된 OnStop 메서드 내에서 닫힙니다. Installutil.exe 도구를 사용하여 프로그램을 Windows 서비스로 설치하려면 Installer에서 상속되는 설치 관리자 클래스를 구현해야 합니다. 자세한 내용은 방법: 관리되는 Windows 서비스에서 WCF 서비스 호스팅을 참조하십시오. 관리되는 Windows 서비스 호스팅 옵션을 통해 사용할 수 있는 시나리오는 메시지가 활성화되지 않은 보안 환경의 IIS 외부에서 호스팅되는 장기 실행 WCF 서비스입니다. 서비스 수명은 대신 운영 체제에 의해 제어됩니다. 모든 버전의 Windows에서 이 호스팅 옵션을 사용할 수 있습니다.

IIS(인터넷 정보 서비스)

IIS 호스팅 옵션은 ASP.NET과 통합되어 있으며, 이러한 기술이 제공하는 프로세스 재활용, 유휴 상태이면 종료, 프로세스 상태 모니터링 및 메시지 기반 활성화 같은 기능을 사용합니다. Windows XP 및 Windows Server 2003 운영 체제에서 이 옵션은 높은 가용성 및 확장성을 필요로 하는 웹 서비스 응용 프로그램을 호스팅하는 기본 솔루션입니다. IIS는 또한 고객이 엔터프라이즈 수준의 서버 제품에서 기대하는 통합된 관리 효율성을 제공합니다. 이 호스팅 옵션을 사용하려면 IIS를 적절히 구성해야 하지만 호스팅 코드를 응용 프로그램의 일부로 작성하지 않아도 됩니다. WCF 서비스를 위한 IIS 호스팅을 구성하는 방법에 대한 자세한 내용은 방법: IIS에서의 WCF 서비스 호스팅을 참조하십시오.

IIS에서 호스팅되는 서비스는 HTTP 전송만 사용할 수 있습니다. IIS 5.1에서의 서비스 구현 시 Windows XP의 경우 몇 가지 제한이 있습니다. Windows XP의 IIS 5.1에서 호스팅되는 WCF 서비스에 제공되는 메시지 기반 활성화 기능은 동일한 컴퓨터에서 자체 호스팅되는 다른 WCF 서비스가 통신할 때 포트 80을 사용하지 못하도록 차단합니다. WCF 서비스가 Windows Server 2003의 IIS 6.0에서 호스팅되는 경우에는 다른 응용 프로그램과 동일한 AppDomain/응용 프로그램 풀/작업자 프로세스에서 실행될 수 있습니다. 그러나 WCF와 IIS 6.0 모두 커널 모드 HTTP 스택(HTTP.sys)을 사용하므로 IIS 5.1과 달리 IIS 6.0에서는 포트 80을 동일한 시스템에서 실행되는 자체 호스팅되는 다른 WCF 서비스와 공유할 수 있습니다.

WAS(Windows Process Activation Service)

WAS(Windows Process Activation Service)는 Windows Vista에서도 사용할 수 있는 Windows Server 2008을 위한 새 프로세스 활성화 메커니즘입니다. 이 옵션의 경우 익숙한 IIS 6.0 프로세스 모델(응용 프로그램 풀 및 메시지 기반 프로세스 활성화)과 호스팅 기능(오류로부터 신속한 보호, 상태 모니터링 및 재활용 등)은 유지되지만, 활성화 아키텍처에서 HTTP에 대한 종속성을 제거하였습니다. IIS 7.0은 WAS를 사용하여 HTTP를 통한 메시지 기반 활성화를 수행합니다. 추가 WCF 구성 요소는 또한 WAS에 연결되어 TCP, MSMQ 및 명명된 파이프와 같은 WCF에서 지원하는 다른 프로토콜을 통한 메시지 기반 활성화를 제공합니다. 이렇게 하면 통신 프로토콜을 사용하는 응용 프로그램에서 프로세스 재활용, 오류로부터 신속한 보호 및 일반적인 구성 시스템과 같은 HTTP 기반 응용 프로그램에서만 사용할 수 있었던 IIS 기능을 사용할 수 있습니다.

이 호스팅 옵션을 사용하려면 WAS를 적절히 구성해야 하지만 호스팅 코드를 응용 프로그램의 일부로 작성하지 않아도 됩니다. WAS 호스팅을 구성하는 방법에 대한 자세한 내용은 방법: WAS에서 WCF 서비스 호스팅을 참조하십시오.

호스팅 환경 선택

다음 표에서는 각 호스팅 옵션과 관련된 몇 가지 주요 이점 및 시나리오를 요약하여 설명합니다.

image

응용 프로그램이 배포되는 Windows 버전, 메시지를 보내는 데 필요한 전송, 응용 프로그램에서 필요로 하는 프로세스와 응용 프로그램 도메인 재활용 유형에 따라 선택하는 호스팅 환경이 달라집니다. 다음 표에서는 이러한 요구 사항과 관련된 데이터를 요약하여 설명합니다.

image

신뢰할 수 없는 호스트에서 서비스나 확장을 실행하면 보안이 손상된다는 점에 주의해야 합니다. 또한 가장하여 ServiceHost를 열 때 응용 프로그램에서 사용자의 WindowsIdentity를 캐싱하여 사용자가 로그오프되었는지 확인해야 합니다.

http://msdn.microsoft.com/ko-kr/library/bb546187.aspx

웹서비스를 통한 엔티티모델은 서로 연결이 되있지 않기 때문에 업데이트라든지 삭제,추가는 조금 다른방법으로 구현필요

 

Customers 또는 Orders와 같은 엔터티 개체를 네트워크상의 클라이언트로 serialize하면 해당 엔터티가 원래 데이터 컨텍스트에서 분리됩니다.

 

데이터 컨텍스트에서는 분리된 엔터티 개체에 대해서는 변경 내용이나 다른 개체와의 관계를 더 이상 추적하지 않습니다.

이러한 특징은 클라이언트에서 데이터를 읽기만 하는 경우에는 문제가 되지 않습니다. 또한 클라이언트가 데이터베이스에 새 행을 추가할 수 있게 하는 것도 비교적 간단합니다. 그러나 응용 프로그램에서 클라이언트가 데이터를 업데이트하거나

삭제해야 하는 경우에는 DataContext..::.SubmitChanges를 호출하기 전에 새 데이터 컨텍스트에 엔터티를 연결해야 합니다. 또한 원래 값을 기준으로 낙관적 동시성 검사를 사용하는 경우에는 어떤 방법으로든 데이터베이스에 원래 엔터티와 수정된 엔터티를 모두 제공해야 합니다.

Attach 메서드는 분리된 엔터티를 새 데이터 컨텍스트에 연결하기 위해 제공됩니다.

데이터 삭제

데이터베이스에서 기존 개체를 삭제할 경우 프레젠테이션 계층에서는 중간 계층 인터페이스에서 관련 메서드를 호출하고, 삭제할 개체의 원래 값이 포함된 복사본을 전달합니다.

삭제 작업에는 낙관적 동시성 검사가 사용되기 때문에 삭제할 개체를 새 데이터 컨텍스트에 먼저 연결해야 합니다. 이 예제에서는 Boolean 매개 변수를 false로 설정하여 개체에 타임스탬프(RowVersion)가 없음을 나타냅니다. 그러나 데이터베이스 테이블에서 각 레코드에 대해 타임스탬프를 생성하는 경우 클라이언트측에서는 동시성 검사가 훨씬 간단해집니다. 즉, 원래 개체 또는 수정된 개체 중 하나를 전달하고 Boolean 매개 변수를 true로 설정하기만 하면 됩니다. 그러나 어떤 경우든 중간 계층에서는 일반적으로 ChangeConflictException을 catch해야 합니다. 낙관적 동시성 충돌을 처리하는 방법에 대한 자세한 내용은 낙관적 동시성 개요(LINQ to SQL)를 참조하십시오.

연결된 테이블에 외래 키 제약 조건이 있는 엔터티를 삭제할 경우에는 해당 EntitySet<(Of <(TEntity>)>) 컬렉션에 있는 모든 개체를 먼저 삭제해야 합니다.

// Attach is necessary for deletes.
public void DeleteOrder(Order order)
{
    NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);

    db.Orders.Attach(order, false);
    // This will throw an exception if the order has order details.
    db.Orders.DeleteOnSubmit(order);
    try
    {
        // ConflictMode is an optional parameter.
        db.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException e)
    {
       // Get conflict information, and take actions
       // that are appropriate for your application.
       // See MSDN Article How to: Manage Change Conflicts (LINQ to SQL).
    }
}

 

데이터 업데이트

LINQ to SQL은 낙관적 동시성이 사용되는 다음과 같은 시나리오에서 업데이트를 지원합니다.

  • 타임스탬프 또는 RowVersion 번호를 기반으로 하는 낙관적 동시성

  • 엔터티 속성 하위 집합의 원래 값을 기반으로 하는 낙관적 동시성

  • 원래 엔터티와 수정된 엔터티 모두를 기반으로 하는 낙관적 동시성

Customer 및 Customer와 연결된 Order 개체의 컬렉션과 같이 엔터티와 해당 관계에 대해 업데이트나 삭제 작업을 수행할 수도 있습니다. 클라이언트에서 엔터티 개체의 그래프와 해당 자식(EntitySet) 컬렉션을 수정할 경우 낙관적 동시성 검사에 원래 값이 필요하면 클라이언트에서는 각 엔터티와 EntitySet<(Of <(TEntity>)>) 개체의 원래 값을 제공해야 합니다. 클라이언트에서 한 번의 메서드 호출로 관련된 업데이트, 삭제 및 삽입을 수행하려면 각 엔터티에 대해 수행할 작업 유형을 메서드 안에 지정할 수 있어야 합니다. 그런 다음 SubmitChanges를 호출하기 전에 중간 계층에서 적절한 Attach 메서드를 호출한 후 각 엔터티에 대해 InsertOnSubmit, DeleteAllOnSubmit 또는 InsertOnSubmit()()()(삽입의 경우 Attach가 필요하지 않음)을 호출해야 합니다. 업데이트를 시도하기 전에 원래 값을 가져오기 위한 방법으로 데이터베이스에서 데이터를 검색하지 마십시오.

낙관적 동시성에 대한 자세한 내용은 낙관적 동시성 개요(LINQ to SQL)를 참조하십시오. 낙관적 동시성 변경 충돌을 해결하는 방법에 대한 자세한 내용은 방법: 변경 내용 충돌 관리(LINQ to SQL)를 참조하십시오.

다음 예제에서는 각 시나리오를 보여 줍니다.

타임스탬프를 사용하는 낙관적 동시성

// Assume that "customer" has been sent by client.
// Attach with "true" to say this is a modified entity
// and it can be checked for optimistic concurrency because
//  it has a column that is marked with "RowVersion" attribute
db.Customers.Attach(customer, true)
try
{
    // Optional: Specify a ConflictMode value
    // in call to SubmitChanges.
    db.SubmitChanges();
}
catch(ChangeConflictException e)
{
    // Handle conflict based on options provided
    // See MSDN article How to: Manage Change Conflicts (LINQ to SQL).
}

 

원래 값의 하위 집합을 사용하는 낙관적 동시성
(이 경우 클라이언트는 serialize된 개체 전체 및 수정될 값을 반환합니다.)

public void UpdateProductInventory(Product p, short? unitsInStock, short? unitsOnOrder)
{
    using (NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString))
    {
        // p is the original unmodified product
        // that was obtained from the database.
        // The client kept a copy and returns it now.
        db.Products.Attach(p, false);

        // Now that the original values are in the data context, apply the changes.
        p.UnitsInStock = unitsInStock;
        p.UnitsOnOrder = unitsOnOrder;
        try
        {
             // Optional: Specify a ConflictMode value
             // in call to SubmitChanges.
             db.SubmitChanges();
        }
        catch (ChangeConflictException e)
        {
            // Handle conflict based on provided options.
            // See MSDN article How to: Manage Change Conflicts
            // (LINQ to SQL).
        }
    }
}

 

전체 엔터티를 사용하는 낙관적 동시성

public void UpdateProductInfo(Product newProd, Product originalProd)
{
     using (NorthwindClasses1DataContext db = new
        NorthwindClasses1DataContext(connectionString))
     {
         db.Products.Attach(newProd, originalProd);
         try
         {
               // Optional: Specify a ConflictMode value
               // in call to SubmitChanges.
               db.SubmitChanges();
         }
        catch (ChangeConflictException e)
        {
            // Handle potential change conflict in whatever way
            // is appropriate for your application.
            // For more information, see the MSDN article
            // How to: Manage Change Conflicts (LINQ to SQL)/
        }
    }
}

 

 

필요한 엔터티 멤버

앞에서 설명한 것처럼 Attach 메서드를 호출하기 전에 엔터티 개체의 일부 멤버만 설정해야 합니다. 설정해야 하는 엔터티 멤버는 다음과 같은 조건을 충족해야 합니다.

  • 엔터티 ID의 일부여야 합니다.

  • 수정될 멤버여야 합니다.

  • 타임스탬프이거나 UpdateCheck 특성이 Never 이외의 값으로 설정되어 있어야 합니다.

테이블에 낙관적 동시성 검사를 위한 타임스탬프 또는 버전 번호가 사용되는 경우 Attach를 호출하기 전에 이러한 멤버를 설정해야 합니다. 해당 Column 특성에 IsVersion 속성이 true로 설정된 멤버는 낙관적 동시성 검사에 사용됩니다. 데이터베이스에 있는 버전 번호 또는 타임스탬프 값이 동일해야 요청된 업데이트가 제출됩니다.

UpdateCheck가 Never로 설정되지 않은 멤버도 낙관적 동시성 검사에 사용됩니다. 다른 값이 지정되지 않은 경우 기본값은 Always입니다.

이러한 필수 멤버가 하나라도 없으면 SubmitChanges를 호출했을 때 ChangeConflictException("행이 없거나 변경되었습니다.")이 throw됩니다.

상태

엔터티 개체를 DataContext 인스턴스에 연결하면 개체가 PossiblyModified 상태가 됩니다. 다음과 같은 세 가지 방법을 사용하여 연결된 개체를 강제로 Modified 상태로 만들 수 있습니다.

  1. 개체를 수정되지 않은 상태로 연결한 다음 필드를 직접 수정합니다.

  2. 개체의 현재 인스턴스와 원래 인스턴스를 사용하는 Attach 오버로드와 연결합니다. 이렇게 하면 이전 값과 새 값이 변경 추적기에 제공되므로 변경된 필드를 자동으로 인식할 수 있습니다.

  3. true로 설정된 두 번째 부울 매개 변수를 사용하는 Attach 오버로드와 연결합니다. 이렇게 하면 원래 값을 제공하지 않고도 수정된 개체를 변경 추적기에서 인식할 수 있습니다. 이 방법을 사용하려면 개체에 버전/타임스탬프 필드가 있어야 합니다.

참고 URL http://msdn.microsoft.com/ko-kr/library/ms752236.aspx

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel;
using System.Runtime.Serialization;


namespace TESTSliverlight.Web
{
    [DataContract]
    public class WCFBook
    {
        public WCFBook(string BookTitle) {

            this.BookTitle = BookTitle;
        }
       [DataMember]
        public string BookTitle { get; set; }

    }

}

 

데이터 계약을 구현하는 방법을 보여 줍니다. 데이터 계약을 사용하면 서비스와 구조적 데이터를 주고 받을 수 있습니다.

// Define a service contract.
[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
{
    [OperationContract]
    ComplexNumber Add(ComplexNumber n1, ComplexNumber n2);
    [OperationContract]
    ComplexNumber Subtract(ComplexNumber n1, ComplexNumber n2);
    [OperationContract]
    ComplexNumber Multiply(ComplexNumber n1, ComplexNumber n2);
    [OperationContract]
    ComplexNumber Divide(ComplexNumber n1, ComplexNumber n2);
}

서비스 간에 연결을 통해 전달할 수 있는 클래스의 필드를 나타내기 위해서는

[DataContract(Namespace="http://Microsoft.ServiceModel.Samples")]
public class ComplexNumber
{
   [DataMember]
    public double Real = 0.0D;
    [DataMember]
    public double Imaginary = 0.0D;

    public ComplexNumber(double real, double imaginary)
    {
        this.Real = real;
        this.Imaginary = imaginary;
    }
}

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="MarinerERP.WebService.DataService.InsaServiceBehavior">
        <serviceMetadata httpGetEnabled="true"  /> true로 지정한다
        <serviceDebug includeExceptionDetailInFaults="true"   />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <services>
    <service behaviorConfiguration="MarinerERP.WebService.DataService.InsaServiceBehavior"
      name="MarinerERP.WebService.DataService.InsaService">
      <endpoint address="" binding="netTcpBinding"  contract="IMarinerChannel.IInsaModule" />
      <endpoint address="mexTcp" binding="mexTcpBinding"   contract="IMetadataExchange" />
    </service>
  </services>
</system.serviceModel>

http://msdn.microsoft.com/ko-kr/library/ms788993.aspx

httpsHelpPageEnabled

WCF가 httpsHelpPageUrl 특성으로 지정된 주소에 HTML 도움말 페이지를 게시할지 여부를 제어하는 부울 값입니다. 기본값은 true입니다.

HTML 브라우저에 HTML 도움말 페이지가 게시되지 않게 하려면 이 속성을 false로 설정합니다.

httpsHelpPageUrl 특성으로 지정된 위치에 HTML 도움말 페이지가 게시되게 하려면 이 특성을 true로 설정해야 합니다 또한 다음 조건 중 하나도 충족해야 합니다.

  • httpsHelpPageUrl 특성은 HTTPS 프로토콜 체계를 지원하는 절대 주소여야 합니다.
  • HTTPS 프로토콜 체계를 지원하는 서비스의 기본 주소가 있어야 합니다.

HTTPS 프로토콜 체계를 지원하지 않는 절대 주소가 httpsHelpPageUrl 특성에 지정되면 예외가 throw되지만, 앞의 조건을 모두 충족하지 않는 시나리오에서는 예외가 발생하지 않고 HTML 도움말 페이지도 생성되지 않습니다.

http://msdn.microsoft.com/en-us/magazine/cc163647.aspx

Code download available at: WCF.exe (142 KB)
Browse the Code Online

  Contents

WCF Programming Model
Service Contracts and Dispatch Behavior
Data Contracts
Message and Service Contracts
Implementing Service Contracts
Hosting the Service and Defining Endpoints
Choosing and Customizing Bindings
Opening the Host
Configuring Service Endpoints
Using Activation Services
Programming Clients
Configuring Client Endpoints
Generating Client Proxies
Logging Messages
Conclusion

Windows® Communication Foundation (WCF), formerly code-named "Indigo," is about to radically change the face of distributed programming for developers using the Microsoft® .NET Framework. WCF unifies the existing suite of .NET distributed technologies into a single programming model that improves the overall developer experience through a consistent architecture, new levels of functionality and interoperability, and all the extensibility points you could want. This article introduces you to WCF programming and shows you how to get started.

As its name suggests, WCF provides the .NET Framework with a basis for writing code to communicate across components, applications, and systems. WCF was designed according to the tenets of service orientation. A service is a piece of code you interact with through messages. Services are passive. They wait for incoming messages before doing any work. Clients are the initiators. Clients send messages to services to request work.

Figure 1 Services and Endpoints

Services expose one or more endpoints where messages can be sent. Each endpoint consists of an address, a binding, and a contract (see Figure 1). The address specifies where to send messages. The binding describes how to send messages. And the contract describes what the messages contain. Clients need to know this information before they can access a service.

Services can package up endpoint descriptions to share with clients, typically by using Web Services Description Language (WSDL). Then clients can use the provided service description to generate code within their environment capable of sending and receiving the proper messages (see Figure 2).

Figure 2 Sharing Endpoint Descriptions

Windows Communication Foundation provides a new library of classes found in the System.ServiceModel namespace that bring these service-oriented concepts to life. This is what's typically referred to as the WCF programming model.

WCF Programming Model

With WCF, you're either writing services that expose endpoints or you're writing clients that interact with endpoints. Hence, endpoints are central to the WCF programming model and infrastructure. WCF models endpoints with the .NET classes and interfaces shown in Figure 3. This mapping is true whether you're writing WCF clients or services.

  Figure 3 WCF Classes and Interfaces

Element
Class or Interface

Endpoint
System.ServiceModel.ServiceEndpoint

Address
System.Uri

Binding
System.ServiceModel.Binding

Contract
Interfaces annotated with System.ServiceModel attributes

When building a WCF service, you typically start by defining a .NET interface definition to serve as the service contract. Then you implement the service contract in a .NET class, known as the service type, and configure its behavior. Next, you define the endpoints the service will expose, specifying the address, binding, and contract for each one. Finally, you host the service type in an application using the WCF hosting infrastructure. Once the service type is hosted, clients can retrieve its endpoint descriptions and begin integrating with it.

When building a WCF client, you first need the description of the target endpoint you want to access. The endpoint description can be used to dynamically create a typed proxy. WCF provides a tool named SvcUtil.exe for automating this process. Then you can write code against the typed proxy to access the service by sending the appropriate messages to the target endpoint.

Service Contracts and Dispatch Behavior

You model service contracts in .NET using traditional C# interface definitions. You can use any .NET interface as a starting point, such as the one shown here:

Copy Code

namespace ServiceLibrary
{
    public interface IEchoService
    {
        string Echo(string msg);
    }
}

To make this a WCF service contract, you must annotate the interface itself with [ServiceContract] and each operation you want to expose with [OperationContract]:

Copy Code

using System.ServiceModel;

namespace ServiceLibrary
{
    [ServiceContract(Namespace="http://example.org/echo/")]
    public interface IEchoService
    {
        [OperationContract]
        string Echo(string msg);
    }
}

These attributes influence the mapping between the worlds of .NET and SOAP. WCF uses the information found in the service contract to perform dispatching and serialization. Dispatching is the process of deciding which method to call for an incoming SOAP message. Serialization is the process of mapping between the data found in a SOAP message and the corresponding .NET objects used in the method invocation. This mapping is controlled by an operation's data contract.

WCF dispatches based on the message action. Each method in a service contract is automatically assigned an action value based on the service namespace and method name. For example, the default action for the Echo method just shown is http://example.org/echo/Echo. You can customize the action value for each method using [OperationContract]. A value of * can be used for any action when a specific match doesn't exist.

In the following example, WCF will dispatch messages with an action of urn:echo:string to the Echo method. Messages with any other action are dispatched to the EchoMessage method:

Copy Code

[ServiceContract(Namespace="http://example.org/echo/")]
public interface IEchoService
{
    [OperationContract(Action="urn:echo:string")]
    string Echo(string msg);

    [OperationContract(Action="*")]
    Message EchoMessage(Message msg);
}

Data Contracts

Once the target method has been determined based on the action, WCF relies on the method's data contract to perform serialization. The data contract is defined by the types used in the method signature. In the previous example, EchoMessage is generic and could be used to process a variety of incoming SOAP messages. Therefore I've used Message to model the input and output. Message is a special type used to represent all messages flowing through WCF. When using Message, WCF does not perform type-based serialization. Instead, it just gives you direct access to what's found in the SOAP message.

When Message is not used, WCF performs serialization to map between the data found in the SOAP message and the corresponding .NET objects needed to invoke the method. For example, in the case of Echo, WCF maps the SOAP payload to a .NET string. WCF provides an implicit mapping for all .NET primitive types (string, int, double, and so on).

The way WCF serializes .NET classes depends on the serialization engine in use. The default serialization engine is known as DataContract, a simplified version of XmlSerializer, the default serialization engine used in ASMX today. DataContract defines attributes for annotating class definitions to influence the serialization process. Here's an example of it in use:

Copy Code

[DataContract(Namespace="http://example.org/person")]
public class Person
{
    [DataMember(Name="first", Order=0)]
    public string First;
    [DataMember(Name="last", Order=1)]
    public string Last;
    [DataMember(IsRequired=false, Order=2)]
    private string id;
    ...
}

With DataContract, only fields marked with DataMember will be serialized. And you can serialize private fields. This is much different from the way XmlSerializer works. Now you can use Person in an operation contract:

Copy Code

[ServiceContract(Namespace="http://example.org/echo/")]
public interface IEchoService
{
    ... // previous methods omitted

    [OperationContract]
    Person EchoPerson(Person p);
}

When EchoPerson is invoked, WCF will serialize Person instances according to the DataContract attributes specified on the Person class. DataContract produces a very simple XML structure—a sequence of elements. You can control the name of each element, the order, and whether a particular element is required, but that's about it.

If you need to do anything more sophisticated, WCF lets you fall back to using XmlSerializer. You indicate your desire to use XmlSerializer by annotating the interface with [XmlSerializerFormat]:

Copy Code

[ServiceContract]
[XmlSerializerFormat]
public interface IEchoService { ... }

This tells WCF to use XmlSerializer for all types in the contract, according to the XmlSerializer defaults and the various System.Xml.Serialization customization attributes. This makes it much easier to move existing ASMX services forward to WCF.

WCF also supports serializing types marked with [Serializable], which allows .NET remoting types to work with WCF without change. WCF provides an implicit mapping for [Serializable] types where all public/private fields are automatically serialized.

Message and Service Contracts

All of the examples up to this point have relied on WCF to automatically map the method signature to the SOAP message. The parameter list and return type are always mapped to the SOAP body for the request and response, respectively. If you need to support headers, you can write another class that models the structure of the entire SOAP envelope for the particular operation, specifying which fields map to headers versus the body. You define this mapping with the [MessageContract] attributes:

Copy Code

[MessageContract]
public class EchoPersonMessage
{
    [MessageBody]
    public Person Person;
    [MessageHeader]
    public Authorization Authorization;
}

In this example, the Person field is mapped to the SOAP body while the Authorization field is mapped to a SOAP header. Now you can use EchoPersonMessage in an operation contract:

Copy Code

[ServiceContract]
public interface IEchoService
{
    ... // previous methods omitted

    [OperationContract]
    void EchoPerson(EchoPersonMessage msg);
}

Using MessageContract is a more advanced technique that is only necessary when you need direct control over the SOAP contract.

Implementing Service Contracts

Now you can implement the service contract by simply implementing the .NET interface in a class:

Copy Code

using System.ServiceModel;
namespace ServiceLibrary
{
    public class EchoService : IEchoService
    {
        public string Echo(string msg)
        {
            return msg;
        }
        ... // remaining methods omitted
    }
}

By doing this, EchoService is guaranteed to support the service contract defined by IEchoService. When using an interface to define the service contract, you don't need any contract-related attributes on the class definition. However, you may want to use [ServiceBehavior] to influence its local behavior:

Copy Code

using System.ServiceModel;

namespace ServiceLibrary
{
    ... // interface definition omitted

    [ServiceBehavior(
       InstanceContextMode=InstanceContextMode.Single,
       ConcurrencyMode=ConcurrencyMode.Multiple)]
    public class EchoService : IEchoService
    {
       ...

This particular example tells WCF to manage a singleton instance of the service type and to allow multithreaded access to the instance. There is also an [OperationBehavior] attribute for controlling operation-level behavior.

Behaviors influence processing within the host, but have no impact on the service contract whatsoever. Behaviors are one of the primary WCF extensibility points. Any class that implements IServiceBehavior can be applied to a service through the use of a custom attribute or configuration element.

Hosting the Service and Defining Endpoints

To use EchoService, you need to host it in a .NET-based application. The ServiceHost class gives you direct control over the WCF hosting infrastructure. You instantiate ServiceHost based on a particular service type. The following code shows how to do this in a console application:

Copy Code

using System;
using System.ServiceModel;
using ServiceLibrary;

class Program
{
    static void Main(string[] args)
    {
        using (ServiceHost host = new ServiceHost(
            typeof(EchoService), new Uri("http://localhost:8080/echo")))
        {
            ...

In addition to specifying the service type, you also specify the base addresses for the different transports you plan to use. In this example, I've specified a base address of http://localhost:8080/echo. This will be used as the base for any relative HTTP addresses I might specify when adding endpoints. The base HTTP address is also used as the default for retrieving the service description.

You then add the service endpoints. Again, a service may have one or more endpoints and each endpoint consists of an address, a binding, and a contract. You provide this information to your ServiceHost by calling AddServiceEndpoint. This is where you specify the service contract you defined earlier (IEchoService). For the binding, you typically choose from one of the many predefined bindings that ship with WCF and an appropriate address given the binding's transport. Here's an example:

Copy Code

host.AddServiceEndpoint(typeof(IEchoService), 
    new BasicHttpBinding(), "svc");
host.AddServiceEndpoint(typeof(IEchoService), 
    new NetTcpBinding(), "net.tcp://localhost:8081/echo/svc");

In this particular example, I've specified IEchoService as the contract for both endpoints, but with each endpoint using a different binding and address. The first endpoint uses the BasicHttpBinding and a relative HTTP address of svc (which would make its absolute address http://localhost:8080/echo/svc). The second endpoint uses the NetTcpBinding and an address of net.tcp://localhost:8081/echo/svc. Therefore, this service allows consumers to communicate with it over either HTTP or TCP using different WS-* protocols, as defined by each binding.

Choosing and Customizing Bindings

The WCF infrastructure relies heavily on endpoint definitions to control how messages are processed. In fact, WCF uses the endpoint definitions to dynamically build the appropriate message processing runtime within hosts and clients. The binding has the most significant influence on this process.

The binding controls three aspects of message communication: the suite of WS-* protocols, including WS-Security, WS-ReliableMessaging, and so on; the message encoding, such as XML 1.0, Message Transmission Optimization Mechanism (MTOM), and binary; and the transport protocol, including HTTP, TCP, and Microsoft Message Queuing (MSMQ). A given binding specifies one message encoding and one transport, but it can specify numerous WS-* protocols. Given that, there are an overwhelming number of permutations that could be used. To simplify things, WCF provides a set of predefined bindings that fit the most common use cases. Figure 4 lists some of these and describes what they embody.

  Figure 4 Predefined WCF Bindings

Class Name
Element Name
Transport
Encoding
WS-* Protocols

BasicHttpBinding
basicHttpBinding
HTTP
XML 1.0
WS-I Basic Profile 1.1

WSHttpBinding
wsHttpBinding
HTTP
XML 1.0
Message security, reliable sessions, and transactions

WSDualHttpBinding
wsDualHttpBinding
HTTP
XML 1.0
Message security, reliable sessions, and transactions

NetTcpBinding
netTcpBinding
TCP
Binary
Transport security, reliable sessions, and transactions

NetNamedPipeBinding
netNamedPipeBinding
Named Pipes
Binary
Transport security, reliable sessions, and transactions

NetMsmqBinding
netMsmqBinding
MSMQ
Binary
Transport security and queue transactions

Developers who care primarily about interoperability and don't need WS-* functionality will typically use BasicHttpBinding. Developers who care about interoperability, but also need message-based security, reliable messaging, and transactions will typically choose WSHttpBinding. Developers using WCF on both sides (client and service) can choose from NetTcpBinding and NetNamedPipeBinding, which provide significant optimizations. Those building asynchronous systems can choose NetMsmqBinding. There are also a few bindings that aren't listed here, such as MsmqIntegrationBinding for integrating with existing MSMQ applications, NetPeerTcpBinding for building peer-to-peer services, and WSFederationBinding for federated security. A service can support any combination of these bindings.

Once you choose a binding and instantiate it, you can customize some of its characteristics through the properties exposed on the binding class. The following example shows how to enable transport-based security on the BasicHttpBinding:

Copy Code

BasicHttpBinding b = new BasicHttpBinding();
b.Security.Mode = BasicHttpSecurityMode.Transport;
b.Security.Transport.ClientCredentialType = 
    HttpClientCredentialType.Basic;
host.AddServiceEndpoint(typeof(IEchoService), b, "svc");

If the predefined bindings and their customizations still don't fit your needs, you can also write a custom binding by implementing a class that derives from Binding.

Opening the Host

Now that you've configured the host with endpoint information, WCF can build the runtime needed to support your configuration. This occurs when you call Open on a particular ServiceHost:

Copy Code

host.Open();

Once the call to Open returns, the WCF runtime is built and ready to receive messages at the addresses specified by each endpoint. During this process, WCF generates an endpoint listener for each supplied endpoint and the code needed to support the WS-* protocols specified by the binding. (An endpoint listener is the piece of code that actually listens for incoming messages using the specified transport and address.)

You can retrieve information about the service at run time through the object model exposed by ServiceHost. This allows you to retrieve anything you might want to know about the initialized service, such as what endpoints it exposes and what endpoint listeners are currently active.

Figure 5 shows a complete example of the console application that hosts EchoService. Figure 6 shows the output. Notice there are two additional endpoint listener addresses that I didn't specify in a ServiceEndpoint. WCF automatically provided these using the base HTTP address for retrieving the service description.

  Figure 5 Console App Hosting EchoService

Copy Code

class Program
{
    static void Main(string[] args)
    {
        using (ServiceHost host = new ServiceHost(
            typeof(EchoService), new Uri("http://localhost:8080/echo")))
        {
            // define the service endpoints
            host.AddServiceEndpoint(typeof(IEchoService), 
                new BasicHttpBinding(), "svc");
            host.AddServiceEndpoint(typeof(IEchoService), 
                new NetTcpBinding(), "net.tcp://localhost:8081/echo/svc");
            host.Open();

            Console.WriteLine(
                "{0} is open and has the following endpoints:\n",
                host.Description.ServiceType);

            int i=1;
            foreach (ServiceEndpoint end in host.Description.Endpoints)
            {
                Console.WriteLine("Endpoint #{0}", i++);
                Console.WriteLine("Address: {0}", 
                    end.Address.Uri.AbsoluteUri);
                Console.WriteLine("Binding: {0}", 
                    end.Binding.Name);
                Console.WriteLine("Contract: {0}\n", 
                    end.Contract.Name);
            }

            Console.WriteLine(
                "The following EndpointListeners are active:\n");
            foreach (EndpointListener l in host.EndpointListeners)
                Console.WriteLine(l.Listener.Uri.AbsoluteUri);

            // keep the process alive
            Console.ReadLine();
        }
    }
}

Figure 6 Output of Console App

If you browse to http://localhost:8080/echo, you'll see the default WCF help page (see Figure 7) and if you browse to http://localhost:8080/echo?wsdl, you'll see the generated WSDL definition. The other endpoint listener (http://localhost:8080/echo/mex) knows how to speak WS-MetadataExchange. It's important to note that this sample is not using IIS in any way. WCF provides built-in integration with httpsys, which allows any application to automatically become an HTTP listener.

Figure 7 EchoService Help Page

Configuring Service Endpoints

Hardcoding endpoint information into the host application is not ideal since endpoint details may change over time. It's not hard to imagine how you could store the endpoint information in a configuration file or database and read it while initializing ServiceHost. Since this is a common need, WCF automatically provides this functionality through the predefined <system.serviceModel> configuration section.

The following configuration file defines the same two endpoints that were specified by calling AddServiceEndpoint:

Copy Code

<configuration>
  <system.serviceModel>
    <services>
      <service type="ServiceLibrary.EchoService">
        <endpoint address="svc" binding="basicHttpBinding" 
                  contract="ServiceLibrary.IEchoService"/>
        <endpoint address="net.tcp://localhost:8081/echo/svc"
                  binding="netTcpBinding" 
                  contract="ServiceLibrary.IEchoService"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>

If you add this configuration file to the host application shown in Figure 5 and comment out the calls to AddServiceEndpoint, you'll see the same results in the output. This increases deployment flexibility since the communication details are completely factored out of the compiled code. You can also configure bindings within <system.serviceModel>. Figure 8 shows how to configure basicHttpBinding to use transport security like I had done in code.

  Figure 8 Configuring Transport Security

Copy Code

<configuration>
  <system.serviceModel>
    <services>
      <service type="ServiceLibrary.EchoService">
        <endpoint
          address="https://localhost:8082/echo/svc"
          binding="basicHttpBinding"
          bindingConfiguration="MyBindingConfiguration" 
          contract="ServiceLibrary.IEchoService"/>
        ...
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="MyBindingConfiguration">
          <security mode="Transport">
            <transport clientCredentialType="Basic" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

You can even define new custom bindings from scratch using the <customBinding> element, which would be equivalent to deriving a new class from Binding. When it comes to configuring endpoints, bindings, and even behaviors, anything you can do in code, you can also do through configuration.

Figure 9 Using the GUI to Define an Endpoint

Although IntelliSense® for the configuration file works well in Visual Studio® 2005, some developers still prefer to avoid XML altogether. The WinFX® SDK includes a tool called SvcConfigEditor, which provides a graphical interface for working with the various <system.serviceModel> settings. Figure 9 shows how to configure endpoints using this tool.

Using Activation Services

Although I've been experimenting with console applications, ServiceHost makes it easy to host WCF services in a variety of applications, including Windows Forms applications and managed Windows Services. In all of these application scenarios, you're responsible for managing the process hosting WCF. This is known as self-hosting in WCF terms.

In addition to self-hosting, WCF also makes it possible to activate services by using IIS along with traditional Web hosting techniques. You can accomplish this by using a .svc file (similar to .asmx) that specifies the service type to host:

Copy Code

<%@Service class="ServiceLibrary.EchoService" %>

You place this file in a virtual directory and deploy your service type to its \bin directory or the Global Assembly Cache (GAC). When using this technique you specify the service endpoint in the web.config, but you don't need to specify the address since it's implied by the location of the .svc file.

Copy Code

<configuration>
  <system.serviceModel>
    <services>
      <service type="ServiceLibrary.EchoService">
        <endpoint address="" binding="basicHttpBinding" 
          contract="ServiceLibrary.IEchoService"/>
      </service>
      ...

Now if you browse to the .svc file, you'll notice the help page is displayed, showing that a ServiceHost was automatically created within an ASP.NET application domain. This is accomplished by an HTTP module that filters incoming .svc requests and automatically builds and opens the appropriate ServiceHost when needed.

If you install the WinFX extensions for Visual Studio 2005, you'll find a new Web Site project template called Indigo Service (this name will change). When you create a new Web site based on this template, it automatically gives you the .svc file along with the corresponding implementation class (found in App_Code). It also comes with a default endpoint configuration in web.config.

On IIS versions 5.1 and 6.0 the WCF activation model is tied to the ASP.NET pipeline, and therefore to HTTP. IIS 7.0, which is planned for release with Windows Vista™, introduces a transport-neutral activation mechanism known as Windows Activation Services (WAS). With WAS, you'll be able to leverage .svc-like activation over any transport.

Programming Clients

Programming WCF clients involves sending messages to a service endpoint according to its address, binding, and contract. To accomplish this, you first create a ServiceEndpoint representing the target endpoint. The following example shows how to create a ServiceEndpoint, based on the service's HTTP endpoint, in a client console application:

Copy Code

using System;
using System.ServiceModel;
using ServiceLibrary;

class Program
{
    static void Main(string[] args)
    {
        ServiceEndpoint httpEndpoint = new ServiceEndpoint(
            ContractDescription.GetContract(typeof(IEchoService)),
            new BasicHttpBinding(), new EndpointAddress(
                "http://localhost:8080/echo/svc");

This code assumes that the client has access to the same IEchoService interface definition that the service used and that the client knows what binding and address to use. Then you can create a ChannelFactory based on the ServiceEndpoint:

Copy Code

ChannelFactory<IEchoService> factory =
    new ChannelFactory<IEchoService>(httpEndpoint);

ChannelFactory creates typed proxies. In this case, it creates proxies of type IEchoService, which you can use to invoke the operations defined by the contract:

Copy Code

IEchoService svc = factory.CreateChannel();
Console.WriteLine(svc.Echo("Hello, world"));

If the service requires a customized binding, you will need to create the binding and customize it before creating ServiceEndpoint. If you want to access the TCP endpoint, simply create another ServiceEndpoint specifying the TCP endpoint details and supply it to ChannelFactory instead of the HTTP endpoint. Everything else would work the same.

The proxy shields you from the different addresses and bindings in use, allowing you to write application code against the service contract. Figure 10 shows the complete client console application.

  Figure 10 EchoService Client App

Copy Code

using System;    
using System.ServiceModel;
using ServiceLibrary;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            // define service endpoints on client
            ServiceEndpoint httpEndpoint = new ServiceEndpoint(
                ContractDescription.GetContract(
                   typeof(IEchoService)),new BasicHttpBinding(),
                new EndpointAddress("http://localhost:8080/echo/svc"));

            ServiceEndpoint tcpEndpoint= new ServiceEndpoint(
                ContractDescription.GetContract(
                   typeof(IEchoService)),
                new NetTcpBinding(), new EndpointAddress(
                   "net.tcp://localhost:8081/echo/svc"));

            IEchoService svc = null;

            // create channel factory based on HTTP endpoint
            using (ChannelFactory<IEchoService> httpFactory = 
                new ChannelFactory<IEchoService>(httpEndpoint))
            {
                // create channel proxy for endpoint
                svc = httpFactory.CreateChannel();
                // invoke service operation
                Console.WriteLine("Invoking HTTP endpoint: {0}",
                    svc.Echo("Hello, world"));
            }

            // create channel factory based on TCP endpoint
            using (ChannelFactory<IEchoService> tcpFactory =
                new ChannelFactory<IEchoService>(tcpEndpoint))
            {
                // create channel proxy for endpoint
                svc = tcpFactory.CreateChannel();
                // invoke service operation
                Console.WriteLine("Invoking TCP endpoint: {0}",
                    svc.Echo("Hello, world"));
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
}

Configuring Client Endpoints

Hardcoding endpoint information into client code is also less than ideal. So WCF provides a similar configuration mechanism for specifying client endpoints within <system.serviceModel>. The following application configuration file specifies the same client endpoint details used in the code:

Copy Code

<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="httpEndpoint" 
        address="http://localhost:8080/echo/svc"
        binding="basicHttpBinding"
        contract="ServiceLibrary.IEchoService"/>
      <endpoint name="tcpEndpoint" 
        address="net.tcp://localhost:8081/echo/svc"
        binding="netTcpBinding"
        contract="ServiceLibrary.IEchoService"/>
    </client>
  </system.serviceModel>
</configuration>

Notice that I've given each client endpoint a name. You can use this name in your code when creating a ChannelFactory, as illustrated in the following code:

Copy Code

using (ChannelFactory<IEchoService> httpFactory = 
    new ChannelFactory<IEchoService>("httpEndpoint"))
{
    svc = httpFactory.CreateChannel();
    Console.WriteLine("Invoking HTTP endpoint: {0}",
        svc.Echo("Hello, world"));
}

Now when you want to create a ChannelFactory for the TCP endpoint, you can simply change the configuration name to tcpEndpoint. You can also configure bindings in the client configuration file just like I did in the service configuration file.

If you add this configuration file to the client console application shown in Figure 10, comment out the code to create the ServiceEndpoint objects, and specify the endpoint names when constructing each ChannelFactory, the result will be the same.

Generating Client Proxies

The preceding examples assumed the client had access to the same interface definition used to implement the service. This is common when .NET is used to implement both sides (in traditional .NET remoting scenarios), but that's not always the case, and it's never the case when the service wasn't implemented with the .NET Framework. The preceding examples also assumed the client knew the precise endpoint details in advance, which isn't always common either.

When clients don't have access to such information, they can discover it using the WS-MetadataExchange specification. Services share endpoint descriptions with clients through WSDL definitions. Most service platforms provide tools for generating client-side code from WSDL definitions. The WinFX SDK provides SvcUtil.exe for this purpose.

When executing SvcUtil.exe, you specify the URL of the WSDL definition and it will generate the necessary .NET code and configuration elements that define the endpoints and bindings used by the service endpoints. Figure 11 shows the output of running SvcUtil.exe against http://localhost:8080/echo. Notice that it generates two files: EchoService.cs and output.config.

Figure 11 SvcUtil.exe Output

EchoService.cs contains a .NET interface definition equivalent to the one the service implemented. Output.config contains the client endpoint information needed to access the service (similar to what I just wrote manually). You'll need to merge the contents of output.config with your application configuration file manually. With this in place, you can write client code using the same techniques shown in the previous examples, only using the generated interface and configuration file.

In general, the WCF client programming model is explicit about the service boundary. You create a typed channel against a specific service endpoint and then send messages through it. Although this helps emphasize the tenets of service orientation, it can be tedious to work at this level all the time. To help you, SvcUtil.exe also generates a proxy class that completely hides the ChannelFactory and ServiceEndpoint creation details. If you open EchoService.cs, you'll find the following class definition:

Copy Code

public partial class EchoServiceProxy : 
   System.ServiceModel.ClientBase<IEchoService>, IEchoService
{
    public EchoServiceProxy()
    {
    }    
    public EchoServiceProxy(string endpointConfigurationName) : 
            base(endpointConfigurationName)
    {
    }
...

This proxy class can simplify your client application code. All you have to do is instantiate the proxy, specify the name of the endpoint configuration, and make method calls. Here's an example:

Copy Code

using (EchoServiceProxy proxy = new EchoServiceProxy("IEchoService"))
{
    // invoke service operation
    Console.WriteLine("Invoking HTTP endpoint: {0}",
        proxy.Echo("Hello, world"));
}
using (EchoServiceProxy proxy = new EchoServiceProxy("IEchoService1"))
{
    // invoke service operation
    Console.WriteLine("Invoking TCP endpoint: {0}",
        proxy.Echo("Hello, world"));
}

The generated configuration file (output.config) defines the IEchoService and IEchoService1 endpoint configurations.

Logging Messages

As you begin working with the WCF programming model, you may find it useful to trace the SOAP messages traveling between clients and services. Windows Communication Foundation provides built-in support for message logging, which can be turned on through your application configuration file.

Figure 12 SvcTraceViewer.exe

SvcConfigEditor provides a Diagnostics tab where you can enable the messaging logging features. Once you enable messaging logging and rerun the app, you'll see the trace file appear in the specified location. The WinFX SDK also provides SvcTraceViewer.exe for viewing the information in a trace file (see Figure 12).

Conclusion

WCF is a new step in distributed programming for developers using the .NET Framework. If you currently build systems using any of today's .NET distributed technologies, it's time to start paying attention to WCF and the future it holds. It's only a matter of time before all .NET-targeted code related to communications will be written using WCF.

Note that all of the code samples shown in this article are based on Visual Studio 2005 and the WCF November 2005 CTP.

+ Recent posts