Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

WCF Service Hosted on any binding, on Any Hosted Domain

5.00/5 (1 vote)
15 Feb 2015CPOL3 min read 9.5K  
Out of frustration comes simplicity ...

Introduction

I have a WCF service and all works fine on the local development machine, service is defined in the web.config.

Then I have to "deploy" the service to the "production test web site". That's where the fun began ... the system is a VB.NET 3.5 umbrella of web sites developed using VS2008 that all use the same code base ... so I couldn't take advantage of the .NET4 WCF enhancements.

Background

So it starts like any other project ... boss says "we need a web chat" for our web site(s) ... in that process, I dig around find a bunch of examples and mash-up a "web chat system" using WCF, LINQ, JSON, MSSQL.

All is working great on my local development environment and now it's time to "MOVE" out to the "test web server" for the "dog and pony show" to demo the "chat system" to the rest of the "company".

Using the Code

The "production test web site" had two site bindings:

  • www.testsite.com and
  • testsite.com

It caused the "WCF Reporting Error: This collection already contains an address with scheme http." which we all know can correct with a few lines in the web.config file.

XML
<baseAddressPrefixFilters>
  <add prefix="http://testsite.com/chat/Services"/>
</baseAddressPrefixFilters>

Then explicitly set the "endpoints for each "site binding" or "base address"

XML
<endpoint address="http://www.testsite.com/chat/Services/chatServices.svc" .../>
<endpoint address="http://testsite.com/chat/Services/chatServices.svc" .../>

which now "allows" the service to run on both "site bindings" for the "test website".

OK, now the test site runs fine, but now that same "set of code" also can be deployed on another "domain".

Let's call that domain www.othertestsite.com and othertestsite.com.

XML
<baseAddressPrefixFilters>
  <add prefix="http://othersite.com/chat/Services"/>
</baseAddressPrefixFilters>

<endpoint address="http://www.othersite.com/chat/Services/chatServices.svc" .../>
<endpoint address="http://othersite.com/chat/Services/chatServices.svc" .../>

So, if I wanted to deploy the chat to the new domain after copying the files up, I would have to change the "baseAddressPrefixFilters" and the "endpoint address" to now match my new domain ... not horrible but the "code" is a base for "5" different but related web sites that share lots of code between them. Deployment to all the different sites was going to be a "web.config" nightmare.

I needed a way to change the "items" above so I decided to just change the values in the web.config file at runtime to use the "host the site was running under" to set the values in the web.config file worked like a charm but ...

I use a common "directory" for the testsite.com and the othersite.com each "domain" was overwriting the other "domains" updates to the "common" web.config file so that didn't work out as expected ...

The next idea was "custom" service host factory, but I needed to "keep" the web.config intact and use it as the "base settings for the WCF chat service ... so to make a long story longer here is how I handled the "multiple endpoint, multiple deployment domains" problem

So, the final "solution" is shown below.

The base "<system.serviceModel>" setup in the "web.config" file has "no addresses defined" this lets me set up my endpoint and service behaviors and any other "static" WCF options.

XML
<system.serviceModel>
  <behaviors>
    <endpointBehaviors>
      <behavior name="crmWebClient.chatServicesAspNetAjaxBehavior">
        <enableWebScript />
      </behavior>
    </endpointBehaviors>
    <serviceBehaviors>
      <behavior name="crmWebClient.chatServicesAspNetAjaxBehavior">
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" />
      </behavior>
    </serviceBehaviors>
  </behaviors>

  <serviceHostingEnvironment aspNetCompatibilityEnabled="true">
  </serviceHostingEnvironment>

  <services>
    <service behaviorConfiguration="crmWebClient.chatServicesAspNetAjaxBehavior"
  name="crmWebClient.chatServices">
      <endpoint address=""
      behaviorConfiguration="crmWebClient.chatServicesAspNetAjaxBehavior"
      binding="webHttpBinding"
      contract="crmWebClient.chatServices" />
    </service>
  </services>

I added a "service factory to my code" Factory="crmWebClient.MyWebServiceHostFactory"

ASP.NET
<%@ ServiceHost Language="VB" 
Debug="true" Service="crmWebClient.chatServices" 
    CodeBehind="chatServices.svc.vb" 
    Factory="crmWebClient.MyWebServiceHostFactory" %>

Then, I created "MyWebServiceHostFactory" and "MyServiceHost" to handle the "creation" of the service at run time since it's a WCF service that gets used by JSON ... that's the reason for using the "WebServiceHostFactory".

MyWebServiceHostFactory

VB.NET
Public Class MyWebServiceHostFactory
  Inherits WebServiceHostFactory

  Protected Overrides Function CreateServiceHost(ByVal serviceType As Type, _
  ByVal baseAddresses As Uri()) As ServiceHost

    'All the custom factory does is return a new instance of our custom host class. 
    'The bulk of the custom logic should
    'live in the custom host (as opposed to the factory) 
    'for maximum reuse value outside of the IIS/WAS hosting environment.
    Dim myServiceHost As ServiceHost = Nothing
    ' loop through the addresses and see which one contains our current host name
    ' the one that matches is the one we use

    For Each baseAddress As Uri In baseAddresses
      If HttpContext.Current.Request.ServerVariables.Item_
      ("SERVER_NAME").ToString.ToLower.Trim = baseAddress.Host.ToLower.Trim Then
        myServiceHost = New MyServiceHost(serviceType, baseAddress)
        Exit For
      End If
    Next

    Return myServiceHost

  End Function

End Class

MyServiceHost

VB.NET
Class MyServiceHost
  Inherits ServiceHost

  Public Sub New(ByVal serviceType As Type, ByVal ParamArray baseAddresses As Uri())

    MyBase.New(serviceType, baseAddresses)

  End Sub

  'Overriding ApplyConfiguration() allows us to alter the 
  'ServiceDescription prior to opening the service host.

  Protected Overrides Sub ApplyConfiguration()
    ' First, we call base.ApplyConfiguration() to read 
    ' any configuration that was provided for the service we're hosting.
    ' After this call,Me.ServiceDescription describes the service as it was configured.
    MyBase.ApplyConfiguration()

  End Sub

End Class

This allows the "WCF" service to be "deployed" on any of my "domains" and no web.config changes are needed.

The service "creates itself" based on any "host header addresses" defined in IIS so if the site has 4 host headers, it will run the service on any of them even if I have a "shared" code directory each "domain" will generate its own service instance on its domain using the "base" settings in the web.config file...

As a currently working real world example (only site names were changed), maybe "one" developer might find the article useful ...

Points of Interest

After days of searching the web, looking at examples and blogs, and even other "Code Project" examples, none of the articles went far enough or went the "wrong" direction with solving the problem ...

If you want to control "endpoint" creation based on UriScheme, you can add this code "after MyBase.ApplyConfiguration()"

VB.NET
For Each baseAddress As Uri In Me.BaseAddresses
  If baseAddress.Scheme = Uri.UriSchemeHttp Then
  ElseIf baseAddress.Scheme = Uri.UriSchemeHttps Then
  ElseIf baseAddress.Scheme = Uri.UriSchemeNetPipe Then
  ElseIf baseAddress.Scheme = Uri.UriSchemeNetTcp Then
  End If
Next

History

  • Posted on 2/14/2015

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)