Calling WCF on the server of origin from Silverlight
Let's say you're building a Silverlight app that uses WCF to talk to the same server that the web page is being served up from. You create your application, test it locally, it all works fine, but you go and deploy it to the server and it doesn't work any more. You look in Fiddler and don't see any traffic at all. If you debug, you may see an exception like this:
The remote server returned an unexpected response: (404) Not Found.
at System.ServiceModel.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.ClientBase`1.ChannelBase`1.EndInvoke(String methodName, Object[] args, IAsyncResult result)
at WCFToLocalServer.ServiceReference1.ServiceClient.ServiceClientChannel.EndDoWork(IAsyncResult result)
at WCFToLocalServer.ServiceReference1.ServiceClient.WCFToLocalServer.ServiceReference1.Service.EndDoWork(IAsyncResult result)
at WCFToLocalServer.ServiceReference1.ServiceClient.OnEndDoWork(IAsyncResult result)
at System.ServiceModel.ClientBase`1.OnAsyncCallCompleted(IAsyncResult result)
Or worse, you get one of these:
System.ServiceModel.CommunicationException: An error occurred while trying to make a request to URI 'http://localhost:15840/WCFToLocalServerWeb/Service.svc'. This could be due to a cross domain configuration error. Please see the inner exception for more details. ---> System.Security.SecurityException ---> System.Security.SecurityException: Security error.
at MS.Internal.InternalWebRequest.Send()
at System.Net.BrowserHttpWebRequest.BeginGetResponseImplementation()
at System.Net.BrowserHttpWebRequest.InternalBeginGetResponse(AsyncCallback callback, Object state)
at System.Net.AsyncHelper.<>c__DisplayClass4.<BeginOnUI>b__3(Object sendState)
--- End of inner exception stack trace ---
at System.Net.AsyncHelper.BeginOnUI(BeginMethod beginMethod, AsyncCallback callback, Object state)
at System.Net.BrowserHttpWebRequest.BeginGetResponse(AsyncCallback callback, Object state)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.CompleteSend(IAsyncResult result)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnSend(IAsyncResult result)
--- End of inner exception stack trace ---
at System.ServiceModel.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.ClientBase`1.ChannelBase`1.EndInvoke(String methodName, Object[] args, IAsyncResult result)
at WCFToLocalServer.ServiceReference1.ServiceClient.ServiceClientChannel.EndDoWork(IAsyncResult result)
at WCFToLocalServer.ServiceReference1.ServiceClient.WCFToLocalServer.ServiceReference1.Service.EndDoWork(IAsyncResult result)
at WCFToLocalServer.ServiceReference1.ServiceClient.OnEndDoWork(IAsyncResult result)
at System.ServiceModel.ClientBase`1.OnAsyncCallCompleted(IAsyncResult result)
What is going on?
The problem is that endpoints in the service client proxy code that gets generated use absolute URIs. This URI is stored in the ServiceReferences.ClientConfig:
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_Service" maxBufferSize="65536"
maxReceivedMessageSize="65536">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:15840/WCFToLocalServerWeb/Service.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_Service"
contract="WCFToLocalServer.ServiceReference1.Service" name="BasicHttpBinding_Service" />
</client>
</system.serviceModel>
</configuration>
This is fine if you're hitting a service like Digg or one of the other public web service APIs out there, but not if you want to be able to test on your own machine and also deploy the same XAP to your server without rebuilding.
You could go ahead and change this to the URI on your server and rebuild, but then it won't work locally for testing unless you have a crossdomain file on your server, and you would be hitting your server's web service, not your local one while testing.
So how do we get around this?
One workaround I've used is to dynamically build the service's URI based on the URI of the page so no matter where the app is running, it will call the right server. The generated proxy client code inherits from System.ServiceModel.ClientBase which has a few other constructors besides the default one which checks the config information to get the endpoint. The one we're interested in is this:
protected ClientBase(Binding binding, EndpointAddress remoteAddress);
You'll have a line something like the following in your app:
ServiceReference1.ServiceClient svc = new WCFToLocalServer.ServiceReference1.ServiceClient();
Replace it with the following:
Uri uri = System.Windows.Browser.HtmlPage.Document.DocumentUri;
string host = uri.AbsoluteUri;
host = host.Substring(0, host.Length - uri.LocalPath.Length);
string servicePath = "/WCFToLocalServerWeb/Service.svc"
string serviceUri = host + servicePath;
ServiceReference1.ServiceClient svc =
new WCFToLocalServer.ServiceReference1.ServiceClient(new System.ServiceModel.BasicHttpBinding(), new System.ServiceModel.EndpointAddress(serviceUri));
Make sure to change the servicePath in this sample to the actual location of your web service. You'll also have to change the object names to match your class names and namespaces generated by the "Add service reference" wizard.
Now no matter where you run the app, whether it's a dynamic port on the Visual Studio web server, or on your local IIS, or on your server, the web service located on the same host that the page is served up from will now get the request. You can check this out in Fiddler and confirm that it's going to the right place.