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.
Comments
- Anonymous
November 25, 2010
Thank you, Carlos! This idea is great.I've used it with the following correction: instead of sirect string manipulation I used Uri and UriBuilder functions: void UpdateServiceAddress(ServiceClient wcf) { Uri uriClient = Application.Current.Host.Source; Uri uriWCF = wcf.Endpoint.Address.Uri; UriBuilder ub = new UriBuilder(); ub.Scheme = uriClient.Scheme; ub.Host = uriClient.Host; ub.Port = uriWCF.Port; ub.Path = uriWCF.LocalPath; wcf.Endpoint.Address = new System.ServiceModel.EndpointAddress(ub.Uri); }I believe this is more robust.AV - Anonymous
January 25, 2013
Thank you for this solution - Anonymous
September 04, 2013
thanks for ths code Carlos its really a precious work done by u. got m to the exact solution.:)one thing i wud like to mention that m getting wrong port number if i use your code. but id i use second code getting correct url