Hosting WCF services in IIS: Fix URLs of WSDL files that include internal domain or machine names

If you host your WCF service in the IIS web server and have the problem, that the URLs of the WSDL files contain internal domain names (or your machine names) that cannot be resolved by the clients when trying to consume the service you will find three possible options to resolve this issue in this blog post:

  1. Use the IIS manager to add a hostname to the binding of the site that hosts the WCF service.
  2. Set to multipleSiteBindingsEnabled attribute (see MSDN documentation for more information) that was introduced in .NET 4 to true in the Web.config:
    <system.serviceModel>
      [...]
      <serviceHostingEnvironment [...] multipleSiteBindingsEnabled="true"/>
    </system.serviceModel>
  3. Include the WcfWsdlUrlFixHttpModule (see source code below) in your project and activate the HTTP module in the Web.config as follows:
     
    <system.web>
      [...]
      <httpModules>
        [...]
        <add name="WcfWsdlUrlFixHttpModule" type="YourNamespace.WcfWsdlUrlFixHttpModule, YourAssembly"/>
      </httpModules>
    </system.web>
     
    <system.webServer>
      [...]
      <modules>
        [...]
        <add name="WcfWsdlUrlFixHttpModule" type="YourNamespace.WcfWsdlUrlFixHttpModule, YourAssembly"/>
      </modules>
    </system.serviceModel>

    The WcfWsdlUrlFixHttpModule is defined as follows:

    namespace YourNamespace
    {
      public class WcfWsdlUrlFixHttpModule: IHttpModule
      {
        public void Dispose() {}
     
        void IHttpModule.Init(HttpApplication context)
        {
          context.BeginRequest += ContextBeginRequest;
        }
     
        static void ContextBeginRequest(object sender, EventArgs e)
        {
          var app = sender as HttpApplication;
          if (app != null && app.Request.Url.LocalPath.EndsWith(".svc"))
          {
            app.Response.Filter = new WcfWsdlUrlFixFilter(app.Response.Filter);
          }
        }
      }
     
      internal class WcfWsdlUrlFixFilter : Stream
      {
        private static readonly Regex Regex = new Regex(@"(http)[^\s^\>^\'^\""]*?(\.svc)");
        private readonly Stream _responseStream;
     
        public WcfWsdlUrlFixFilter(Stream responseStream)
        {
          _responseStream = responseStream;
        }
     
        public override bool CanRead
        {
          get { return _responseStream.CanRead; }
        }
     
        public override bool CanSeek
        {
          get { return _responseStream.CanSeek; }
        }
     
        public override bool CanWrite
        {
          get { return _responseStream.CanWrite; }
        }
     
        public override void Flush()
        {
          _responseStream.Flush();
        }
     
        public override long Length
        {
          get { return _responseStream.Length; }
        }
     
        public override long Position
        {
          get { return _responseStream.Position; }
          set { _responseStream.Position = value; }
        }
     
        public override int Read(byte[] buffer, int offset, int count)
        {
          return _responseStream.Read(buffer, offset, count);
        }
     
        public override long Seek(long offset, SeekOrigin origin)
        {
          return _responseStream.Seek(offset, origin);
        }
     
        public override void SetLength(long value)
        {
          _responseStream.SetLength(value);
        }
     
        public override void Write(byte[] buffer, int offset, int count)
        {
          var content = System.Text.Encoding.UTF8.GetString(buffer);
     
          // Create URL to .svc "file" based on the current request's RawUrl
          var request = HttpContext.Current.Request;
          var url = string.Format(
            "{0}://{1}{2}{3}",
            request.Url.Scheme,
            request.Url.Host,
            (request.Url.Port != 80 ? string.Format(":{0}", request.Url.Port.ToString(CultureInfo.InvariantCulture)) : string.Empty),
            request.RawUrl.Split('?').First()
          );
     
          content = Regex.Replace(content, url);
          buffer = System.Text.Encoding.UTF8.GetBytes(content);
          _responseStream.Write(buffer, 0, buffer.Length);
        }
      }
    }

    In detail, the WcfWsdlUrlFixHttpModule uses a regular expression (thanks to my colleague Sascha for the support) to modify all URLs in the service description that point to WSDL files according to the schema, hostname and path the client uses in its request before the response is send to the client. This solution may seems to be a dirty hack, but it works and in contrast to the other options it takes rewrite rules into consideration that hide the real location of the .svc file.

    For example the following configuration for the IIS “URL Rewrite” extension could be used to host a WCF service under the URL http://your-domain.com/wcf/Service.svc instead of its real location http://your-domain.com/path/to/wcfservice/Service.svc:

    <system.webServer>
      [...]
      <rewrite>
        <rules>
          <rule name="Wcf Rewrite" stopProcessing="true">
            <match url="wcf/(.*)"/>
            <action type="Rewrite" url="/path/to/wcfservice/{R:1}"/>
          </rule>
        </rules>
      </rewrite>
    </system.webServer>

    By default, when the client requests the URL http://your-domain.com/wcf/Service.svc the URL to the WSDL files will be http://your-domain.com/path/to/wcfservice/Service.svc?wsdl, but when using the WcfWsdlUrlFixHttpModule as described above the URLs to the WSDL file will point to the “/wcf/” path (e.g. http://your-domain.com/wcf/Service.svc?wsdl) and completely hides the real directory structure.

Leave a comment

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

*