Using relative addresses for services in Silverlight applications

One of the biggest complaints about how Add Service Reference works for Silverlight applications is that the address it creates on the client config file (ServiceReferences.ClientConfig) points to the VS "test" web service (Cassini), something like https://localhost:12345/Service1.svc. It works perfectly when you debug on your machine (F5), but when you deploy it elsewhere (e.g., https://www.my.server.com/MyApp) it fails - the client will look for the service on localhost (instead on the real server) and it won't be found.

There are some interesting ideas to overcome this problem. Tim Heuer posted about an approach with "production" versus "staging" endpoints in config at https://timheuer.com/blog/archive/2010/04/05/managing-service-references-in-silverlight-applications-for-different-environments.aspx. This is great, but I was looking for something simpler, where one wouldn't need to change the generated config file. On Silverlight 4, there's a new feature where you can have a relative address on the client address in config (https://blogs.msdn.com/b/silverlightws/archive/2010/04/05/dynamically-updating-proxy-address-for-staging-production.aspx), but again, that involved changing the generated config file.

What I had been using (since Silverlight 2) has worked just fine for me, so I decided to post the solution here. The idea is that, in code, one helper function would take the actual page address (which, for the scenarios where you ASR to a service in the web project, is in the same domain where the .svc file will be), and based on that it will update the address of the client endpoint. The code is shown below (and a full project can be found here.

 public partial class MainPage : UserControl
{
  public MainPage()
{
InitializeComponent();
}
  void UpdateServiceAddress(ServiceReference1.Service1Client client)
{
    string originalAddress = client.Endpoint.Address.Uri.ToString();
AddToDebug("Original address: {0}", originalAddress);
    int svcIndex = originalAddress.IndexOf(".svc");
    int serviceNameIndex = originalAddress.LastIndexOf('/', svcIndex);
    string serviceName = originalAddress.Substring(serviceNameIndex + 1);
AddToDebug("Service name: {0}", serviceName);

string baseAddress = Application.Current.Host.Source.ToString();
baseAddress = baseAddress.Substring(0, baseAddress.LastIndexOf('/')); // removing /App.xap
    baseAddress = baseAddress.Substring(0, baseAddress.LastIndexOf('/')); // removing /ClientBin
    AddToDebug("Base address: {0}", baseAddress);

    string newAddress = baseAddress + "/" + serviceName;
AddToDebug("New address: {0}", newAddress);
client.Endpoint.Address = new System.ServiceModel.EndpointAddress(newAddress);
}
  private void btnClickMe_Click(object sender, RoutedEventArgs e)
{
ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
UpdateServiceAddress(client);
client.AddCompleted += new EventHandler<ServiceReference1.AddCompletedEventArgs>(client_AddCompleted);
client.AddAsync(345, 678);
    AddToDebug("Called AddAsync");
}
  void client_AddCompleted(object sender, ServiceReference1.AddCompletedEventArgs e)
{
AddToDebug("In AddAsync");
    if (e.Error == null)
{
AddToDebug("Result: {0}", e.Result);
}
    else
    {
AddToDebug("Error: {0}", e.Error);
}
}
  void AddToDebug(string text, params object[] args)
{
    if (args != null && args.Length > 0) text = string.Format(text, args);
    this.Dispatcher.BeginInvoke(() => this.txtDebug.Text = this.txtDebug.Text + text + Environment.NewLine);
  }
}

When used in production, you should add some additional error checking to the code above, but the idea should remain the same.