none
Transacted WCF client call via loosely typed contract

    Question

  •  

    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 a

    writer.WriteStartElement("b");

    writer.WriteString("200");

    writer.WriteEndElement(); // close b

    writer.WriteEndElement(); //close operation1

    writer.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 xmlnsTongue Tied="http://www.w3.org/2003/05/soap-envelope"><s:Code><s:Value>sTongue Tiedender</s:Value><sTongue Tiedubcode><s:Value xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/transactions">a:TransactionHeaderMissing</s:Value></sTongue Tiedubcod
    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,

    Tuesday, August 05, 2008 11:17 AM

Answers

  • 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

     

    Wednesday, August 06, 2008 10:50 PM

All replies

  • Hi,

     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
    Tuesday, August 05, 2008 12:12 PM
    Moderator
  • 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

     

    Wednesday, August 06, 2008 10:50 PM
  • Hi 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
    Saturday, August 09, 2008 7:19 PM
  • 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 Snippet

    TextReader 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 Snippet

    factory.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
    Sunday, August 10, 2008 8:50 AM
  • Thanks for the help.
    Wednesday, October 28, 2009 4:10 PM
  • 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/

    Tuesday, February 15, 2011 7:28 AM