none
Unable to send large amount of GZIPed post HTTP request data to WCF Service RRS feed

  • Question

  • I have coded a C# WCF web service that contains an endpoint for receiving raw data GZIP'd from a HTTP Post request.
    The problem I'm having is that when the post data is larger than 64k, the web service automatically returns a 400 HTTP response, which is expected based on the default settings.
    So for the service host binding (I'm using a custom GZIP binding based off a WebHttpBinding), I configured it's ReaderQuotas to have all its Max properties set to the maximum values, however the service still limits the data to 64k and throws back 400 http response for data too great, does anyone have any other clues on how to raise the limits?

    I'm using the GZIP binding class based on the Microsoft example http://msdn.microsoft.com/en-us/library/ms751458(v=vs.100).aspx, and I tweaked it to set ReaderQuotas properties in the GZIP class based off other suggestions on the web, but that still does not make any different.

    Additionally I set up a an error handler behaviour on the service to capture when these bad requests come in, however that too is not getting triggered when these large HTTP requests fallover.

    Class that creates and starts service endpoint

    public class serviceRunner() { public WebServiceHost startHost() { WebServiceHost webserviceHost = new WebServiceHost(typeof(Service1), new Uri("http://localhost:8090")); webserviceHost.AddServiceEndpoint(typeof(IService), getBinding(), "example").Behaviors.Add(new WebHttpBehavior()); ((ServiceBehaviorAttribute)webserviceHost.Description.Beha viors[typeof(ServiceBehaviorAttribute)]).MaxItemsInObjectG raph = 999999999; webserviceHost.Open(); return webserviceHost; }

    private static Binding getBinding()
    {
    //create web HTTP binding, setting upper limits for data coming in
    WebHttpBinding webHTTPBinding = new WebHttpBinding();
    webHTTPBinding.MaxReceivedMessageSize = int.MaxValue;
    webHTTPBinding.MaxBufferSize = int.MaxValue;
    webHTTPBinding.MaxBufferPoolSize = int.MaxValue;
    webHTTPBinding.ReaderQuotas.MaxArrayLength = int.MaxValue;
    webHTTPBinding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
    webHTTPBinding.ReaderQuotas.MaxBytesPerRead = int.MaxValue;
    webHTTPBinding.ReaderQuotas.MaxDepth = int.MaxValue;
    webHTTPBinding.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;

    //create a new custom binding, based on the web HTTP Binding, that has a GZIP encoding element
    CustomBinding customBinding = new CustomBinding(webHTTPBinding);
    for (int i = 0; i < customBinding.Elements.Count; i++){
    if (customBinding.Elements[i] is WebMessageEncodingBindingElement){
    WebMessageEncodingBindingElement webBE = (WebMessageEncodingBindingElement)customBinding.Elements[i];
    webBE.ContentTypeMapper = new MyMapper();
    customBinding.Elements[i] = new GZipMessageEncodingBindingElement(webBE);
    }
    else if (customBinding.Elements[i] is TransportBindingElement)
    {
    ((TransportBindingElement)customBinding.Elements[i]).MaxReceivedMessageSize = int.MaxValue;
    ((TransportBindingElement)customBinding.Elements[i]).MaxBufferPoolSize = int.MaxValue;
    }
    }
    return customBinding;
    }

    }

    /*Class to set content type of outgoing HTTP response*/
    public class MyMapper : WebContentTypeMapper{
      public override WebContentFormat GetMessageFormatForContentType(string contentType){
      if (contentType == "text/plain"){
      return WebContentFormat.Raw;
      }else{
      return WebContentFormat.Json;
      }
      }
    }
    // This is constants for GZip message encoding policy.
    static class GZipMessageEncodingPolicyConstants{
     public const string GZipEncodingName = "GZipEncoding";
     public const string GZipEncodingNamespace = "http://schemas.microsoft.com/ws/06/2004/mspolicy/netgzip1";
     public const string GZipEncodingPrefix = "gzip";
    }
    
    //This is the binding element that, when plugged into a custom binding, will enable the GZip encoder
    public sealed class GZipMessageEncodingBindingElement
    : MessageEncodingBindingElement //BindingElement
    , IPolicyExportExtension
    {
     MessageEncodingBindingElement innerBindingElement;
    
    //By default, use the default text encoder as the inner encoder
    public GZipMessageEncodingBindingElement()
     : this(new TextMessageEncodingBindingElement()) {
    }
    
    public GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement){
     this.innerBindingElement = messageEncoderBindingElement;
    }
    
    public MessageEncodingBindingElement InnerMessageEncodingBindingElement{
     get { return innerBindingElement; }
     set { innerBindingElement = value; }
    }
    
    //Main entry point into the encoder binding element. 
    public override MessageEncoderFactory CreateMessageEncoderFactory(){
     return new GZipMessageEncoderFactory(innerBindingElement.CreateMessageEncoderFactory());
    }
    
    public override MessageVersion MessageVersion{
     get { return innerBindingElement.MessageVersion; }
     set { innerBindingElement.MessageVersion = value; }
    }
    
    public override BindingElement Clone(){
     return new GZipMessageEncodingBindingElement(this.innerBindingElement);
    }
    
    public override T GetProperty<T>(BindingContext context){
     if (typeof(T) == typeof(XmlDictionaryReaderQuotas)){
    	return innerBindingElement.GetProperty<T>(context);
     }else{
    	return base.GetProperty<T>(context);
     }
    }
    
    public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context){
     if (context == null)
     throw new ArgumentNullException("context");
    
     context.BindingParameters.Add(this);
    
     var property = GetProperty<XmlDictionaryReaderQuotas>(context);
     property.MaxStringContentLength = int.MaxValue;
     property.MaxArrayLength = int.MaxValue;
     property.MaxBytesPerRead = int.MaxValue;
    
     return context.BuildInnerChannelFactory<TChannel>();
    }
    
    public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context){
     if (context == null)
      throw new ArgumentNullException("context");
      context.BindingParameters.Add(this);
      return context.BuildInnerChannelListener<TChannel>();
    }
    
    public override bool CanBuildChannelListener<TChannel>(BindingContext context){
     if (context == null)
      throw new ArgumentNullException("context");
    
      context.BindingParameters.Add(this);
      return context.CanBuildInnerChannelListener<TChannel>();
    }
    
    void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext policyContext){
     if (policyContext == null){
    	throw new ArgumentNullException("policyContext");
     }
     XmlDocument document = new XmlDocument();
     policyContext.GetBindingAssertions().Add(document.CreateElement(
     GZipMessageEncodingPolicyConstants.GZipEncodingPrefix,
     GZipMessageEncodingPolicyConstants.GZipEncodingName,
     GZipMessageEncodingPolicyConstants.GZipEncodingNamespace));
    }
    }




    • Edited by XsqitDev Wednesday, November 20, 2013 8:52 AM
    Wednesday, November 20, 2013 8:09 AM

