Transacted WCF client call via loosely typed contract
-
Tuesday, August 05, 2008 11:17 AM
I have a WCF service which exposes operations which could be invoked inside transaction scope. My client side instead of using the exact service contract, uses loosly typed contract to communicate at message level with the service. With this approach, my client side code is not able to flow in the transaction to the service.
Attach is the sample code:
--------------
using
System;using
System.Collections.Generic;using
System.Linq;using
System.Text;using
System.ServiceModel;using
System.ServiceModel.Channels;using
System.ServiceModel.Description;using
System.Transactions;using
System.Xml;using
System.IO;namespace
ConsoleApplication1{
[
ServiceContract(Namespace = "somenamespace")] interface ITransactedService{
[
OperationContract(Action = "operation1", ReplyAction = "operation1/response")][
TransactionFlow(TransactionFlowOption.Allowed)] int Operation1(int a, int b);[
OperationContract(Action = "operation2", ReplyAction = "operation2/response")][
TransactionFlow(TransactionFlowOption.Mandatory)] int Operation2(int a, int b);}
[
ServiceContract] interface IClientContract{
[
OperationContract(Action = "*", ReplyAction = "*")][
TransactionFlow(TransactionFlowOption.Allowed)] Message Process(Message request);}
class Service : ITransactedService{
#region
ITransactedService Members[
OperationBehavior(TransactionScopeRequired = true)] public int Operation1(int a, int b){
return a + b;}
[
OperationBehavior(TransactionScopeRequired=true)] public int Operation2(int a, int b){
return a + b;}
#endregion
}
class Program{
static void Main(string[] args){
ServiceHost host = new ServiceHost(typeof(Service)); NetTcpBinding binding = new NetTcpBinding();binding.TransactionFlow =
true; EndpointAddress address = new EndpointAddress("net.tcp://localhost/transactedtest");host.Description.Endpoints.Add(
new ServiceEndpoint(ContractDescription.GetContract(typeof(ITransactedService)),binding, address)); ServiceDebugBehavior debug = new ServiceDebugBehavior();debug.IncludeExceptionDetailInFaults =
true;host.Description.Behaviors.Remove<
ServiceDebugBehavior>();host.Description.Behaviors.Add(debug);
host.Open();
IChannelFactory<IClientContract> factory = new ChannelFactory<IClientContract>(binding);factory.Open();
try{
IClientContract channel = factory.CreateChannel(address); using(TransactionScope ts = new TransactionScope()){
StringBuilder sb = new StringBuilder(); XmlWriter writer = XmlWriter.Create(sb);writer.WriteStartElement(
"Operation1", "somenamespace");writer.WriteStartElement(
"a");writer.WriteString(
"100");writer.WriteEndElement();
//close awriter.WriteStartElement(
"b");writer.WriteString(
"200");writer.WriteEndElement();
// close bwriter.WriteEndElement();
//close operation1writer.Close();
StringReader sr = new StringReader(sb.ToString()); XmlReader reader = XmlReader.Create(sr); Message request = Message.CreateMessage(MessageVersion.Default, "operation2",reader); Message response = channel.Process(request);sb =
new StringBuilder();writer =
XmlWriter.Create(sb); XmlDictionaryWriter dwriter = XmlDictionaryWriter.CreateDictionaryWriter(writer);response.WriteBodyContents(dwriter);
dwriter.Flush();
dwriter.Close();
Console.WriteLine("Got resposne from service:{0}", sb.ToString());ts.Complete();
}
}
catch(Exception e){
}
}
}
}
-----------------
I get an fault message from service saying that Operation2 must be invoked inside Transaction context. The exact message is:
<?xml version="1.0" encoding="utf-16"?><s:Fault xmlns
="http://www.w3.org/2003/05/soap-envelope"><s:Code><s:Value>s
ender</s:Value><s
ubcode><s:Value xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/transactions">a:TransactionHeaderMissing</s:Value></s
ubcod
e></s:Code><s:Reason><s:Text xml:lang="en-IN">The service operation requires a transaction to be flowed.</s:Text></s:Reason></s:Fault>My scenario is performance critical, hence I can't use reflection on client side to create exact service side contract and pump in the messages. Is there any
Also Is there anyway for the client to get exceptions instead of fault message, while working with loosely typed contract?
Thanks a lot,
Answers
-
Wednesday, August 06, 2008 10:50 PM
Hi
This scenario does not work because the transaction header is not written on the outgoing message here. This is a known issue and we plan to fix it in .Net Framework 4.0 . Currently there is no workaround for this. Please let us know if you have further questions.
Thanks
Supriya
All Replies
-
Tuesday, August 05, 2008 12:12 PMModeratorHi,
I don't have the exact answer for you yet, but I believe you have to create a OperationContextScope and inject the custom SOAP headers that contains the transaction id etc. before you invoke the service method.
The information about the transaction can be found in the Transaction.Current property.
If you turn on WCF tracing, you can try to flow a transaction the normal way, and see which extra headers that is sent over the wire.
--larsw -
Wednesday, August 06, 2008 10:50 PM
Hi
This scenario does not work because the transaction header is not written on the outgoing message here. This is a known issue and we plan to fix it in .Net Framework 4.0 . Currently there is no workaround for this. Please let us know if you have further questions.
Thanks
Supriya
-
Saturday, August 09, 2008 7:19 PMHi Supriya
I also need a similar functionality. Can you describe in what scenarios exactly this problem exists? When I use untyped client contract is it never possible to use transactions, even when explicitly starting them in code?
Thanks,
Yaron -
Sunday, August 10, 2008 8:50 AM
Incorrect Supriya.
It is possible to flow a transaction to an untyped contract and you can progrmatically set the Action on the client programatically in this scenario too, if you wish.
Code SnippetTextReader text = new StringReader("<test>Hello</test>");
XmlReader reader = new XmlTextReader(text);
var factory = new ChannelFactory<System.ServiceModel.Channels.IRequestChannel>("IRequestChannel");
factory.Endpoint.Contract.SessionMode = SessionMode.Allowed;
factory.Endpoint.Contract.Operations[0].Behaviors.Add(new TransactionFlowAttribute(TransactionFlowOption.Mandatory));
var channel = factory.CreateChannel();
Message request = Message.CreateMessage(MessageVersion.Default, "*", reader);
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
Message response = channel.Request(request);
string f = response.ToString();
scope.Complete();
factory.Close();
}
The TransactionFlowBindingElement requires you to declare which contract operations will flow a transaction but you can add this code before opening the channelfactory/CreateChannel:
Code Snippetfactory.Endpoint.Contract.Operations[0].Behaviors.Add(new TransactionFlowAttribute(TransactionFlowOption.Required));
factory.Endpoint.Contract.Operations[0].Messages[0] = new MessageDescription(action, MessageDirection.Input);
Note when programatically setting the action, it has to be specified and the same, when you create your message.
Hope ths helps everyone as there has been much discussion on this topic and some misstatements over the months.
Ben - ECM
- Proposed As Answer by Raci Wednesday, October 28, 2009 4:11 PM
-
Wednesday, October 28, 2009 4:10 PMThanks for the help.
-
Tuesday, February 15, 2011 7:28 AM
I experienced this problem as well, and I found a solution:
private static void ConfigureTransactionFlow(ServiceEndpoint endpoint) { CustomBinding binding = endpoint.Binding as CustomBinding; if (binding == null) { binding = new CustomBinding(endpoint.Binding); } TransactionFlowBindingElement element = binding.Elements.Find<TransactionFlowBindingElement>(); if (element != null) { element.AllowWildcardAction = true; endpoint.Binding = binding; } }
You can find my blog about it here: http://bveldhoen.wordpress.com/2011/02/15/generic-message-contract-and-transaction-flow-in-wcf/

