The evolution of the Simple Object Access Protocol (SOAP) (see http://www.w3.org/TR/SOAP/) has challenged the boundaries of the Internet. SOAP and HTTP enables you to logon to external systems and execute remote function calls. Just imagine, using a Web browser in Melbourne, you can go through the Internet and execute methods on your company's mainframe in Seattle. This architecture enables cooperative Web servers to expose their business logic without compromising security.
The Microsoft .NET architecture puts heavy emphasis on Web services. In my opinion, the Web services interface could be the most appealing of the .NET Framework tools. There are a considerable amount of Web services out there. The mechanics of building a Web service is been addressed in quite a few technical articles. For example, you can refer to my first 15 Seconds article on .NET Web Services (see http://www.15seconds.com/Issue/010430.htm). The questions I had after reading these articles are: how do I consume them; how can I use them to benefit my information needs?
I will try to answers those questions. In doing so I will be discussing the following topics.
How do clients communicate with Web services?
Creating a proxy object using an existing Web service.
Creating clients:
Web browser client
Windows application client
WAP client.
The best way to learn is to use a real-world example. We will use an existing Web service on www.xmethods.com. This Web service will extract NASDAQ price feeds. Our clients will have a simple interface. The look and feel of the clients is compromised to concentrate on the semantics of building the interfaces.
1.1.1 Description of the Clients
All three clients will accept a company code as the user input. If the request is successful, the company name and the price value is displayed. If the company code is not available in NASDAQ, an error message will be displayed. The clients will have "Get Quote" and "Reset" buttons to initiate the user interactions.
I used Visual Studio.NET as my Integrated Development Environment (IDE). This beta doesn't incorporate the .NET Mobile Web to its development environment. Therefore, we need to use any text editor to create the WAP client. The .NET Mobile Web SDK will be integrated to the next version of the Visual Studio.NET.
1.3 How Do Clients Communicate with Web Services?
Let's recap on Web services' functionality. In my last article's Figure 1, the user at Location A will use the Internet as the vehicle to execute remote function calls (RFC) on Location B's Web server. The communication is done using the Simple Object Access Protocol and HTTP.
Figure 1. SOAP calls are remote function calls that invoke method executions on Web service components at Location B. The output is rendered as XML and passed back to the user at Location A.
Do we actually execute methods on the Location B's Web servers? This was the key question I had when I was new to this technology. As you can imagine that can be a serious security threat. As Webmasters we don't want anyone to use our Web resources and to do malicious damage to our sensitive data, not to mention chewing up our bandwidth. We also have to remember this is a distributed application. Therefore we have to be concerned about the marshalling of data.
To get around these problems we need to replicate the object behavior locally on the user's Web server. In our example, we will replicate the Location B Web Service functionality at Location A. That means we are creating a "proxy object" to act on behalf of the original Web service. The proxy object will have all the public available data interfaces as the original Web service. How do we get the public available data interface? If we take a trip down memory lane, you will remember the keyword "Web only" in the Web service code. So every "Web only" method will be replicated at the proxy object. This will protect us from exposing sensitive business logic to malicious hackers at the Web service end (Location B). In a way what we are doing is a "synchronization of object data exchange" between Location A and B. This process is known as creating a "proxy object" at Location A.
Therefore our code at Location A will instruct the proxy object. Then the proxy object will associate with the Location B Web service and produce the results to users at Location A. Well, how are we doing? Clear as mud?
Let's look at a revised version of Figure 1. This probably will clear our mind.
Figure 2 shows the communication between a Web browser client and a Web service over the Internet.
The proxy object is the basic concept of the Web Service invocation. So the first step to creating Web service clients is to create a proxy object. Then we can use multiple platforms (Web browsers, WAP, Personal Digital Assistant [PDA], SOAP clients) to extract data from the proxy object.
*NOTE: In later versions, this utility is split up into WSDL.exe and DISCO.exe. For this article, use the WSDL.exe utility. (see example at http://www.15seconds.com/issue/020117.htm).
Find the path of the Service Description Language (SDL) file. Appending "?SDL" to the Web service will get the SDL contract.
The above is the minimum required. Let's examine some important extra features.
/disco:<FileName>
This is to create a "Discovery" file. A discovery file is one of the mechanisms to find your Web service for a user who does not know the exact URL of the Web service. It is an XML file that will give a brief outline of the Web service. It is good practice to create a discovery file. Visual Studio.NET Web server projects will automatically create a default discovery file as a part of a new project. You can enable "Dynamic Discovery" to automatically track ".disco" files from a specified directory. Refer to my previous 15 Seconds article for an introduction. An advanced article can be found on the Microsoft Web site (see http://msdn.microsoft.com/library/dotnet/cpguide/cpconenablingdiscoveryforwebservice.htm).
/l:<Language Code>
The language can be Csharp##, Visual Basic, or Jscript., i.e., /l:Csharp.
/n:<Namespace>
The namespace you like to include this class. All the classes in that namespace will have access to this proxy object.
/o:<Location>
The place where you want to create the files. If omitted, this will be the current directory.
/i:<Namespace>
Additional namespaces you like to import into this class.
/protocol:<protocol Name>
This is the protocol to generate the proxy to work with: SOAP, HTTP GET, or HTTP POST. Default value is SOAP.
Let's use an example and create a proxy object.
This command will create the "LiveQuote.cs" file in the current directory. It is a C# file in the "WebServiceClients" namespace. You will recognize the importance of creating "WebServiceClients" namespace when we create the clients.
Now we need to compile the C# class. This will generate a Dynamic Link Library (DLL) to link to our client projects. We will put this under the "bin" directory.
This will create "LiveQuotes.dll" under the "bin" directory. If you need to read about compiling C# class, please refer to .NET SDK help.
Well folks, it is simple as that. With two commands we created a proxy object and are ready to extract data from the Web services. This proxy object will have the complete public interface for any sophisticated business logic functions. Remember, we didn't even register the DLL. We just compiled the source code and stuck the DLL in the "bin" directory. This is sufficient to get access to the DLL by the Web server. You may be confused if you are not familiar with the deployment mechanisms in ASP.NET. We do not need to register a DLL in order to be recognized by the operating system. We just put it under the local "bin" directory and the .NET Framework will pick it up at run time.
Let's look at the creation of clients. The common theme in creating any client is:
Create an instance of a proxy object.
Execute method calls on the proxy objects.
Capture the XML formatted data returned from the Web service.
Write client-specific controls to display the results.
1.5 Building a Web Page Client
1) The Web service listeners handle HTTP GET, HTTP POST, and SOAP method calls. First, let's create a Web project in Visual Studio.NET.
This will create a new project and add a new virtual directory to our default Web server called "LiveQuotes_Clients." A physical directory by the same name will be created under your "DriveName/wwwroot" directory.
2) Right-click on "References" of the project and click "Add Reference."
Click on the projects tab and navigate to our proxy object DLL.
3) Use the "Toolbox Web form controls" to create the ASP.NET files. If you have done any VB development, this will be a walk in the park for you.
I developed a file called "Client_WebForm_POST.aspx" by renaming the default WebForm1.aspx. When I am inserting the Web controls to the form, automatic C# code is generated in the background. It will be called "Client_WebForm_POST.cs."
This is one of the beta glitches in Visual Studio.NET. When we add a reference to a DLL we expect it to be automatically inserted in the relevant code, but it is not. We need to manually enter the following line to get access to the"WebServiceClients" namespace.
using WebServiceClients;
The proxy livequotes.DLL belongs to "WebServiceClients" namespace. Therefore, we now have access to this DLL from the code.
We also need to write some code to handle the user interactions, for example, button clicks.
public void btn_GetQuote_Click (object sender, System.EventArgs e)
{
LiveQuotes ProxyLiveQuotes = new LiveQuotes();
try
{
label_PriceValue.Text = ProxyLiveQuotes.MSNGetLastQuote(txt_CompanyCode.Text).ToString();
label_NameValue.Text = ProxyLiveQuotes.MSNGetCompanyName(txt_CompanyCode.Text).ToString();
}
catch
{
label_PriceValue.Text = "0.0";
label_NameValue.Text = "The Company data is not available";
}
}
public void btn_Reset_Click (object sender, System.EventArgs e)
{
label_PriceValue.Text = "0.0";
label_NameValue.Text = " ";
}
Complete code can be found in Appendix 1. In case of an invalid company code a "try" and "catch" block is used for exception handling.
4) Click Debug -> Start to compile the code and bring up a browser window.
We can enter a value for the company code, and it should give us a price feed.
Visual Studio.NET will use the POST method as the default posting method. We can easily use the GET method by changing the HTML form tag from "method = POST" to "method = GET".
1.6 Client Windows Application
It is very easy to create a Windows application client for the Web service in Visual Studio.NET. Here are the steps.
1) Choose "Windows Application" from the New Project file tab.
2) Right-click on "References" and click "Add Reference."
3) Click on ".NET References" and add "System.Web.Services.dll". This is a good practice to include in all console and Windows Web service clients.
4) Click on the "Projects" tab and navigate to the "LiveQuotes.dll," and add the DLL as a reference.
5) Create the form for the application. You can drag and drop form fields (labels, buttons, etc) from the toolbox on the left. Visual Studio.NET will generate the C# code behind the scene. We just need to enter the code to trigger the user's event handlers. (The complete code can be found at Appendix 2)
Here are the important bits of code to execute the button clicks.
protected void btn_GetQuote_Click (object sender, System.EventArgs e)
{
LiveQuotes windowsClient = new LiveQuotes();
try
{
label_PriceValue.Text = windowsClient.MSNGetLastQuote(txt_CompanyCode.Text).ToString();
label_Name.Text = windowsClient.MSNGetCompanyName(txt_CompanyCode.Text).ToString();
}
catch
{
label_PriceValue.Text = "0.0";
label_Name.Text = "The Company data is not available";
}
}
protected void btn_Reset_Click (object sender, System.EventArgs e)
{
txt_CompanyCode.Text = "";
label_Name.Text = "";
label_PriceValue.Text = "";
}
6) Run and execute the client. ( Debug -> Start form Visual Studio.NET).
7) Enter a company code and press the "Get Quote" button. You will get a price feed from NASDAQ.
The code creates a proxy object and uses SOAP over HTTP to invoke the function calls of the remote object. The "try" and "catch" exception handlers are used to stop the application from crashing from an invalid stock code.
1.7 WAP Interfaces
Traditional WAP used to be written purely in Wireless Markup Language. (WML). The .NET Mobile SDK lets you use ASP.NET page-like controls to create a WAP interface that is much faster than writing WML scripts.
Every NET Mobile page should have the following directive at the top.
MS .NET Mobile Web SDK has the ability to use MS Internet Explorer as a WAP client. This will speed up the development process. I used MS Internet Explorer 5.5 to simulate the following WAP functionality.
After selecting "Get Quote," we will get the following feedback.
For all the skeptics out there, here is a more familiar WAP interface. (First card of a Nokia 7110, courtesy of www.gelon.net)
I have discussed the basics of building Web service clients. There are Personal Digital Assistants and SOAP clients that open other avenues to this technology. (I will walk you through them if I receive enough user feedback.)
1.8 Conclusion
This article concentrates on the Microsoft Web services. Microsoft is not the only player in this market. IBM and Sun are also fighting fiercely to get their market share in Web services. Here are some great starting points for IBM and Java fanatics.
Well it's been an interesting journey investigating Web Services. The concept is sound and all major players are getting on the bandwagon. Only time will tell us what we will achieve with this technology.
1.9 Code Download and Setup Instructions
There is a WinZip file called "WebServiceClients.zip." Here are the instructions to set the clients up.
Unzip the WinZip file.
Copy the source code to an Internet Information Server (IIS) virtual directory. (to access Web and WAP clients).
Create a proxy object.
Try to create and execute clients following the instructions in this article.
1.10 Acknowledgements
Damien Watkins, Simon Cuce (Monash University)
Jason McConnel (Microsoft)
Tom Bendistinto (Information Technology & eCommerce or IT & e)
Amy Nelson, Karen Dias
Thanks for all your help and motivation. This article is a product of many collective minds.
1.10.1 About the Author
Chris Peiris is a systems architect for IT & E-Commerce in Melbourne, Australia. He has been designing and developing MS Web solutions since 1995. His expertise lies in developing scalable, high-performance Web solutions for financial institutions and media groups. He also teaches at Monash University in Caulfield, Australia. His core skills are C++, Java, .NET, DNA, MTS, Site Server, Data Warehousing, WAP, and SQL Server. Hobbies include cricket, teaching, and reading books. Chris has a Bachelor of Computing, Bachelor of Business (Accounting), and Masters of Information Management Systems. He can be reached at www.chrispeiris.com.
2.1.2 Client_WebForm_POST.cs (Background C Sharp file)
namespace LiveQuotes_Clients
{
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using WebServiceClients;
/// <summary>
/// Summary description for WebForm1.
/// </summary>
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Button btn_GetQuote;
protected System.Web.UI.WebControls.Button btn_Reset;
protected System.Web.UI.WebControls.Label label_PriceValue;
protected System.Web.UI.WebControls.Label label_Price;
protected System.Web.UI.WebControls.Label label_NameValue;
protected System.Web.UI.WebControls.Label label_Name;
protected System.Web.UI.WebControls.TextBox txt_CompanyCode;
protected System.Web.UI.WebControls.Label lable_CompanyCode;
public WebForm1()
{
Page.Init += new System.EventHandler(Page_Init);
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//
// Evals true first time browser hits the page
//
}
}
protected void Page_Init(object sender, EventArgs e)
{
//
// CODEGEN: This call is required by the ASP+ Windows Form Designer.
//
InitializeComponent();
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
btn_GetQuote.Click += new System.EventHandler (this.btn_GetQuote_Click);
btn_Reset.Click += new System.EventHandler (this.btn_Reset_Click);
this.Load += new System.EventHandler (this.Page_Load);
}
public void DropDownList1_SelectedIndexChanged (object sender, System.EventArgs e)
{
}
public void btn_GetQuote_Click (object sender, System.EventArgs e)
{
LiveQuotes ProxyLiveQuotes = new LiveQuotes();
try
{
label_PriceValue.Text = ProxyLiveQuotes.MSNGetLastQuote(txt_CompanyCode.Text).ToString();
label_NameValue.Text = ProxyLiveQuotes.MSNGetCompanyName(txt_CompanyCode.Text).ToString();
}
catch
{
label_PriceValue.Text = "0.0";
label_NameValue.Text = "The Company data is not available";
}
}
public void btn_Reset_Click (object sender, System.EventArgs e)
{
label_PriceValue.Text = "0.0";
label_NameValue.Text = " ";
}
}
}
3. Appendix 2
3.1 Windows Application Client Code
namespace ClientCSharpApp
{
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.WinForms;
using System.Data;
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.WinForms.Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components;
private System.WinForms.Label label_PriceValue;
private System.WinForms.Label label_Name;
private System.WinForms.Button btn_Reset;
private System.WinForms.Label label_CompanyName;
private System.WinForms.Label label_Price;
private System.WinForms.Label label_CompanyCode;
private System.WinForms.TextBox txt_CompanyCode;
private System.WinForms.Button btn_GetQuote;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
public override void Dispose()
{
base.Dispose();
components.Dispose();
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container ();
this.label_Name = new System.WinForms.Label ();
this.txt_CompanyCode = new System.WinForms.TextBox ();
this.label_Price = new System.WinForms.Label ();
this.label_CompanyName = new System.WinForms.Label ();
this.label_PriceValue = new System.WinForms.Label ();
this.btn_GetQuote = new System.WinForms.Button ();
this.btn_Reset = new System.WinForms.Button ();
this.label_CompanyCode = new System.WinForms.Label ();
//@this.TrayHeight = 0;
//@this.TrayLargeIcon = false;
//@this.TrayAutoArrange = true;
label_Name.Location = new System.Drawing.Point (168, 64);
label_Name.Size = new System.Drawing.Size (288, 24);
label_Name.TabIndex = 6;
txt_CompanyCode.Location = new System.Drawing.Point (168, 32);
txt_CompanyCode.TabIndex = 1;
txt_CompanyCode.Size = new System.Drawing.Size (100, 20);
label_Price.Location = new System.Drawing.Point (24, 112);
label_Price.Text = "Current Price";
label_Price.Size = new System.Drawing.Size (100, 23);
label_Price.Font = new System.Drawing.Font ("Verdana", 8);
label_Price.TabIndex = 3;
label_CompanyName.Location = new System.Drawing.Point (24, 72);
label_CompanyName.Text = "Company Name";
label_CompanyName.Size = new System.Drawing.Size (100, 23);
label_CompanyName.Font = new System.Drawing.Font ("Verdana", 8);
label_CompanyName.TabIndex = 4;
label_PriceValue.Location = new System.Drawing.Point (168, 112);
label_PriceValue.Size = new System.Drawing.Size (100, 23);
label_PriceValue.TabIndex = 7;
btn_GetQuote.Location = new System.Drawing.Point (360, 152);
btn_GetQuote.Size = new System.Drawing.Size (88, 23);
btn_GetQuote.TabIndex = 0;
btn_GetQuote.Font = new System.Drawing.Font ("Arial Black", 8);
btn_GetQuote.Text = "Get Quote";
btn_GetQuote.Click += new System.EventHandler (this.btn_GetQuote_Click);
btn_Reset.Location = new System.Drawing.Point (160, 152);
btn_Reset.Size = new System.Drawing.Size (75, 23);
btn_Reset.TabIndex = 5;
btn_Reset.Font = new System.Drawing.Font ("Arial Black", 8);
btn_Reset.Text = "Reset";
btn_Reset.Click += new System.EventHandler (this.btn_Reset_Click);
label_CompanyCode.Location = new System.Drawing.Point (24, 32);
label_CompanyCode.Text = "Company Code";
label_CompanyCode.Size = new System.Drawing.Size (100, 23);
label_CompanyCode.Font = new System.Drawing.Font ("Verdana", 8);
label_CompanyCode.TabIndex = 2;
this.Text = "Live Quotes C# Client App";
this.AutoScaleBaseSize = new System.Drawing.Size (5, 13);
this.BackColor = System.Drawing.SystemColors.ControlLightLight;
this.ClientSize = new System.Drawing.Size (472, 189);
this.Click += new System.EventHandler (this.Form1_Click);
this.Controls.Add (this.label_PriceValue);
this.Controls.Add (this.label_Name);
this.Controls.Add (this.btn_Reset);
this.Controls.Add (this.label_CompanyName);
this.Controls.Add (this.label_Price);
this.Controls.Add (this.label_CompanyCode);
this.Controls.Add (this.txt_CompanyCode);
this.Controls.Add (this.btn_GetQuote);
}
protected void Form1_Click (object sender, System.EventArgs e)
{
}
protected void btn_GetQuote_Click (object sender, System.EventArgs e)
{
LiveQuotes windowsClient = new LiveQuotes();
try
{
label_PriceValue.Text = windowsClient.MSNGetLastQuote(txt_CompanyCode.Text).ToString();
label_Name.Text = windowsClient.MSNGetCompanyName(txt_CompanyCode.Text).ToString();
}
catch
{
label_PriceValue.Text = "0.0";
label_Name.Text = "The Company data is not available";
}
}
protected void btn_Reset_Click (object sender, System.EventArgs e)
{
txt_CompanyCode.Text = "";
label_Name.Text = "";
label_PriceValue.Text = "";
}
/// <summary>
/// The main entry point for the application.
/// </summary>
public static void Main(string[] args)
{
Application.Run(new Form1());
}
}
}
In the second article of his series on Indigo web services, Chris Peiris explains how to host an Indigo web service and examines the IIS, self hosting, and Windows Activation Service hosting options. He then provides step-by-step instructions and sample code for an IIS-hosted and self-hosted Indigo web service. [Read This Article][Top]
In the first part of his series on Microsoft Indigo, Chris Peiris examines the basics of SOA, explains how Indigo fits into the picture and the problems it solves. He then introduces Indigo's programming model and finishes by building a sample Indigo web service using the Microsoft .Net Framework 2.0. [Read This Article][Top]
Adnan Masood concludes his discussion of Microsoft SQL Server Analysis services and Microsoft SQL Server Reporting services. In the final part, he discusses Reporting Server web services and using custom code in reports. [Read This Article][Top]
This article explains the features of the IE Web service behavior and shows how to asynchronously communicate with an ASP.NET Web service directly from the client.
[Read This Article][Top]
Calvin Luttrell shows how to validate e-mail addresses stored in Excel 2003 and
provides a special function for solving that pesky problem Yahoo! mail servers cause. [Read This Article][Top]
This short article describes a quick and easy way to provide some security to an ASP.NET Web service by modifying its associated documentation file. [Read This Article][Top]
Kerberos authentication is the cornerstone of Windows operating system authentication architecture. Web Services Enhancement 2.0 (WSE 2.0) extends Kerberos support to ASP.NET Web services. Chris Peiris explains the support for this new feature in WSE 2.0. [Read This Article][Top]
Chip Irek examines the architectural issues and component design issues of building a .NET application in a service-oriented architecture. [Read This Article][Top]
Thiru Thangarathinam shows how to use asynchronous Web services, Windows
Service applications, server-based timer components and .NET XML API classes to create high-performance, scalable, and flexible applications. [Read This Article][Top]
Part one showed how to transform XML data into HTML by using an XSL stylesheet from within a .NET application. This part explains how to make use of XSLT Extension objects and invoke a C# class method from an XSL stylesheet. [Read This Article][Top]
Mailing List
Want to receive email when the next article is published? Just Click Here to sign up.