TransactionMessagePropertyUDPTransport 샘플은 Windows Communication Foundation(WCF)의 Transport: 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>