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.
<baseAddressPrefixFilters>
<add prefix="http://testsite.com/chat/Services"/>
</baseAddressPrefixFilters>
Then explicitly set the "endpoints for each "site binding" or "base address"
<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.
<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.
<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"
<%@ 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
Public Class MyWebServiceHostFactory
Inherits WebServiceHostFactory
Protected Overrides Function CreateServiceHost(ByVal serviceType As Type, _
ByVal baseAddresses As Uri()) As ServiceHost
Dim myServiceHost As ServiceHost = Nothing
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
Class MyServiceHost
Inherits ServiceHost
Public Sub New(ByVal serviceType As Type, ByVal ParamArray baseAddresses As Uri())
MyBase.New(serviceType, baseAddresses)
End Sub
Protected Overrides Sub ApplyConfiguration()
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()
"
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