none
How to create a streaming operation with a non-wrapped request and a wrapped response? RRS feed

  • Question

  • Hello,

    I want to create a streaming download operation in a C# ServiceContract. I want the response to be streaming, with the Stream wrapped in an extra element (IsWrapped=true). The request for this operation will be generated using xsd.exe, so should not be wrapped (IsWrapped=false).

    I created my service and testing it with SoapUI showed the expected results. A response with an Mtom attachment in an extra element. When I add a service reference to my service in Visual studio for unittesting purposes however, the generated code contains a byte[] in a class with the name of the wrapper instead of a Stream in the top level of the message, so the entire response will be buffered at client side. Having responses of maximum 1,5GB, this is unacceptable.

    When I change the request to be wrapped (IsWrapped=true) as well, the response does contain a Stream in the client generated code. I've tried 'Add service reference' is Visual Studio and svcutil.exe to generate client code, both with the same results.

    Question: How can I generate client code containing a Stream instead of a byte[] when the request message is not wrapped (IsWrapped=false) and the response message is wrapped (IsWrapped=true)? (If this requires changes in the service, that's fine, because it is not in Production yet)

    See a simplified code sample below, illustrating my problem:

    Simplified Service Contract:

    [ServiceContract]
    public interface IBasicService
    {
        [OperationContract]
        ResponseMessage GetData(RequestMessage request);
    }

    Simplified service implementation:

    public class BasicService : IBasicService
    {
        public ResponseMessage GetData(RequestMessage request)
        {
            var response = new ResponseMessage
            {
                Contents = new MemoryStream(Encoding.UTF8.GetBytes(request.RequestText))
            };
    
            return response;
        }
    }

    Simplified request message definition:

    // Change this to IsWrapped=true. That will have 'Add Service reference' Generate a Stream for the Response message....Why does it matter what the request looks like?!
    [MessageContract(IsWrapped=false)]
    public class RequestMessage
    {
        [MessageBodyMember]
        public string RequestText { get; set; }
    }

    Simplified response message definition:

    [MessageContract(IsWrapped = true, WrapperName="WrapperElement", WrapperNamespace="http://WrapperNamespace")]
    public class ResponseMessage
    {
        [MessageBodyMember]
        public System.IO.Stream Contents { get; set; }
    }

    Web.config

    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.5" />
        <httpRuntime targetFramework="4.5"/>
      </system.web>
      <system.serviceModel>
        <bindings>
          <basicHttpBinding>
            <binding name="WrappedBinding" 
                     transferMode="StreamedResponse" 
                     messageEncoding="Mtom" />
          </basicHttpBinding>
        </bindings>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
              <serviceDebug includeExceptionDetailInFaults="true"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <services>
          <service name="Wrapped.BasicService">
            <endpoint address="" 
                      binding="basicHttpBinding" 
                      bindingConfiguration="WrappedBinding"
                      contract="Wrapped.IBasicService" />
          </service>
        </services>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
      </system.serviceModel>
      <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
        <directoryBrowse enabled="true"/>
      </system.webServer>
    
    </configuration>

    Resulting code from 'Add Service Reference' when request message has IsWrapped=false, note the class with the name of the wrapper and de byte[] property:

    // Generated method
    public Tests.BasicService.WrapperElement GetData(string RequestText) {
                Tests.BasicService.RequestMessage inValue = new Tests.BasicService.RequestMessage();
                inValue.RequestText = RequestText;
                Tests.BasicService.ResponseMessage retVal = ((Tests.BasicService.IBasicService)(this)).GetData(inValue);
                return retVal.WrapperElement;
            }
    
    //Generated response class
    [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
        [System.ServiceModel.MessageContractAttribute(IsWrapped=false)]
        public partial class ResponseMessage {
    
            [System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://WrapperNamespace", Order=0)]
            public Tests.BasicService.WrapperElement WrapperElement;
    
            public ResponseMessage() {
            }
    
            public ResponseMessage(Tests.BasicService.WrapperElement WrapperElement) {
                this.WrapperElement = WrapperElement;
            }
        }
    
    /// <remarks/>
        [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34230")]
        [System.SerializableAttribute()]
        [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://WrapperNamespace")]
        public partial class WrapperElement : object, System.ComponentModel.INotifyPropertyChanged {
    
            // NOTE THE byte[] HERE!
            private byte[] contentsField;
    
            /// <remarks/>
            [System.Xml.Serialization.XmlElementAttribute(Namespace="http://tempuri.org/", DataType="base64Binary", Order=0)]
            public byte[] Contents {
                get {
                    return this.contentsField;
                }
                set {
                    this.contentsField = value;
                    this.RaisePropertyChanged("Contents");
                }
            }
    
            public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    
            protected void RaisePropertyChanged(string propertyName) {
                System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
                if ((propertyChanged != null)) {
                    propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
                }
            }
        }

    Generated code when the request has IsWrapped = true, note the Stream is now a MessageBodyMember:

    // Generated method
    public System.IO.Stream GetData(string RequestText) {
                Tests.BasicService.RequestMessage inValue = new Tests.BasicService.RequestMessage();
                inValue.RequestText = RequestText;
                Tests.BasicService.ResponseMessage retVal = ((Tests.BasicService.IBasicService)(this)).GetData(inValue);
                return retVal.Contents;
            }
    
    // Generated reponse message
    [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
        [System.ServiceModel.MessageContractAttribute(WrapperName="WrapperElement", WrapperNamespace="http://WrapperNamespace", IsWrapped=true)]
        public partial class ResponseMessage {
    
            [System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://tempuri.org/", Order=0)]
            // NOTE THAT THIS DOES CONTAIN A Stream.
            public System.IO.Stream Contents;
    
            public ResponseMessage() {
            }
    
            public ResponseMessage(System.IO.Stream Contents) {
                this.Contents = Contents;
            }
        }


    Monday, October 19, 2015 11:33 AM

All replies

  • I created my service and testing it with SoapUI showed the expected results. A response with an Mtom attachment in an extra element. When I add a service reference to my service in Visual studio for unittesting purposes however, the generated code contains a byte[] in a class with the name of the wrapper instead of a Stream in the top level of the message, so the entire response will be buffered at client side. Having responses of maximum 1,5GB, this is unacceptable.

    You can define it as List, Stream or codd-knows-what on the service side.
    What will actually get transported is a base64 encoded byte array in a XML document, to be parsed on the other end once completely recieved.

    There is no point transmitting it as anything else. SOAP only deals with the full return value in a XML document and autoamtically turns anything into a more primitive type (any collction to array) that is unambigious among programming languages.
    Interoprability was big factor in SOAP, and simplicity is the best way to archieve it.

    If you want to define details of how those messages get transmitted and parsed (inlcuding avoiding having to fully transmit the message before processing), SOAP is likely the wrong tool. However SOAP specifications according to wikipedia have this:
    "The SOAP underlying protocol binding framework describing the rules for defining a binding to an underlying protocol that can be used for exchanging SOAP messages between SOAP nodes"

    If you want to transmit in contrlled bursts (rather then all at once), that migh be a way. Of course it is still a level less efficient to just move to a more manual way of transmission.

    Monday, October 19, 2015 2:20 PM
  • Thank you for your reply.

    I guess what you're saying is I shouldn't have to change the service in order to handle the contents of the response in a streaming manner on the client side. I agree with that.

    Probably I should rephrase the question:

    -How do I implement a client with a streaming implementation that consumes my service? I would expect that, since the service returns a message with a Stream as MessageBodyMember and has Transfermode="StreamedResponse" in the web.config binding, a code generator would treat the contents as a stream.

    Additionally, the following question pops up:

    -Why does the code generator treat the response contents as a Stream when the request message is wrapped (IsWrapped=true) and treat the response contents as a byte[] when the request message isn't wrapped (IsWrapped=false)? (Feels like a bug in the code generation, please correct me if I'm wrong)

    I'm 'afraid' that clients consuming my service will have to bend into a custom buffering implementation because the code they generate creates a byte[]...

    EDIT: Is my question clear? Or do you need more context?
    • Edited by Jesse de Wit Thursday, October 22, 2015 8:49 AM Uncertain about question clarity
    Tuesday, October 20, 2015 1:39 PM
  • Hi JssDWt,

    I am not sure I understand your question. But as far as I know, when we use the

    Message Contract, we can override the Message Format for us.

    We can use the ClientMessageFormatter and DispatchMessageFormatter to do it.

    For more information, please refer to the following articles:

    1.WCF Extensibility – Message Formatters 

    Best Regards,

    Grady

    Friday, October 23, 2015 6:04 AM
    Moderator
  • Let me try to clarify.

    Steps to reproduce my problem:

    1. Create the service in a WCF project Visual Studio with the code I posted above.
    2. Build the service and host in IIS express.
    3. Create a unittest project and choose 'Add Service Reference'. Add de wsdl of the service hosted in IIS express.
    4. Try to write a unittest that does not buffer the response message. (If the message is large)

    Step 4 is where my problem is. The code generated in step 3 contains a byte[] in the response message. If the response were to be 1.5GB, we would have a big problem while deserializing the message!

    Now, when you change the attribute above the request message definition in the service to the following.

    [MessageContract(IsWrapped=true)]

    Update the service reference in the unittest. The generated code from step 3 does contain a Stream in the response message now. (I don't understand why the 'wrapping' of the request message changes the generated code for the response message.)

    So how do I fix step 4, when I have a non-wrapped request message. That is my question. I don't believe a Message Formatter will help me with this problem?

    Please let me now if the question is still unclear.




    • Edited by Jesse de Wit Monday, November 9, 2015 8:21 AM make text bold. Ask for clarification.
    Thursday, October 29, 2015 2:10 PM