Team LiB
Previous Section Next Section

Supporting Dynamic URLs

One of the ways in which a great deal of sample code falls short of demonstrating how web services are consumed in professional applications is that the samples typically show a hard-coded service URL. Even if the web service being consumed is never scheduled to change its URL, it is possible that domain names could change, the file name of the service could change, and even more likely, the server will change depending on whether you are consuming a test service or a production service. Another possibility is that different vendors provide services that conform to the same contract. Assume, for the sake of example, that three different vendors provide a news publication service. You are building an application that can consume news from any service that conforms to the appropriate WSDL contract. The only way to accomplish that is through the use of dynamic URLs. In other words, real-world applications don't hard-code web service URLs; they enable users to enter them through the GUI, configuration files, database tables, and so on.

Storing URLs in app.config

One of the ways in which your application can support dynamic URLs is by providing the URL in the application's configuration file. In the case of a WinForms application, that is the app.config file. (Remember that an app.config file is automatically renamed and copied to the deployment directory when the application is built in Visual Studio .NET.)

To see how the storage of a URL works in the app.config file, highlight the web reference in the solution explorer with the Properties window open. You will see a property called URL Behavior. This is set to Static by default. Change it to dynamic, and you will see that a change has been made to your app.config file. The following few lines show the new addition to the file:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="HelloConsumer.HelloService.Hello"
      value="http://localhost/HelloService/Hello.asmx"/>
  </appSettings>
</configuration>

Instead of using the URL that you supplied when you first discovered the web service, your application is now dynamically loading the URL from the app.config at runtime, using that value as the default. If you're not convinced, go ahead and change the URL in this file to something nonexistent and then run the application to see the results. As you might expect, the application throws an exception when it tries to communicate with the bogus URL you supplied in the app.config file.

Storing URLs in Isolated Storage

Another option for storing the URL is a very powerful, but often ignored, method of storing configuration and user options data. Although information can be stored in the app.config file, that file is read-only and can be easily located and edited. In addition, that file only has one scope: the application. If you want to allow different users to access different URLs, you would need to come up with a very complex and custom scheme for storing information in the app.config file that might confuse users.

Enter isolated storage. Isolated storage is a concept that allows for scoped, isolated, secure, quota-limited storage of arbitrary files. The beauty of the system is that not only can you easily differentiate between files stored for different users as well as applicationwide data files, but the physical location of those files is completely irrelevant to your application. It doesn't make any difference to you where the isolated storage files are being stored. All you have to do is use the API methods and you get all that functionality for free.

The following code demonstrates how you could store a URL in an XML document. This technique can be adapted to use with any kind of file you like as long as it can be represented as a stream.

The concept of isolated storage is beyond the scope of this chapter. Simply put, it is a sandbox for files separated by AppDomain, user, machine, or all of these. In the case of this sample, it is merely a tool to further show ways to enhance your web service consumers. The basic idea is that stores can be specific to your application, or they can be specific to your application and each user within your application. Within a store, you can create, update, and delete files. This store is configurable and managed by a computer administrator, and that administrator can do things such as specify a maximum disk size for each store, and so forth. Think of a store as a virtual folder and the files within that store as virtual files. You don't have to concern yourself with the physical implementation of isolated storage, just its stream-based API.

TIP

Isolated storage is not the only system within the .NET Framework that relies on streams. In fact, everywhere in the Framework where you would think a stream might be an option as a parameter, it is. The more comfortable you are with a streams-based programming model, the shorter your road to .NET mastery.


This example adds a text box and two buttons to the main form. The first button is for retrieving the URL from isolated storage, and the second button is for setting the URL in isolated storage. The code in Listing 18.2 shows the event handlers for both buttons, including all the isolated storage code.

Listing 18.2. The Isolated Storage Retrieval and Update Code for an XML Document
private void btnGetURL_Click(object sender, System.EventArgs e)
{
  IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(
  IsolatedStorageScope.Assembly | IsolatedStorageScope.User, null, null );
  string[] fileNames = isoStore.GetFileNames( "WebServiceConfig.xml" );
  if (fileNames.Length == 0)
  {
    // file wasn't found, create it.
    IsolatedStorageFileStream isoFS =
      new IsolatedStorageFileStream( "WebServiceConfig.xml",
    FileMode.Create, isoStore );
    XmlDocument doc = new XmlDocument();
    string defaultXml =
      @"<configuration><serviceUrl>" +
      "http://localhost/HelloService/Hello.asmx</serviceUrl></configuration>";
    doc.LoadXml(defaultXml);
    doc.Save( isoFS );
    isoFS.Close();
    tempURL = @"http://localhost/HelloService/Hello.asmx";
  }
  else
  {
    // load from existing file
    IsolatedStorageFileStream isoFS =
      new IsolatedStorageFileStream( "WebServiceConfig.xml",
      FileMode.Open, isoStore );
    XmlDocument doc = new XmlDocument();
    doc.Load( isoFS );
    isoFS.Close();
    XmlNode node = doc.SelectSingleNode("//serviceUrl");
    tempURL = node.InnerText;
  }
  MessageBox.Show(this, "URL loaded from options data: " + tempURL , "Options Data"); 
}

