16

When building an app, it is often deployed in different environments (test, dev, prod), and therefore the endpoint addresses are changing. As the ServiceReferences.ClientConfig is built as a part of Silverlight's .xap file, its hard to change the endpoints after building the solution, as often is done with web.config.

I've searched quite a bit for it, but I cant figure out what is best practice here, so my question is:

What is best practice when it comes to dynamic wcf endpoint address configuration in silverlight?

To clarify, depending on which server the app is on (test,dev, prod) the endpoints change:

  <endpoint
    name="MyService"
    address="http://testserv/MyService.svc"
    binding="basicHttpBinding"
    bindingConfiguration="MybasicHttpBinding"
    contract="MyApp.MyService"
             />

  <endpoint
    name="MyService"
    address="http://prodserv/MyService.svc"
    binding="basicHttpBinding"
    bindingConfiguration="MybasicHttpBinding"
    contract="MyApp.MyService"
             />

In some way, i need the silverlight client to know which one to use, depending on which server its on / which build is compiled.

randoms
  • 2,793
  • 1
  • 31
  • 48
  • Can you clarify, is it your website using services or just the Silverlight client referencing website services? – iCollect.it Ltd Sep 09 '11 at 12:04
  • The silverlight client is using / referencing wcf services that has a dynamic address depending on which server we are on (prod,test,dev). – randoms Sep 09 '11 at 12:09

5 Answers5

27

After reading sLedgem's post, and some googling, I found the perfect solution to make ServiceReferences act like web.config.

First off: Create the different files manually;

ServiceReferences.Debug.ClientConfig
ServiceReferences.Release.ClientConfig

You can add your own as well if you have more than the two default configurations in Visual Studio.

Second: Add the file dependency in the Project.csproj file (Open the project file in a text editor):

  <ItemGroup>
    <None Include="Properties\AppManifest.xml" />
    <Content Include="ServiceReferences.ClientConfig">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="ServiceReferences.Debug.ClientConfig">
      <DependentUpon>ServiceReferences.ClientConfig</DependentUpon>
    </Content >
    <Content Include="ServiceReferences.Release.ClientConfig">
      <DependentUpon>ServiceReferences.ClientConfig</DependentUpon>
    </Content >
  </ItemGroup>

Now, when you reload the project, you will see that ServiceReferences.Release.ClientConfig is expandable in the Solution Explorer, and when you expand it, you will see the Release and Debug file.

Third: Add the Transformation rules to the Project file just before the closing </Project>

(again, open it in a text editor)

<!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
  Other similar extension points exist, see Microsoft.Common.targets.   -->
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('ServiceReferences.$(Configuration).ClientConfig')">
  <!-- Generate transformed ServiceReferences config in the intermediate directory -->
  <TransformXml Source="ServiceReferences.ClientConfig" Destination="$(IntermediateOutputPath)$(TargetFileName).ClientConfig" Transform="ServiceReferences.$(Configuration).ClientConfig" />
  <!-- Force build process to use the transformed configuration file from now on. -->
  <ItemGroup>
    <ServiceReferencesConfigWithTargetPath Remove="ServiceReferences.ClientConfig" />
    <ServiceReferencesConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).ClientConfig">
      <TargetPath>$(TargetFileName).ClientConfig</TargetPath>
    </ServiceReferencesConfigWithTargetPath>
  </ItemGroup>
</Target>

What it does is to look in the corresponding servicereferences file, depending on your configuration, and copy / replace code using the same TransformXML library that web.config uses.

Example:

in my ServiceReferences.ClientConfig i have the following code:

  <endpoint name="ICatalogueService" 
            address="address" 
            binding="basicHttpBinding"
            bindingConfiguration="My_basicHttpBinding" 
            contract="Services.ServiceInterfaces.ICatalogueService"/>

ServiceReferences.Release.ClientConfig:

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.serviceModel>
    <client>
      <endpoint
        name="ICatalogueService"       
        address="http://server/Services/CatalogueService.svc"
        binding="basicHttpBinding"
        bindingConfiguration="My_basicHttpBinding"
        contract="Services.ServiceInterfaces.ICatalogueService"
        xdt:Transform="Replace" xdt:Locator="Match(name)" />
    </client>
    <extensions />
  </system.serviceModel>
</configuration>

As you can see, the endpoint will be replaced, and the match is done on the name attribute.

