How to override WebServiceHostFactory MaxReceivedMessageSize?
Asked Answered
H

2

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);
        }
    }
Horatia answered 6/12, 2011 at 9:16 Comment(0)
S
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" %>
Scenery answered 6/12, 2011 at 11:52 Comment(7)
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?Horatia
Is it possible for you to post the factory code you have for providing more help.Scenery
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)Horatia
Do you have the endpoints defined in web.config on your server side?Scenery
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" %>Horatia
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.Scenery
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 <services> <service name="APINamespace.SCAPIService" behaviorConfiguration="ServiceBehaviour"> <endpoint address="" binding="webHttpBinding" bindingConfiguration="rest" contract="APINamespace.ISCAPIService" behaviorConfiguration="web"> </endpoint> </service> Even though I had messed this up, the factory was auto-adding an endpoint and the service still worked. Thanks for your help mateHoratia
H
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.

Hither answered 11/1, 2012 at 4:46 Comment(3)
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!Laine
In VB .Net, would I use the .New() keyword for the constructors?Blinders
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.Reinhart
S
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" %>
Scenery answered 6/12, 2011 at 11:52 Comment(7)
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?Horatia
Is it possible for you to post the factory code you have for providing more help.Scenery
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)Horatia
Do you have the endpoints defined in web.config on your server side?Scenery
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" %>Horatia
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.Scenery
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 <services> <service name="APINamespace.SCAPIService" behaviorConfiguration="ServiceBehaviour"> <endpoint address="" binding="webHttpBinding" bindingConfiguration="rest" contract="APINamespace.ISCAPIService" behaviorConfiguration="web"> </endpoint> </service> Even though I had messed this up, the factory was auto-adding an endpoint and the service still worked. Thanks for your help mateHoratia

© 2022 - 2024 — McMap. All rights reserved.