Транспорт: пример пользовательских транзакций по протоколу UDP
Пример TransactionMessagePropertyUDPTransport основан на примере транспорта: UDP в Windows Communication Foundation (WCF)Transport Extensibility. Он расширяет пример транспорта 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;
}
После получения от клиента пакета UPD служба десериализует его, чтобы извлечь сообщение и, возможно, транзакцию.
count = listenSocket.EndReceiveFrom(result, ref dummy);
// read the transaction and message TransactionMessageBuffer.ReadTransactionMessageBuffer(buffer, count, out transaction, out msg);
TransactionMessageBuffer.ReadTransactionMessageBuffer()
- это вспомогательный метод, обращающий процесс сериализации, выполняемый TransactionMessageBuffer.WriteTransactionMessageBuffer()
.
Если транзакция получена в потоке, она присоединяется к сообщению в свойстве 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()
, идентификатору транзакции службы. Сопоставление происходит, только если транзакция клиента доставлена службе в виде потока.Для выполнения клиентского приложения относительно конечных точек, опубликованных с помощью конфигурации, нажмите в окне приложения службы клавишу ВВОД и снова запустите тестовый клиент. Служба должна предоставить следующие результаты.
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>