Answers

  • So Here's the programmatic solution I've come up with after spending days on this. Note that I do not know how to configure the solution in the app.config file but only through code. Firstly follow this Link  to obtain and fix the GZIP classes in Microsoft's coding samples. Then use the below example code as a basis for configuring your own web service.

    //Some class class to start up the REST web service
    public class someClass(){
        public static void runRESTWebservice(){
            webserviceHost = new WebServiceHost(typeof(Service1), new Uri("http://localhost:8080));
            webserviceHost.AddServiceEndpoint(typeof(IService), getBinding(), "webservice").Behaviors.Add(new WebHttpBehavior());
            webserviceHost.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
        }
    
        //produces a custom web service binding mapped to the obtained gzip classes
        private static Binding getBinding(){
            CustomBinding customBinding = new CustomBinding(new WebHttpBinding());
            for (int i = 0; i < customBinding.Elements.Count; i++)
            {
                if (customBinding.Elements[i] is WebMessageEncodingBindingElement)
                {
                    WebMessageEncodingBindingElement webBE = (WebMessageEncodingBindingElement)customBinding.Elements[i];
                    webBE.ContentTypeMapper = new MyMapper();
                    customBinding.Elements[i] = new GZipMessageEncodingBindingElement(webBE);
                }
                else if (customBinding.Elements[i] is TransportBindingElement)
                {
                    ((TransportBindingElement)customBinding.Elements[i]).MaxReceivedMessageSize = int.MaxValue;
                }
            }
            return customBinding;
        }
    }
    
    //mapper class to match json responses
    public class MyMapper : WebContentTypeMapper{
        public override WebContentFormat GetMessageFormatForContentType(string contentType){
            return WebContentFormat.Json;
        }
    }
    
    //Define a service contract interface plus methods that returns JSON responses
    [ServiceContract]
    public interface IService{
        [WebGet(UriTemplate = "somedata", ResponseFormat = WebMessageFormat.Json)]
        string getSomeData();
    }
    
    //In your class that implements the contract explicitly set the encoding of the response in the methods you implement
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class Service1 : IService
    {
        public string getSomeData()
        {
            WebOperationContext.Current.OutgoingResponse.Headers[HttpResponseHeader.ContentEncoding] = "gzip";
            return "some data";
        }
    }

    I worked most this out by following this link.

    Note: It somewhat baffles me how Microsoft haven't built GZIP natively into WCF being such an essential part of any REST webservice returning large sets of data.

    Tuesday, November 26, 2013 9:19 AM