전송: UDP 샘플에 의한 사용자 지정 트랜잭션
TransactionMessagePropertyUDPTransport 샘플은 WCF(Windows Communication Foundation) 전송 확장성의 전송: UDP 샘플을 기반으로 합니다. 이 샘플은 사용자 지정 트랜잭션 흐름을 지원하도록 UDP 전송 샘플을 확장하고 TransactionMessageProperty 속성의 사용 방법을 보여 줍니다.
UDP 전송 샘플의 코드 변경
트랜잭션 흐름을 보여 주기 위해 이 샘플에서는 ICalculatorContract
의 트랜잭션 범위가 필요하도록 CalculatorService.Add()
의 서비스 계약을 변경합니다. 또한 System.Guid
작업의 계약에 Add
매개 변수를 추가합니다. 이 매개 변수는 클라이언트 트랜잭션의 식별자를 서비스에 전달하는 데 사용됩니다.
class CalculatorService : IDatagramContract, ICalculatorContract
{
[OperationBehavior(TransactionScopeRequired=true)]
public int Add(int x, int y, Guid clientTransactionId)
{
if(Transaction.Current.TransactionInformation.DistributedIdentifier == clientTransactionId)
{
Console.WriteLine("The client transaction has flowed to the service");
}
else
{
Console.WriteLine("The client transaction has NOT flowed to the service");
}
Console.WriteLine(" adding {0} + {1}", x, y);
return (x + y);
}
[...]
}
전송: UDP 샘플에서는 클라이언트와 서비스 간에 메시지를 전달하는 데 UDP 패킷을 사용합니다. 전송: 사용자 지정 전송 샘플에서도 동일한 메커니즘을 사용하여 메시지를 전송하지만 트랜잭션이 이동하면 인코딩된 메시지와 함께 UDP 패킷에 삽입됩니다.
byte[] txmsgBuffer = TransactionMessageBuffer.WriteTransactionMessageBuffer(txPropToken, messageBuffer);
int bytesSent = this.socket.SendTo(txmsgBuffer, 0, txmsgBuffer.Length, SocketFlags.None, this.remoteEndPoint);
TransactionMessageBuffer.WriteTransactionMessageBuffer
는 현재 트랜잭션의 전파 토큰을 메시지 엔터티와 병합하고 이를 버퍼에 배치하는 새로운 기능이 포함된 도우미 메서드입니다.
사용자 지정 트랜잭션 흐름 전송의 경우 클라이언트 구현에서는 트랜잭션 흐름이 필요한 서비스 작업을 파악하고 이 정보를 WCF에 전달해야 합니다. 또한 사용자 트랜잭션을 전송 계층으로 전송하기 위한 메커니즘도 있어야 합니다. 이 샘플에서는 “WCF 메시지 검사자”를 사용하여 이 정보를 가져옵니다. 여기서 구현된 TransactionFlowInspector
라는 클라이언트 메시지 검사자는 다음 작업을 수행합니다.
지정된 메시지 작업에 대해 트랜잭션을 이동해야 하는지 여부를 결정합니다. 이 작업은
IsTxFlowRequiredForThisOperation()
에서 수행됩니다.트랜잭션을 이동해야 하는 경우
TransactionFlowProperty
를 사용하여 현재 앰비언트 트랜잭션을 메시지에 연결합니다. 이 작업은BeforeSendRequest()
에서 수행됩니다.
public class TransactionFlowInspector : IClientMessageInspector
{
void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
// obtain the tx propagation token
byte[] propToken = null;
if (Transaction.Current != null && IsTxFlowRequiredForThisOperation(request.Headers.Action))
{
try
{
propToken = TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
}
catch (TransactionException e)
{
throw new CommunicationException("TransactionInterop.GetTransmitterPropagationToken failed.", e);
}
}
// set the propToken on the message in a TransactionFlowProperty
TransactionFlowProperty.Set(propToken, request);
return null;
}
}
static bool IsTxFlowRequiredForThisOperation(String action)
{
// In general, this should contain logic to identify which operations (actions) require transaction flow.
[...]
}
}
TransactionFlowInspector
자체는 사용자 지정 동작인 TransactionFlowBehavior
를 사용하여 프레임워크에 전달됩니다.
public class TransactionFlowBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
TransactionFlowInspector inspector = new TransactionFlowInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
위 메커니즘을 유지한 상태에서 사용자 코드는 서비스 작업을 호출하기 전에 TransactionScope
를 만듭니다. 트랜잭션을 서비스 작업으로 이동해야 하는 경우 메시지 검사자는 트랜잭션이 전송에 전달되는지 확인합니다.
CalculatorContractClient calculatorClient = new CalculatorContractClient("SampleProfileUdpBinding_ICalculatorContract");
calculatorClient.Endpoint.Behaviors.Add(new TransactionFlowBehavior());
try
{
for (int i = 0; i < 5; ++i)
{
// call the 'Add' service operation under a transaction scope
using (TransactionScope ts = new TransactionScope())
{
[...]
Console.WriteLine(calculatorClient.Add(i, i * 2));
}
}
calculatorClient.Close();
}
catch (TimeoutException)
{
calculatorClient.Abort();
}
catch (CommunicationException)
{
calculatorClient.Abort();
}
catch (Exception)
{
calculatorClient.Abort();
throw;
}
서비스는 클라이언트로부터 UDP 패킷을 수신한 다음 이 패킷을 역직렬화하여 메시지와 트랜잭션을 추출합니다.
count = listenSocket.EndReceiveFrom(result, ref dummy);
// read the transaction and message TransactionMessageBuffer.ReadTransactionMessageBuffer(buffer, count, out transaction, out msg);
TransactionMessageBuffer.ReadTransactionMessageBuffer()
는 TransactionMessageBuffer.WriteTransactionMessageBuffer()
가 수행한 serialization 프로세스를 반대로 하는 도우미 메서드입니다.
트랜잭션이 이동하면 TransactionMessageProperty
의 메시지에 추가됩니다.
message = MessageEncoderFactory.Encoder.ReadMessage(msg, bufferManager);
if (transaction != null)
{
TransactionMessageProperty.Set(transaction, message);
}
이렇게 하면 디스패처가 디스패치할 때 트랜잭션을 선택한 다음 메시지에 설명된 서비스 작업을 호출할 때 이 트랜잭션을 사용합니다.
샘플을 설치, 빌드 및 실행하려면
솔루션을 빌드하려면 Windows Communication Foundation 샘플 빌드의 지침을 따릅니다.
현재 샘플은 전송: UDP 샘플과 비슷하게 실행해야 합니다. 이 샘플을 실행하려면 UdpTestService.exe를 실행하여 서비스를 시작합니다. Windows Vista를 실행하는 경우에는 상승된 권한으로 서비스를 시작해야 합니다. 이렇게 하려면 파일 탐색기에서 UdpTestService.exe를 마우스 오른쪽 단추로 클릭하고 관리자 권한으로 실행을 클릭합니다.
다음과 같은 출력이 표시됩니다.
Testing Udp From Code. Service is started from code... Press <ENTER> to terminate the service and start service from config...
이때 UdpTestClient.exe를 실행하여 클라이언트를 시작할 수 있습니다. 클라이언트에서 생성되는 출력은 다음과 같습니다.
0 3 6 9 12 Press <ENTER> to complete test.
서비스 출력은 다음과 같습니다.
Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! The client transaction has flowed to the service adding 0 + 0 The client transaction has flowed to the service adding 1 + 2 The client transaction has flowed to the service adding 2 + 4 The client transaction has flowed to the service adding 3 + 6 The client transaction has flowed to the service adding 4 + 8
서비스 애플리케이션은 클라이언트가 보낸,
The client transaction has flowed to the service
작업의clientTransactionId
매개 변수에 포함된 트랜잭션 식별자를 서비스 트랜잭션의 식별자와 일치시킬 수 있는 경우CalculatorService.Add()
라는 메시지를 표시합니다. 이 두 식별자는 클라이언트 트랜잭션이 서비스로 이동한 경우에만 일치합니다.구성을 사용하여 게시된 엔드포인트에 대해 클라이언트 애플리케이션을 실행하려면 서비스 애플리케이션 창에서 Enter 키를 누른 다음 테스트 클라이언트를 다시 실행합니다. 서비스에서 다음과 같이 출력되어야 합니다.
Testing Udp From Config. Service is started from config... Press <ENTER> to terminate the service and exit...
서비스에 대해 클라이언트를 실행하면 이전과 비슷한 출력이 표시됩니다.
Svcutil.exe를 사용하여 클라이언트 코드 및 구성을 다시 생성하려면 서비스 애플리케이션을 시작한 다음 샘플의 루트 디렉터리에서 다음 Svcutil.exe 명령을 실행합니다.
svcutil http://localhost:8000/udpsample/ /reference:UdpTransport\bin\UdpTransport.dll /svcutilConfig:svcutil.exe.config
Svcutil.exe는
sampleProfileUdpBinding
에 대한 바인딩 확장명 구성을 생성하지 않으므로 직접 추가해야 합니다.<configuration> <system.serviceModel> … <extensions> <!-- This was added manually because svcutil.exe does not add this extension to the file --> <bindingExtensions> <add name="sampleProfileUdpBinding" type="Microsoft.ServiceModel.Samples.SampleProfileUdpBindingCollectionElement, UdpTransport" /> </bindingExtensions> </extensions> </system.serviceModel> </configuration>