If you have any questions, let me know :)

randoms
  • 2,793
  • 1
  • 31
  • 48
  • 3
    This is the "awesomest", greatest, most brilliant thing I have ever seen. Exactly what I needed....and I'm using it for my own XML files that hold other types of configuration. Thank you so much for posting this. Works perfectly! +1 – Mike S. Apr 10 '12 at 17:19
6

Great solution to the problem
I could not get the <ItemGroup></ItemGroup> section to work effectively in my solution.
I removed it and added the following script to my Prebuild event in the project:

del $(ProjectDir)ServiceReferences.ClientConfig;
copy $(ProjectDir)ServiceReferences.$(ConfigurationName).ClientConfig $(ProjectDir)ServiceReferences.ClientConfig;
Yossi
  • 136
  • 2
  • 7
3

You can do it during runtime by using the constructor of the WCF client in SL that takes endpoint configuration name and the address. The endpoint configuration name is just "MyService" in your example. The address argument you provide will override the one included in ClientConfig.

One of the ways to calculate the address of your service during runtime from SL is (I don't guarantee it will work in every environment configuration):

  1. Calculate the root of your site, e.g. by finding the common part of Application.Current.Host.Source.AbsoluteUri and HtmlPage.Document.DocumentUri.AbsoluteUri. Basically, you take characters from the beginning of the shorter path as long as they match case-insensitively characters in the other path.
  2. Append relative path to services if any (it doesn't appear to be the case here).
  3. Append MyService.svc

Extra Info:

This may look complicated when you have many services, but it all can be nicely refactored and with a help of Unity made pretty easy to use for any service. For example, I use a helper function which registers a service client with and it's call looks like this: ServicesHelper.RegisterService<MyServiceContractClient, IMyServiceContract>( "MyService" ); When I need to create an instance of the service client I just resolve MyServiceContractClient type with Unity which uses an injection constructor to create a new instance of my service already properly configured. It can also handle HTTPS situation. Let me know if you need more information on any of that.

tdracz
  • 1,334
  • 1
  • 9
  • 4
  • that could work, but in some settings the wcf serivces and the silverlight application is not located on the same server. If that was the case i could just use relative paths like address="../MyService/service.svc" i think? – randoms Sep 13 '11 at 11:30
  • Do you mean you use cross-domain calls? If not and you just host WCF services and SL application in the same web application then the above solution will work even if you access your application from a different server than it is hosted on. – tdracz Sep 13 '11 at 13:44
  • Btw, when you use SL 4, you can use relative paths: [see here](http://stackoverflow.com/questions/4136802/servicereferences-clientconfig-changing-values-silverlight) – tdracz Sep 14 '11 at 15:57
  • My WCF service will always be local to the Silverlight XAP, so this solution is ideal for me. Having to pull apart the .xap and change the ServicesReferences.ClientConfig, or even using web transforms, really violates the basic principles of automated builds and deployments. – David Keaveny Oct 08 '14 at 21:54
1

randoms' response is spot on except for ONE little thing. Don't mark the .Debug.ClientConfig and .Release.ClientConfig as "Content". Mark them as "None". That way your .Debug.ClientConfig and .Release.ClientConfig aren't put into your .xap file. Here's what's in my Silverilght project file (and it works great):

<Content Include="ServiceReferences.ClientConfig">
  <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="ServiceReferences.DEV.ClientConfig">
  <DependentUpon>ServiceReferences.ClientConfig</DependentUpon>
</None>
<None Include="ServiceReferences.TEST.ClientConfig">
  <DependentUpon>ServiceReferences.ClientConfig</DependentUpon>
</None>
<None Include="ServiceReferences.PROD.ClientConfig">
  <DependentUpon>ServiceReferences.ClientConfig</DependentUpon>
</None>
theBoringCoder
  • 233
  • 2
  • 14
1

Have a look here:

http://weblogs.asp.net/srkirkland/archive/2009/10/13/common-web-config-transformations-with-visual-studio-2010.aspx

then here

http://www.funkymule.com/post/2010/03/08/XML-Transform-on-Silverlight-ClientConfig-Files.aspx

It uses the same principle behind the web.config transformations (ie, web.config is changed depending on what configuration you are compiling (ie release, debug) so that the serviceref.config is changed according to your whim upon compile time. works a charm

sLedgem
  • 501
  • 2
  • 11