Ranting and raving about anything I feel like complaining about.

Controlling Service References in Silverlight 2

Silverlight Logo

I get this question a lot when I teach the Silverlight Tour and when I do talks at conferences and user groups:

Is there a right way to handle web service endpoints in Silverlight 2 when dealing with development and deployment servers?

The problem is that in Silverlight 2 you don't have a web.config file that can be modified as your application moves from development, to testing, to staging to deployment...or do you?

When you make a Service Reference in a Silverlight 2 project, a new project item called the ServiceReference.ClientConfig is added to your Silverlight 2 project. This file (as long winded the name is) contains the configuration for your endpoints and bindings to your web services. I think the idea around this configuration file was that much like ASP.NET: when you move a project across environments, you can simply edit the configuration file to make the change. This is fine but its not quite as trivial since the ServiceReference.ClientConfig file is embedded in the .xap file. Some people are opening the xap file and changing these bindings but I wanted to mention a different strategy...one that was only enabled in Silverlight 2 RTW (I think).

Let's take a small example of a ServiceReference.ClientConfig file for a project with a single service:

<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicEndpoint"
                 maxBufferSize="2147483647"
                 maxReceivedMessageSize="2147483647">
          <security mode="None" />
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:57929/MyTestService.svc"
                binding="basicHttpBinding"
                bindingConfiguration="BasicEndpoint"
                contract="MyServices.MyTestService"
                name="TestEndpoint" />
    </client>
  </system.serviceModel>
</configuration>

When you create a service reference, it creates this file and creates the configuration for the Endpoint as well as the Binding. In earlier versions of Silverlight 2, I would tell people to just use code to create their endpoints manually if they didn't want to edit this file during deployment. But we now have a different option. If you notice there is a name attribute on the endpoint. We can use this name in a new overload for the generated WebClient class for our service. For example:

MyTestServiceClient svc = new MyTestServiceClient("TestEndpoint");

svc.GetNamesCompleted += (snd, args) =>
  {
    if (args.Error != null)
    {
      throw args.Error;
    }
    HtmlPage.Window.Alert(args.Result);
  };

svc.GetNamesAsync("Shawn");

When we create our client proxy class (MyTestServiceClient in this case), we can specify the name of the Endpoint. This means we can pick the Endpoint at runtime instead of having to manually configure your Endpoint and Bindings in code.  For example, in the simple case we could change our ServiceReference.ClientConfig to support a new Endpoint:

<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicEndpoint"
                 maxBufferSize="2147483647"
                 maxReceivedMessageSize="2147483647">
          <security mode="None" />
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:57929/MyTestService.svc"
                binding="basicHttpBinding"
                bindingConfiguration="BasicEndpoint"
                contract="MyServices.MyTestService"
                name="TestEndpoint" />
      <endpoint address="http://WcsServer/MyTestService.svc"
                binding="basicHttpBinding"
                bindingConfiguration="BasicEndpoint"
                contract="MyServices.MyTestService"
                name="RealEndpoint" />
    </client>
  </system.serviceModel>
</configuration>

(NOTE: We could have created a separate binding as well as specified the binding in the bindingConfiguration attribute of the endpoint.)

Now that we have two endpoints, one for testing and one for deployment we can simply create code that knows the difference.  For example:

#if DEBUG
  MyTestServiceClient svc = new MyTestServiceClient("TestEndpoint");
#else
  MyTestServiceClient svc = new MyTestServiceClient("RealEndpoint");
#endif

In this example we simply use the compilation symbol (DEBUG) to determine which endpoint to use.

This technique is just another way of doing this. If your build environment is dynamic, opening the .xap file and editing the ServiceReference.ClientConfig may still be the best option, but this gives you choices

 
 

Comments

Gravatar Nice solution Shawn!
Gravatar setting endpoint.address="http://WcsServer/MyTestService.svc";
does it work?
Gravatar Everett,

Not exactly. You can create the Endpoint and Address manually in code if you prefer...this is an alternative.
Gravatar Hi Shawn,

I'm struggling with this a bit (trying to use it with mojoPortal) and wonder if you have a suggestion to use the System.Web.ApplicationServices.AuthenticationService.

When I add the reference it creates the silverlight proxy and a number of files, AuthenticationService.disco , .wsdl, .xsd etc with a hard coded url to the service in several places including the ServiceReferences.ClientConfig
I need to be able to package mojoPortal along with my silverlight app and have it work without requiring any re-compiling (and ideally without having to change urls in Web.config). Since the services are within the same site as the silverlight app I'd like to be able to use a relative url or else pass in a baseurl as an init param to the silverlight control and then calculate the endpoint path based on the base path and the known relative path.

Is it possible to do this? Having to know the urls at compile time seems very problematic for deploying an app for a third party to install.

Any advice much appreciated.

Best,

Joe Audette
Gravatar Nevermind, I figured out a solution by copying the generated proxy code, then removing the service references and passing a BasicHttpBinding and endpoint to the constructor of the proxy. This way I can set the endpoint calculated from code.
Best,
Joe
Gravatar Joe,

Copying the generated code will work, but its a bit hacky. You shouldn't need to do that. You can set the Endpoint and Binding to the generated code directly.
Gravatar Thanks! This really helped me out.
Gravatar You can also get url of page from
HtmlPage.Document.DocumentUri and assign its host to web service as application starts.

Something like this:

string wsUrl = "http://" + HtmlPage.Document.DocumentUri.Host + "/WebService/Service.asmx";
webService.Endpoint.Address = new System.ServiceModel.EndpointAddress(wsURL);
Gravatar You can use the same tip to do this for Astoria services.
Gravatar Cool John! Good to know.
Gravatar Thanks! Awesome Tip!
Gravatar I use a different approach, and works very well (though yours is nice too). Here it is:


string url = new Uri(Application.Current.Host.Source, "../VBSService.svc").AbsoluteUri;

service = new VBSServiceClient("BasicHttpBinding_VBSService", url);

SO i am just overriding the endpoint address by dinamically adding the base URI

Thanks
Ovi
Gravatar Ovidiu,

Cool...glad to see that works too...
Gravatar Hi Shawn. I tried but could not get it running. Perhaps there could be a simple example?
I tried with Ovidiu Lazar idea and it works now. I do not know how long. Will see. Thanks to both.

Add a Comment

*
*
*