How to override WebServiceHostFactory MaxReceivedMessageSize?

9.5k views Asked by At

There are a lot of similar questions out there, but I have tried every solution in every one of them to no avail.

We have a web service that initialises with WebServiceHostFactory, but if any more than 64k is thrown at it, we get a '400 Bad Request'. Normally, this would just be resolved by bumping up the MaxReceivedMessageSize, MaxBufferSize and MaxBufferPoolSize. The problem is that using the WebServiceHostFactory, the Web.Config is completely ignored. No changes I make in the ServiceModel section reflect in the service at all.

It would be nice to just completely ditch WebServiceHostFactory and set up the web.config from scratch, but our service will not run without it. One of the methods has a stream parameter as well as some other string params. Without the factory, we get

System.InvalidOperationException: For request in operation Test to be a stream the operation must have a single parameter whose type is Stream

So it is not an option to remove the factory. I can't work out exactly what the factory is doing that fixes this error but I spent 4 days on it and never got anywhere.

I've also tried overriding MaxReceivedMessageSize programatically, with some examples I found on around the place:

protected override void OnOpening()
        {
            base.OnOpening();
            foreach (var endpoint in Description.Endpoints)
            {

                //var binding = endpoint.Binding as WebHttpBinding;
                //if (binding != null)
                //{
                //    binding.MaxReceivedMessageSize = 20000000;
                //    binding.MaxBufferSize = 20000000;
                //    binding.MaxBufferPoolSize = 20000000;
                //    binding.ReaderQuotas.MaxArrayLength = 200000000;
                //    binding.ReaderQuotas.MaxStringContentLength = 200000000;
                //    binding.ReaderQuotas.MaxDepth = 32;
                //}


                //var transport = endpoint.Binding.CreateBindingElements().Find<HttpTransportBindingElement>();
                //if (transport != null)
                //{
                //    transport.MaxReceivedMessageSize = 20000000;
                //    transport.MaxBufferPoolSize = 20000000;
                //}


                var newTransport = new HttpTransportBindingElement();
                newTransport.MaxReceivedMessageSize = 20000000;
                newTransport.MaxBufferPoolSize = 20000000;
                endpoint.Binding.CreateBindingElements().Add(newTransport);
            }
        }

The first doesn't work as the factory creates a CustomBinding which cannot be cast to a WebHttpBinding. The second doesn't work as it seems the binding elements are read only - no matter what I set the elements to, nothing changes, which I have verified by reading the values back after 'changing' them. The third was a last ditch attempt to try to throw a new binding element in there, but of course this failed as well.

Now we are completely at a loss. How can we get this thing to run? You think it would be so simple!

Thanks guys

Web.Config

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <compilation targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <bindings>
      <webHttpBinding>
        <binding name="rest" maxReceivedMessageSize="500000000" />
      </webHttpBinding>
    </bindings>
    <services>
      <service name="SCAPIService" behaviorConfiguration="ServiceBehaviour">
        <endpoint address="" binding="webHttpBinding" bindingConfiguration="rest" contract="ISCAPIService" behaviorConfiguration="web">
        </endpoint>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehaviour">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>
</configuration>

Edit, using a suggested fix that has been unsuccessful so far:

public class MyServiceHost : WebServiceHost
    {
        public MyServiceHost()
        {
        }

        public MyServiceHost(object singletonInstance, params Uri[] baseAddresses)
            : base(singletonInstance, baseAddresses)
        {
        }

        public MyServiceHost(Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
        }

        protected override void ApplyConfiguration()
        {
            base.ApplyConfiguration();
            APIUsers.TestString += "here" + Description.Endpoints.Count.ToString();
            foreach (var endpoint in this.Description.Endpoints)
            {
                var binding = endpoint.Binding;
                APIUsers.TestString += binding.GetType().ToString();
                if (binding is WebHttpBinding)
                {
                    var web = binding as WebHttpBinding;
                    web.MaxBufferSize = 2000000;
                    web.MaxBufferPoolSize = 2000000;
                    web.MaxReceivedMessageSize = 2000000;
                }
                var myReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas();
                myReaderQuotas.MaxStringContentLength = 2000000;
                myReaderQuotas.MaxArrayLength = 2000000;
                myReaderQuotas.MaxDepth = 32;
                binding.GetType().GetProperty("ReaderQuotas").SetValue(binding, myReaderQuotas, null);
            }
        }
    }

    class MyWebServiceHostFactory : WebServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            return new MyServiceHost(serviceType, baseAddresses);
        }
    }