private void btnSetURL_Click(object sender, System.EventArgs e)
{
  IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(
  IsolatedStorageScope.Assembly | IsolatedStorageScope.User, null, null );
  string[] fileNames = isoStore.GetFileNames( "WebServiceConfig.xml" );
  tempURL = textBox1.Text;
  if (fileNames.Length ==0 )
  {
    //file doesn't exist, create it.
    IsolatedStorageFileStream isoFS =
      new IsolatedStorageFileStream( "WebServiceConfig.xml",
      FileMode.Create, isoStore );
    XmlDocument doc = new XmlDocument();
    string defaultXml =
      @"<configuration><serviceUrl></serviceUrl></configuration>";
    doc.LoadXml(defaultXml);
    doc.SelectSingleNode("//serviceUrl").InnerText = tempURL;
    doc.Save( isoFS );
    isoFS.Close();
  }
  else
  {
    IsolatedStorageFileStream isoFS =
      new IsolatedStorageFileStream( "WebServiceConfig.xml",
      FileMode.Open, isoStore );
    XmlDocument doc = new XmlDocument();
    doc.Load( isoFS );
    XmlNode node = doc.SelectSingleNode("//serviceUrl");
    node.InnerText = tempURL;
    isoFS.Close();
    isoFS = new IsolatedStorageFileStream( "WebServiceConfig.xml",
    FileMode.Create, isoStore );
    doc.Save( isoFS );
    isoFS.Close();
  }
  MessageBox.Show(this, "URL written to Isolated Storage.");
 }
}

Running the preceding sample and clicking the button to retrieve the URL from isolated storage produces the dialog shown in Figure 18.3.

Figure 18.3. Retrieving the dynamic web service URL from isolated storage.


As you can see, this method obviously requires more code than the app.config method. However, you should be able to see the power in using isolated storage to store files that belong to specific users of your application or files that belong to everyone who uses your application. As mentioned earlier, the files you can place in isolated storage are completely arbitrary. You can easily store the XML serialization of a DataSet in isolated storage, giving you full data-binding functionality. When you are ready to save the changes, all you have to do is open an output stream as in the preceding code and save the changes to the DataSet.

Dynamic URLs via UDDI Consumption

Another way to get access to dynamically changing URLs is to make use of UDDI as a client. UDDI stands for Universal Description, Discovery, and Integration. It is a web service that is used to discover services available on local networks within an enterprise as well as services available publicly on the Internet.

UDDI has a far lower adoption rate than a lot of people expected. I have found that the most practical use of UDDI is by consuming it as a client within an intranet to obtain the true and current URL of an Intranet web service, such as a time-tracking, defect-tracking, or work-item-tracking service tool.

You can make use of public UDDI servers such as https://uddi.microsoft.com or you can make use of internal corporate UDDI servers such as those that can be hosted within Windows Server 2003.

Each entry in a UDDI directory can contain multiple URLs for a given service. You can use this entry in a UDDI directory like a phone book. Each time you want to invoke a web service you precede that call with a UDDI request. This request gives you the current URL of the web service. If the URL has changed, you can update your application and react accordingly.

The use of UDDI as a predecessor call allows the publishers of a web service to instantly change the location of a web service without negatively affecting any of the clients so long as the clients are using the UDDI entry. In addition, UDDI can be used to provide redundancy. If the UDDI entry returns a list of URLs, your application can try each of those URLs until one of the web service applications responds. If one of them fails, your application can then move to the next URL in the list to provide failover functionality.

Although your application might not need the degree of sophistication offered by using dynamic URLs and UDDI, there is no doubt that your application can benefit from providing at least some form of allowing the user to change the URL of the web service. This change can be done through GUI and private settings storage via isolated storage, or it can be done through the app.config file. Regardless of which method you choose, providing this dynamic, agile functionality will certainly enhance the end-user experience for your WinForms application.

    Team LiB
    Previous Section Next Section