傳輸:自訂跨 UDP 異動範例
TransactionMessagePropertyUDPTransport 範例是以 Windows Communication Foundation (WCF) 傳輸擴充性中的傳輸: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
是 Helper 方法,其中包含的新功能可以將目前交易的傳播權杖與訊息實體 (Entity) 合併,再將它放在緩衝區中。
就自訂交易流程傳輸而言,用戶端實作必須知道何種服務作業需要交易流程,並將此項資訊傳遞給 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()
是 Helper 方法,它會反轉 TransactionMessageBuffer.WriteTransactionMessageBuffer()
所執行的序列化 (Serialization) 程序。
如果有異動流入,就會將它附加至 TransactionMessageProperty
中的訊息。
message = MessageEncoderFactory.Encoder.ReadMessage(msg, bufferManager);
if (transaction != null)
{
TransactionMessageProperty.Set(transaction, message);
}
這可以確保發送器在分派階段收到異動,且在呼叫訊息所定址的服務作業時使用此異動。
若要安裝、建置及執行範例
若要建置解決方案,請遵循建置 Windows Communication Foundation 範例中的指示進行。
您應該以執行 Transport: 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>