8

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);
        }
    }
James R
  • 651
  • 1
  • 12
  • 21

2 Answers2

9

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.

Xcalibur
  • 3,613
  • 2
  • 32
  • 26
  • I'm not trying to override any of the binding properties, but +1 for the note about constructors and the InitializeRuntime error. It was just what I was looking for! – AdmSteck Jun 06 '14 at 15:51
  • In VB .Net, would I use the .New() keyword for the constructors? – Thomas Dec 08 '14 at 20:21
  • Very helpful. I was trying to use `ApplyConfiguration()` to add endpoint behaviors, but `WebServiceHost` does not create its endpoints until `OnOpening()`, so this got me going. – kenchilada Mar 19 '15 at 17:02
5

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" %>
Rajesh
  • 7,766
  • 5
  • 22
  • 35
  • Thanks Rajesh. This still isn't quite working for me though - for some reason there are no Endpoints when ApplyConfiguration is called - so it never even makes it inside the foreach loop. As the factory is doing all of the description creation, how would you go have it create the endpoint before ApplyConfiguration is fired? – James R Dec 06 '11 at 14:19
  • Is it possible for you to post the factory code you have for providing more help. – Rajesh Dec 06 '11 at 15:54
  • Sure, I've updated the question with the code. The Declaration looks like this: <%@ ServiceHost Language="C#" Debug="true" Service="APINamespace.Service" CodeBehind="Service.svc.cs" Factory="WcfService1.MyWebServiceHostFactory" %> In my code, the APIUsers.TestString is printed out, just to make sure this code was running. The result was here0 (0 = endpoint count) – James R Dec 07 '11 at 12:43
  • Do you have the endpoints defined in web.config on your server side? – Rajesh Dec 07 '11 at 14:09
  • I've updated the question with my Web.config, but the answer is yes. I read somewhere that the servicemodel section is basically ignored when you use a webservicehostfactory though. Also note the declaration is now <%@ ServiceHost Language="C#" Debug="true" Service="APINamespace.SCAPIService" CodeBehind="SCAPIService.svc.cs" Factory="WcfService1.MyWebServiceHostFactory" %> – James R Dec 08 '11 at 10:52
  • I have tried the above code with a Service that has webHttpBinding set and i get the endpoints in the ApplyConfiguration method as said above. Also am adding on how i have used the service host factory code above. – Rajesh Dec 08 '11 at 10:58
  • I finally worked it out. Didn't realise you needed to specify the namespace in the service name, the particular section in web.config needed to be Even though I had messed this up, the factory was auto-adding an endpoint and the service still worked. Thanks for your help mate – James R Dec 08 '11 at 11:07