2

There are 2 answers

7
Rajesh On BEST ANSWER

If you are implementing your own custom service host you should be able to override a method called "ApplyConfiguration" where you can associate all the configuration properties you need for your binding. Some sample code as shown below:

EDIT: Adding my servicehost factory implementation

public class MyServiceHost : System.ServiceModel.ServiceHost
{
    public MyServiceHost () { }

    public MyServiceHost (Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses)
    {

    }

    public MyServiceHost (object singletonInstance, params Uri[] baseAddresses) : base(singletonInstance, baseAddresses)
    {

    }

protected override void ApplyConfiguration()
            {
                Console.WriteLine("ApplyConfiguration (thread {0})", System.Threading.Thread.CurrentThread.ManagedThreadId);
                base.ApplyConfiguration();
                foreach (ServiceEndpoint endpoint in this.Description.Endpoints)
                {
                    Binding binding = endpoint.Binding;
                    var binding = endpoint.Binding;
                    if(binding is WebHttpBinding)
                    {
                        var web = binding as WebHttpBinding;
                        web.MaxBufferSize = 2000000;
                        web.MaxReceivedMessageSize = 2000000;
                    }
                    var myReaderQuotas = new XmlDictionaryReaderQuotas();
                    myReaderQuotas.MaxStringContentLength = 5242880;
                    binding.GetType().GetProperty("ReaderQuotas").SetValue(binding, myReaderQuotas, null); 
                }
            }

}

The above does override your configuration of each binding and sets the MaxStringContentLength.

public sealed class MyServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory
    {
        public override System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
        {
            return base.CreateServiceHost(constructorString, baseAddresses);
        }

        protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            return new MyServiceHost(serviceType, baseAddresses);
        }
    }

Now my Service.svc file markup has this:

<%@ ServiceHost Language="C#" Debug="true" Factory="Sample.MyServiceHostFactory" Service="Sample.ReaderQuotasService" CodeBehind="ReaderQuotasService.svc.cs" %>
3
Xcalibur On

For the benefit of others, the above answer goes down the right path but has many errors in it and will not work without changes (hence the OP couldn't get the solution to work). Use the following and it will work for you 'out of the box'.

Note - It is extremely important to provide all the below constructors and use the constructor overload demonstrated in the factory, otherwise you will get the following error message:

InitializeRuntime requires that the Description property be initialized. Either provide a valid ServiceDescription in the CreateDescription method or override the InitializeRuntime method to provide an alternative implementation

Custom ServiceHost implementation:

public class CustomServiceHost : ServiceHost
{
    public CustomServiceHost()
    {
    }

    public CustomServiceHost(object singletonInstance, params Uri[] baseAddresses) : base(singletonInstance, baseAddresses)
    {
    }

    public CustomServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses)
    {
    }

    protected override void  OnOpening()
    {
        base.OnOpening();

        foreach (var endpoint in this.Description.Endpoints)
        {
            var binding = endpoint.Binding;
            var web = binding as WebHttpBinding;

            if (web != null)
            {
                web.MaxBufferSize = 2147483647;
                web.MaxReceivedMessageSize = 2147483647;
            }

            var myReaderQuotas = new XmlDictionaryReaderQuotas { MaxStringContentLength = 2147483647 };

            binding.GetType().GetProperty("ReaderQuotas").SetValue(binding, myReaderQuotas, null);
        }
    }
}

Custom ServiceHostFactory implementation:

public sealed class CustomServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        return new CustomServiceHost(serviceType, baseAddresses);
    }
}

The Service1.svc markup above will work as long as the type is correct. I actually used this technique to override WebServiceHost and allow larger JSON response sizes, but the principles should all apply to ServiceHost as well.