Fixing ?disco URL ref and docRef attributes for asmx service

Written by Troy on August 23, 2016 Categories: Uncategorized Tags: , , , , , , ,

So I had to touch an old .asmx service (byarhg) making minimal changes. So that I could keep the old service running (in case of breakage) the new one is hosted on a different and non-standard port number. This causes that old issue, that when the host sits behind a load balancer, poor old ASP.Net is none the wiser and sticks the port number in the generated WSDL URL (e.g. http://loadbalancer.url/myService.asmx?wsdl)

  <wsdl:service name="Service">
    <wsdl:port name="ServiceSoap" binding="tns:ServiceSoap">
      <soap:address location="http://loadbalancer.url:1234/myService.asmx" />
    </wsdl:port>
    <wsdl:port name="ServiceSoap12" binding="tns:ServiceSoap12">
      <soap12:address location="https://loadbalancer.url:1234/myService.asmx" />
    </wsdl:port>
  </wsdl:service>

But that is easy to solve using the <soapExtensionReflectorTypes> configuration (under <webServices>) and providing an implementation of a SoapExtensionReflector. There are examples online (and this is very dated). That fixes the ?wsdl URL, but ?disco URL is left in a half-fixed state:

<?xml version="1.0" encoding="utf-8"?>
<discovery xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.xmlsoap.org/disco/">
  <contractRef ref="https://loadbalancer.uri:1234/Service.asmx?wsdl" docRef="https://loadbalancer.uri:1234/Service.asmx" xmlns="http://schemas.xmlsoap.org/disco/scl/" />
  <soap address="http://loadbalancer.uri/myService.asmx" xmlns:q1="http://foo.com/bar/" binding="q1:ServiceSoap" xmlns="http://schemas.xmlsoap.org/disco/soap/" />
  <soap address="http://loadbalancer.uri/myService.asmx" xmlns:q2="http://foo.com/bar/" binding="q2:ServiceSoap12" xmlns="http://schemas.xmlsoap.org/disco/soap/" />
</discovery>

Note that for the <contractRef> element, the ref and docRef attributes have the port number of the backend web service. If you have tools (or people) who simply put in the URL of the service (that is, without ?wsdl on the end) this will cause issues (for instance, using the New-WebServiceProxy Powershell cmdlet or even in Visual Studio) – they will attempt to request the WSDL at the wrong URL. This was the case for me, meaning this was a breaking change! How to fix? I searched for ways to do it with the SoapExtensionReflector or otherwise, including using ILSpy to inspect the framework classes looking for hacks. But alas, there is no way to do it.

To solve it, I instead wrote a HTTP Module to adjust the generated discovery file, using a ‘Filter’ stream. The filter stream can adjust data being written to the response. A nifty feature I had not used until now.

HTTP Module:

    public class DiscoFixHttpModule : IHttpModule
    {
        private static bool IsDisco(HttpApplication context)
        {
            return IsDisco(context.Request.Url);
        }

        private static bool IsDisco(Uri uri)
        {
            return string.Equals(uri.Query, "?disco", StringComparison.OrdinalIgnoreCase);
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += HandleBeginRequest;
        }

        void HandleBeginRequest(object sender, EventArgs e)
        {
            HttpApplication context = (HttpApplication)sender;
            if (!IsDisco(context))
            {
                return;
            }
            context.Response.Filter = new DiscoFixFilter(context.Response.Filter);
        }
    }

My ‘filter’ (stream):

    public class DiscoFixFilter : Stream
    {
        private readonly Stream output;
        private static readonly string trimPattern = ConfigurationManager.AppSettings["TrimWsdlPortUriPattern"];

        public DiscoFixFilter(Stream outputStream)
        {
            output = outputStream;
        }

        private static string RemovePortEvaluator(Match match)
        {
            return Regex.Replace(match.Value, @"\:\d+", string.Empty);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            string data = Encoding.UTF8.GetString(buffer, offset, count);
            data = Regex.Replace(data, trimPattern, RemovePortEvaluator);
            buffer = Encoding.UTF8.GetBytes(data);
            output.Write(buffer, 0, buffer.Length);
        }
    }

Add the entry for the module, and I have the regular expression in my web.config file in case it needs to accommodate different URLs.

...
  <system.webServer>
    <modules>
      <add name="DiscoFix" type="Foo.Bar.DiscoFixHttpModule, Foo.Bar"/>
    </modules>
  </system.webServer>
...
  <appSettings>
    <add key="TrimWsdlPortUriPattern" value="\bhttp:\/\/loadbalancer(?:qa|test)?\.uri\:\d+\/"/>
  </appSettings>

And that is enough to fix my ?disco URL:

<?xml version="1.0" encoding="utf-8"?>
<discovery xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.xmlsoap.org/disco/">
  <contractRef ref="https://loadbalancer.uri/myService.asmx?wsdl" docRef="https://loadbalancer.uri/myService.asmx" xmlns="http://schemas.xmlsoap.org/disco/scl/" />
  <soap address="http://loadbalancer.uri/myService.asmx" xmlns:q1="http://foo.com/bar/" binding="q1:ServiceSoap" xmlns="http://schemas.xmlsoap.org/disco/soap/" />
  <soap address="http://loadbalancer.uri/myService.asmx" xmlns:q2="http://foo.com/bar/" binding="q2:ServiceSoap12" xmlns="http://schemas.xmlsoap.org/disco/soap/" />
</discovery>

No more port numbers.

No Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>