9

How can I correctly serve WSDL of a WCF webservice located in a private LAN from behind a reverse proxy listening on public IP?

I have an Apache webserver configured in reverse proxy mode which listens for requests on a public IP address and serves them from the internal IIS host. WCF webservice generates WSDL using the FQDN address of the LAN host which, of course, cannot be read by an internet web service client.

Is there any setting that can be configured in wcf application's web.config or in IIS in order to customize the WSDL generated containing host address and put public address instead?

Robert Mircea
  • 699
  • 2
  • 10
  • 18

3 Answers3

11

Add the following attribute to your service class:

<ServiceBehavior(AddressFilterMode:=AddressFilterMode.Any)>

This allows the service to be addressed by the client as https://... but the service can still be hosted on http://.....

See my answer on How to specify AddressFilterMode.Any declaratively for how to create an extension to allow AddressFilterMode.Any to be specified through configuration without requiring code attributes.

In the web.config of the service host, the endpoint element must have an absolute URL in the address attribute that is the public URL that will be used by the client. In the same endpoint element, set the listenUri attribute to the absolute URL on which the service host is listening.

The way I determine what the default absolute URI the host is listening on is to add a service reference in a client application which points the the physical server where the service is hosted. The web.config of the client will have an address for the service. I then copy that into the listenUri attribute in the hosts web.config.

In your service behavior configuration, add the element serviceMetaData with attribute httpGetEnabled=true

So you'll have something like this:

<serviceBehaviors>
  <behavior name="myBehavior">
    <serviceMetadata httpGetEnabled="true" />
  </behavior>
</serviceBehaviors>
<!--  ... -->
<services>
  <service name="NamespaceQualifiedServiceClass" behavior="myBehavior" >
    <endpoint listenUri="http://www.servicehost.com" 
              address="https://www.sslloadbalancer.com" 
              binding="someBinding" 
              contract="IMyServiceInterface" ... />
  </service>
</services>

I am not sure if this works with message security or transport security. For this particular application, the credentials were passed as part of the DataContract so we had basicHttpBinding > security > mode=none. Since the transport is secure (to the ssl load balancer) there were no security issues.

It is also possible in to leave the listenUri attribute blank, however it must be present.

Unfortunately, there is a bug in WCF where the the base address of imported schemas in the WSDL have the listenUri base address rather than the public base address (the one configured using the address attribute of the endpoint). To work around that issue, you need to create an IWsdlExportExtension implementation which brings the imported schemas into the WSDL document directly and removes the imports.

An example of this is provided in this article on Inline XSD in WSDL with WCF. Additionally you can have the example class inherit from BehaviorExtensionElement and complete the two new methods with:

Public Overrides ReadOnly Property BehaviorType() As System.Type
    Get
        Return GetType(InlineXsdInWsdlBehavior)
    End Get
End Property

Protected Overrides Function CreateBehavior() As Object
    Return New InlineXsdInWsdlBehavior()
End Function

This will allow you to add an extension behavior in the .config file and add the behavior using configuration rather than having to create a service factory.

Under the system.servicemodel configuration element add:

<behaviors>
  <endpointBehaviors>
    <behavior name="SSLLoadBalancerBehavior">          
      <flattenXsdImports/>
    </behavior>
  </endpointBehaviors>
</behaviors>
<extensions>
  <behaviorExtensions>
    <!--The full assembly name must be specified in the type attribute as of WCF 3.5sp1-->
    <add name="flattenXsdImports" type="Org.ServiceModel.Description.FlattenXsdImportsEndpointBehavior, Org.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>        
  </behaviorExtensions>
</extensions>

And then reference the new endpoint behavior in your endpoint configuration using the behaviorConfiguration attribute

<endpoint address="" binding="basicHttpBinding" contract="WCFWsdlFlatten.IService1" behaviorConfiguration="SSLLoadBalancerBehavior">
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Richard Collette
  • 5,462
  • 4
  • 53
  • 79
  • 1
    I just want to note that this answer is based on WCF 3.5. I have not had an opportunity to examine if WCF 4.0 corrects some of these issues. I do know that some enhancements were made to better support reverse proxy scenarios. – Richard Collette Nov 02 '10 at 13:29
  • I've tried this and it works, but it has a few problems: you'll have to put all your types, contracts and bindings in the same namespace and if you have types named like FooRequest you'll have to rename them because wcf will generate a type with the same name. For me it was more convenient to manually edit the wsdl files, otherwise the users of my service would have had to change their code. – Dutch Nico May 30 '11 at 11:10
  • @Dutch - My solution will typically have a business object project and a service project, each with different namespaces (ex. org.app.bo and org.app.servicemodel). Data from business objects are copied into service objects and vice versa. I use interfaces defined in the service project to define the contracts (org.app.servicemodel.ISomeService). The service methods usually take a org.app.servicemodel.SomethingRequest type as a parameter and return a org.app.servicemodel.SomethingResponse type. I have not run into naming conflicts using this type of setup. Are you donig something else? – Richard Collette Jun 01 '11 at 21:46
  • Initially I used separate xml-namespaces for my messages, contract and binding. I all put them in the same namespace because inlining didn't work. After that change I ran into naming conflicts like this: [link](http://geekswithblogs.net/LeonidGaneline/archive/2008/06/02/wcf-data-contract-names-dont-use-names-with-response-suffix.aspx) – Dutch Nico Jun 08 '11 at 09:36
  • OK, now I understand the difference. I add my own versioned namespaces based on some best practices documents I have read. That is probably why I have not run into the issue of using SomethingRequest and SomethingResponse types. See versioning guidance http://blogs.msdn.com/b/craigmcmurtry/archive/2006/07/23/676104.aspx and http://msdn.microsoft.com/en-us/library/ms733832.aspx – Richard Collette Jun 10 '11 at 04:55
  • The bug for imported WSDL having the wrong base address was fixed in .NET 3.5 SP1 and .NET 4.0. You can use the following configuration elements to get the proper behavior. – Richard Collette Oct 10 '11 at 14:47
  • +1 for "It is also possible in to leave the listenUri attribute blank, however it must be present." That was my issue. – JamesQMurphy Apr 02 '15 at 04:28
1

I'm having similar issues, one of which was the resolution of public and server addresses. This solved that issue although I still have a couple authentication problems.

See: How to change HostName in WSDL for an IIS-hosted service? by Wenlong Dong

archive

KyleMit
  • 30,350
  • 66
  • 462
  • 664
stephenl
  • 3,119
  • 4
  • 22
  • 22
0

See: Service Station WCF Addressing In Depth by Aaron Skonnard

archive link

KyleMit
  • 30,350
  • 66
  • 462
  • 664
Nicolas Dorier
  • 7,383
  • 11
  • 58
  • 71
  • 3
    That content appears to have moved to http://msdn.microsoft.com/en-us/magazine/cc163412.aspx . – JohnW Dec 09 '09 at 02:59