White Papers Home |  White Papers | Message Board |  Search |  Products |  Purchase | News |   |
Creating and using Web Services
with the .NET framework and Visual Studio.Net
by Rick Strahl
Last Updated: 6/7/2001
Source code for this article:
http://www.west-wind.com/presentations/dotnetwebservices/dotnetwebservices.zip
Over the last few months I've spent a lot of time talking about XML and Web Services and how you can build and consume them. This time we'll look at the new features in Visual Studio.NET and ASP.NET that provide a very easy mechanism for creating Web Services and more importantly the easy mechanism used to consume those same Web Services using the .NET framework's built in support for Web Services.
Web Services promise to bring information into your applications from the Internet in much the same way that browers have made information available to end users. The .Net framework introduces Web Services as an integral part of the architecture, making it very easy to create and consume these services with minimal amounts of code written. In fact, if you read Microsoft's documentation, Web Services are featured as the new component architecture in the distributed age where not only Internet exposure is handled through them but also common reusable business and application services.
The .Net framework abstracts most of the internal logic that handles the remoting details of method calls over the wire and Visual Studio .Net builds support for Web Services directly into the development environment. With all of this in place it becomes almost as easy to call a remote method as it is to call a local method. And that after all is what Web Services are about making server side logic easily available to client applications.
Web Services 101
Ive covered the concepts of Web Services in previous articles (SOAP Web Services - in Code #1, 2001 and Using XML for messaging in Distributes Applications in Code #1 and #2, 2000), but I want to review the most important issues here again for those of you just tuning in now.
Web Services mission is to provide a Remote Procedure Call (RPC) interface for client applications to call class methods on the server side. Actually, the handling interface on the server need not be a class, but in the case of .Net and COM before it classes are typically used as the implementing entity. The idea is that in order to create a Web Service, you create a class methods with standard input and output parameters and you then mark those classes and the specific methods as exposable over the Net.
Web Services are meant to expose functionality of a server to other applications. The client applications in this case may be a Fat Client Windows app, a Fat Server Web application that runs a standard Web backend such as ASP, Cold Fusion, Web Connection etc., a browser based client application using script code, or even Java applet running in a browser on a Unix machine. As long as a client application has support for the Simple Object Access Protocol (SOAP) it can call the remote Web Service and return data for it, assuming the user is authorized.
SOAP is an important part of this process its the protocol thats responsible for routing the RPC message from the client to the server and returning the result back to the client application. SOAP is based on XML and follows a relatively simple design thats easy to implement. SOAPs simple protocol has contributed to its widespread support on just about any platform and development environment. You can find SOAP clients for COM (the MSSOAP Toolkit is available for Visual Studio 6 developers), .Net (obviously), Perl, Java, C++, PHP and just about any development environment you can think of.
Web Services without .Net
.Net makes it really easy to create and consume Web Services, but currently these solutions require .Net applications on both ends of the connection and .Net and Visual Studio.Net won't ship for some time to come yet.
In the meantime you can build Web Services with the MSSOAP toolkit, which works with COM objects and can be used with any COM capable tool such as Visual Basic, Visual FoxPro and so on. The MSSOAP toolkit exposes methods of a COM object to SOAP calls, using either an ASP or ISAPI backend handler to call and execute the Web Service exposed COM Object.
If you're using Visual FoxPro you can use MSSOAP (msdn.microsoft.com/xml) with the built in support tools from within the Visual FoxPro 7.0 development environment that lets you publish Web Services directly. If you're interested in an all FoxPro based solution that doesn't rely on COM objects you can also check out the free wwSOAP tools from West Wind Technologies which provide more flexibility and source code to adapt better to changing spec changes and varying SOAP clients. You can get wwSoap from West Wind Technologies for free at: www.west-wind.com
SOAP implementations provided by vendors typically consist of two pieces: A client side Proxy that handles the SOAP message creation and result message cracking to return the result data, as well as a server piece that implements the Web Service logic. The server piece tends to be an application server that calls out to custom Web Service classes that you create and that contain the business logic of your Web Service. The server code you write essentially consists of simple methods to handle inputs and outputs via parameters and return values respectively. The logic you write in the actual method is up to you and contains any functionality that your language of choice supports. This means writing code to call your business objects or if the process is simple enough using procedural code to perform some operation. Although Web Services can expose classes, youll find that typically you end up creating wrapper classes for existing business objects in order to handle the specific logic required to drive your Web Service. As such youre breaking up the business tier with a front end service (the Web Service) and a business service (your actual business objects or if you use procedural code just that code).
One important thing to remember is that Web Services follow typical Web rules. For one they are stateless. This means that even though Web Services expose classes, they are more of a remote procedure call interface than a remote class interface. You call methods with parameters rather than storing state in properties between method calls. In fact, none of the major Web Service implementations support properties in any way. If you need to keep state youll have to use Web Server specific functionality such as the ASP or ASP.NET Session object to store that state and retrieve it on subsequent hits. Since Web Services use the standard Web architecture, the same tools youve used for HTML based browser applications can also be used in the Web Services code, although you will find that you spend very little time accessing this functionality in your Web Service code as the frameworks abstract away the need to deal with the HTTP and Web Server layer for the most part. Things that you may need to manage yourself include state and security since the Web Service architecture doesn't provide this for you. In both cases you can however take advantage of the HTTP services (Authentication, SSL for example) or the Web server features like the session object to make short work of dealing with these issues.
SOAP proxies vary widely in quality and ease of use and unfortunately, as the official SOAP spec is still under heavy construction by the standards bodies. Interoperability is not perfect, but getting better all the time. Be sure to stay up with your vendors latest toolkits for best compatibility. Proxies are the key to making Web Services easy to use and consume. Microsofts implementations in both COM and .Net provide proxy interfaces that simulate the remote object and provide you with a simple call interface that lets you create the proxy and then call methods on it the same way as you would on a local object. Visual Studio .Net takes this one step further by actually providing you a real proxy object that contains the actual methods of the remote object making it possible to even use time saving features like IntelliSense on the object.
Web Services in .Net
Creating Web Services in .Net and consuming the service either in a Windows Form application or an ASP.Net Web page is almost trivial (well, barring some nasty beta bugs at least <bg>). For the remainder of this article Ill walk you through creating a Web Service and then creating two clients one as a Windows Form and one as ASP.Net Web Form to consume that Web service. Ill keep the examples really simple in this article to demonstrate how Web Services are implemented and how they behave rather than building an elaborate sample application. In the next issue Ill dig in and build a small application that relies on Web Services to provide all of its data and application server logic needs in a more real live scenario that demonstrates the issues that you need to deal with when building a distributed application, which differs somewhat from a standalone application.
But before we get our hands dirty lets talk about how .Net implements Web Services so you can get an idea of what happens behind all the fancy black box code that .Net provides to make Web Services so easy to use.
Behind the scenes there are three major components that make up a Web Service:
- The Web Service on the Server side
- The client application calling the Web Service via a Web Reference
- A WSDL Web Service description that describes the functionality of the Web Service
Figure 1 - .Net Web Services use WSDL files to get a type description of the Web Service which provides the detail needed to the client to create a proxy. The proxy calls the Web Service using the SOAP protocol passing parameters and returning a return value for the remote method call.
A Web Service in .Net consists of a .ASMX page that either contains a class that provides the Web Service functionality or references a specific external class that handles the logic in an external class file. Classes are standard .Net classes and the only difference is that every method that you want to expose to the Web is prefixed with a [WebMethod] attribute. Once the .ASMX page has been created the Web Service is ready for accessing over the Web. .Net provides a very useful information page about your Web Service showing all the methods and parameters along with information on how to access the Web Service over the Web. You can also use this page to test basic operation of your Web Service without calling the Web Service with a real client. Ill talk more about this useful sample page later.
.Net Web Services that run over HTTP can be called in 3 different ways:
- HTTP GET Operation
You can pass parameters to a Web Service by calling the ASMX page with query string parameters for the method to call and the values of simple parameters to pass.
Example: WebDemo.asmx/MethodName?Parm1=value
- HTTP POST Operation
Works the same as GET Operation except that the parameters are passed as standard URL encoded form variables. If you use a client such as wwIPStuff you can use AddPostKey() to add each parameter in the proper parameter order.
Example: WebDemo.asmx/MethodName
- SOAP
This is the proper way to call a Web Service in .Net and its also the way that .Net uses internally to call Web Services.
The GET and POST operations are useful if you need to call a Web Service quickly and no SOAP client is readily available. For example, in a browser based client application it may be easier to use GET and POST instead of constructing and parsing the more complex SOAP headers that are passed back and forth in a SOAP request. But with a proper SOAP client in place SOAP provides the full flexibility of the protocol, where GET and POST operations have to stick to simple inputs and outputs. Among other things that you can do with SOAP is pass complex objects and data over the wire and for these operations to work you need to use SOAP.
WSDL a type library for a Web Service
When you create a Web Service you automatically get support for a WSDL (Web Service Description
Language) schema that describes the Web Service by accessing the .ASMX page with a querystring of WSDL:
http://localhost/webdemos/webdemo.asm?WSDL
A WSDL file describes all the methods and method signatures, as well as the namespaces and the handling URL for the Web Service in an XML document. This document works very much like a type library does in COM for the client application to determine what functionality is available in the Web Service. Visual Studio.Net uses the WSDL file to create a Web Reference on the client side from your Web Service. It reads the WSDL file and based on the definitions found in the WSDL file creates a proxy class that mimics the interface of the Web Service. The resulting class is actual source code that you can look at (see Web References sidebar). Because this class is actually linked into your client project the class becomes available in IntelliSense and you can actually see the full interface of the class as you type.
WSDL is a relatively new standard that is used to describe a Web Service's features and how the service should be called. As SOAP standards are getting more complex and start to include specific namespace references, complex data types a type definition format was needed to provide this information to the SOAP client tools. Microsoft especially embraced WSDL early on in it SOAP toolkits and relies heavily on the WSDL description MS SOAP clients don't work without WSDL files. The SOAP spec does not require WSDL, but the ability to have a type definition in WSDL makes it easier to call a Web Service since the client application will not have to set various namespace references manually this information can be retrieved from the WSDL file. Use of WSDL makes it possible to make single line method calls to various service methods that would otherwise require a number of custom configuration settings.
WSDL is very new and changing rapidly, which once again means that interoperability between tools is limited at the moment. .Net Beta 2 in particular uses a very non-standard format to describe it's method parameter and return types (they are all represented as complex types) which are unreadable by most WSDL implementations to date. This will likely be addressed by standards in the future, and is never a problem when using .Net client to .Net servers.The Client Application
Client applications can be any type of application from a Web backend aggregating data to display custom content to clients to a Fat Client application running Windows forms. The process of connecting a client application in Visual Studio.Net is always the same though: You set up a Web Reference, add the Web Reference namespace and then simply call the methods of the Web Service.
Behind the scenes the method call actually calls a proxy object, which invokes the remote Web Service. The proxy base class contains all the black box magic that performs the SOAP call over the wire and the proxy class simply calls work methods in the base class to do all the dirty work. The proxy reads the WSDL file to verify that the method signature and type information is correct and up to date, then creates the SOAP envelope to send to the Web Service for processing. The proxy makes the HTTP call and passes the SOAP packet off to the Web server.
A SOAP request packet traveling over the wire looks like this:
<?xml version="1.0"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2000/10/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope">
<soap:Body>
<AddNumbers xmlns="http://tempuri.org/">
<lnNumber1>6</lnNumber1>
<lnNumber2>4</lnNumber2>
</AddNumbers>
</soap:Body>
</soap:Envelope>
On the server side the .Net framework handler through ASP.NET and the .ASMX Web Service extension picks up this SOAP request and passes it off to your class for processing. The internals of .Net take care of instantiating your Web Service class, firing the constructor and then invoking the requested method, in this case AddNumbers, passing in the two provided parameters, which are properly converted into the types specified by the WSDL file.
At this point the method executes. This is were all the user processing occurs. Youd write custom method code to do your business logic required to service the functionality of the Web Service. When youre done you return a result value as a simple return from the method.
The .Net Web Service handlers then kick in and take your return value and package it up into a SOAP response packet which looks like this:
<?xml version="1.0"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2000/10/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope">
<soap:Body>
<AddNumbersResponse xmlns="http://tempuri.org/">
<AddNumbersResult>10</AddNumbersResult>
</AddNumbersResponse>
</soap:Body>
</soap:Envelope>
and sends this output back over the HTTP connection to the client application proxy. The proxy now picks up the SOAP response, and unpacks the SOAP response, performs the type conversion of the XML back into the proper return type specified in the WSDL document and returns the value back to the client side.
On the client side all of this happened in a two lines of code:
using WebDemoService; // top of code
/// calling Web Service class WebDemo
WebDemo oService = new WebDemo();
decimal lnResult = oService.AddNumbers(6.4);
assuming the Web Reference has been configured. Not bad for code abstraction, huh?
Creating our first Web Service
Before we begin here's a word about the language and tools used for this article: Ill be using C# to create all code in this article using VS Build 9148 which is a pre-beta 2 build. This build is significantly different than Beta 1 so most of these examples will not work with Beta 1, but they should work with Beta 2 when it ships (by the time you read this it should be out for a couple of months or so). Such is the danger of writing articles for beta software regardless, the concepts with minor adjustments are sure to apply no matter how many changes occur to the software.
There are two ways that you can create Web Services. One is to create self contained services that use a single .ASMX page that contain both the Web Service header and the actual class code for the Web Service. Or you can create just the Web Service header and reference a class externally. Visual Studio.Net uses the latter and well look at that mechanism in a minute.
To create a self contained Web Service you can use any text editor and create something like this:
<%@ WebService Language="c#" Class="FirstWebService" %>
using System;
using System.Web;
using System.Web.Services;
public class FirstWebService {
[WebMethod]
public string HelloWorld(string lcName) {
return "Hello World, " + lcName;
}
[WebMethod]
public decimal AddNumbers(decimal lnNumber1, decimal lnNumber2) {
return lnNumber1 + lnNumber2;
}
/*
[WebMethod]
public DateTime GetServerTime() {
return DateTime.Now;
} */
}
This simple Web Service implements a couple of basic methods that demonstrate the structure of a Web Service. Note that the Web Service in this .ASMX page (FirstWebService.asmx) consists of a header and the actual class code contained in the same file.
The header determines which language and class to use:
<%@ WebService Language="c#" Class="FirstWebService" %>
Web Service classes can also be declared externally using syntax like this:
<%@ WebService Language="c#" Codebehind="FirstWebService.asmx.cs"
Class="CodeService.FirstWebService" %>
In this scenario the .ASMX file only contains the above header with the class actually residing in the Codebehind specified attribute.
Using a single file automatically applies any changes you make to the Web Service code at runtime, causing the Web Service to recompile itself on the fly. With an external class using the Codebehind attribute any changes to the class require a recompile. Another attribute called SRC can also be used instead of Codebehind which allows you to use an external class and also have it compile at runtime, but its unclear whether this will be a shipping feature or not at this time. In general Id suggest to use a single file if you anticipate making many changes to the Web Service, use an external file and precompile if the Web Service is fairly stable. If you use VS.Net you can only use the latter.
The rest of the .ASMX file is the actual implementation of the Web Service, which is nothing more than a class and some namespace declarations. Note that unlike most other .Net programs the code you write inside of an .ASMX is not contained in a namespace reference! The namespace is implicit and tied to the .ASMX file.
The service is implemented just as a class and each method is exposed by using the [WebMethod] attribute to tell the compiler to expose the method:
[WebMethod]
public decimal AddNumbers(decimal lnNumber1, decimal lnNumber2)
{
return lnNumber1 + lnNumber2;
}
at minimum you need to include the following namespaces:
using System;
using System.Web;
using System.Web.Services;
to gain access to the Web Service functionality. Without this the Web Service would not compile and error out.
Testing the Web Service
To check out the Web Service you can access the URL for it like this:
http://localhost/codeservice/firstwebservice.asmx
Figure 2 The Web Service status page lets you see and test the methods that the Web Service exposes. You can also review and optionally capture the WSDL description for the service.
Figure 2 shows the status page that comes up as a result of calling the Web Service directly. This status page is very handy in checking out the functionality of the Web Service without actually creating a client application for it. Figure 3 shows the extremely useful Method test page that allows you to type in parameters and actually test the Web Services operation. As you might guess this is a great way to do initial debugging of your Web Services without having to worry about both client and server pieces at the same time.
Figure 3 The method test page of a Web Service lets you run sample requests against each method of the service. This page also shows the format for the different calling mechanisms including SOAP, GET and POST operations.
When you run one of the sample methods youre actually using the GET operation of the Web Service all parameters are stuck on the URL query string, so testing functionality is somewhat limited to how much data you can stuff onto the query string. Submitting the request shown in Figure 3 results in a call to the following URL:
http://localhost/codeservice/firstwebservice.asmx/AddNumbers?lnNumber1=10&lnNumber2=22.12
which returns an XML document result:
<?xml version="1.0" ?>
<decimal xmlns="http://tempuri.org/">32.12</decimal>
Note that the result is not SOAP encoded and the parameters where not sent up to the server via SOAP either. The GET and POST operations are entirely independent of SOAP, but can be very useful to client applications that may not have access to a SOAP client or want to avoid the overhead that SOAP introduces into remote calls. For simple operations these GET and POST operations are quite useful. For example, if you wanted to consume this data in Visual FoxPro (or any other COM based client) you could simply do:
oDOM = CREATEOBJECT("MSXML2.DomDocument")
oDOM.async = .F.
oDOM.Load("http:// /firstwebservice.asmx/AddNumbers?lnNumber1=10&lnNumber2=22.12")
? val(oDOM.SelectSingleNode("/decimal").text)
Note that values are not typed so you need to perform the type conversions yourself.
Modifying the Web Service
If you look back at the code for the Web Service notice that there is a commented out method block (using the /* and */ delimiters which are C style block comments) of code that blocks out a third method called GetServerTime(). To make this method available in our Web Service immediately all we have to do is remove the comment strings and reload our Web Service status page and voila, the new method is immediately available. .Net detects that the .ASMX file was changed and then uses the Just In Time compiler to recompile the page on the fly. You'll notice that after you've made the change to the class that it takes a while to load again as the just in time compiler fires up and compiles the page. Subsequent loads are quick again.
So what happens if you have a syntax error in your code? Since the page is compiled and the compiler pre-checks syntax and type information of everything before executing the page. If I change the AddNumbers method to:
[WebMethod]
public decimal AddNumbers(decimal lnNumber1, decimal lnNumber2)
{
return lnNumber + lnNumber2; // lnNumber is not valid
}
you get a detailed error page as shown in figure 4:
Figure 4 A Web Service error page provides a lot of detail relating to the error that occurred.
Because the page compiles and is checked for type information error checking at the compile stage is very thorough and can provide very detailed information about what's wrong with the code before running it. Note that you can't debug self contained .ASMX pages through Visual Studio, which is why Visual Studio will create separate .ASMX and .ASMX.cs (or .vb) pages when you create a project in the IDE shell. Compiling and debugging Web Services is much easier in Visual Studio.Net so let's see how we create a Web Service in Visual Studio.
For now I'm not going to show how to call this Web Service from a client application, but rest assured that this Web Service is fully functional and can be called from any .Net client applications that supports Web References using SOAP. I'll show an example of this later on, but before we get to the client code lets see how we create a Web Service application with Visual Studio.
C# (pronounced C Sharp) is a new language supported in the .Net platform. The language is very C++/Java like, with many high level features that make it much more accessible than C++. The high level features deal away with use of pointers, usage of garbage collection via the .Net Common Language Runtime (CLR) and a fully objectified type system that makes the language more like VB than C++ in many ways.
C# was written from the ground up to work with the .Net CLR and supports many of the CLR concepts such as delegates cleanly as part of the language. If you've been scared off by C++ in the past you will find C# much more accessible and a good alternative to Visual Basic syntax in .Net.
Since C# uses the CLR to execute code, it does not have any significant performance advantage over VB.Net, nor are there significant feature differences other than syntax just about everything you can do in C# can also be done with VB.Net. One exception is C#'s support for unsafe, non-garbage collected code that allows C# code to access memory (and pointers) directly.
To me C# has a fresh feel to it it's a new language that doesn't carry any legacy baggage and it is fairly easy to pick up especially if you have any C background at all. The language set is fairly small too most of the application you write in .Net deal with .Net framework classes rather than native language features, so a lightweight language is actually good for quickly picking it up.
Getting started in VS.Net
Let's switch over to using Visual Studio.Net to create a new Web Service from scratch and see how that is different from the service we just created with Notepad or similar editor.
Lets start by creating a new VS.Net project called CodeWebService. Start up VS and create a new project like this:
Figure 5 Create a new Web Service project, which creates a new virtual directory on the Web Server where all of your source files will be contained.
This step takes a while to complete as VS.Net goes out and creates a virtual Web application for you and then creates the project inside of this virtual directory. One useful feature of .Net is the portability this model provides you can move a generated project simply by copying the entire source tree to another machine and open it up there. This directory will contain all of your native source files, both .ASMX and the actual class (.cs or .vb files) that make up the projects source. In addition therere files that configure the Web Application (web.config and global.asax) the project file (codeservice.csproj) and a .disco file that is used as a placeholder to list the available Web Services in this Web application. Figure 6 shows what the default loaded project looks like.
The first thing I like to do is get rid of the default WebService1.asmx page and replace it with a properly named page. Currently VS.Net doesnt rename the class when you rename the page and since I dont want to end up with WebService1 as my class name I rather start over with a properly named page. So lets add instead a new C# Web Service called CodeWebService by using Project|Add New Item. When we do this and double click on the CodeWebService.asmx page you bring up the Web Service in design view. Design view lets you drag and drop various functionality such as database connections onto the service which generates a bunch of code. While useful for some things lets rather see how we can edit the code in the Web Service ourselves.
To see the code for the class right click on the Web Service in the project explorer or on the design mode page in the editor and select View Code. What you'll find is something like this:
namespace CodeService
{
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
public class CodeWebService : System.Web.Services.WebService
{
public CodeWebService()
{
InitializeComponent();
}
private void InitializeComponent()
{
}
public override void Dispose()
{
}
// WEB SERVICE EXAMPLE
// The HelloWorld() example service returns the string Hello World
// To build, uncomment the following lines then save and build the project
// To test this web service, press F5
// [WebMethod]
// public string HelloWorld()
// {
// return "Hello World";
// }
}
}
As mentioned earlier VS.Net generates a Web Service into separate .ASMX and .CS files. The .CS part shown above is a completely self contained class module including its namespace association with the current project. Note that VS is generating a lot more code for the service than the standalone I created manually. The constructor, InitializeComponent and Dispose methods are not required unless you use the designers, which will generate code into these methods. To add our Web Services functionality that we created in our FirstWebService we can simply cut and paste the methods we created before and paste it to replace the Web Service example VS created for us in the code. In order for us to use the Web Service we have to compile it first. If we didn't the Web Service would fail at this point. You can build the project and then test the operation of the Web Service, which should now be identical in behavior to the example we looked at before exposing the three methods shown.
There's a difference though: You can now debug your Web Service by setting a breakpoint (F9) in one of your methods. To try this set the Web Service as your Set as Start Page in the project (right click on the service in Project Explorer) then Run the project (F5). This will bring up the Web page in a browser window. Now click on your method and run the service the debugger will stop on the line of code with the breakpoint
What's even nicer is that you can add multiple projects to a single VS solution and you can then debug all of them as part of the same session. For example, it's possible to create the Web Service code as well as the client code and debug the client code in a Windows Form and then step into the Web Service code as it gets hit on the server side. This is very slick and truly provides with an end to end debugging solution.
Calling the Web Service on the client side
Let's put that debugging theory to a test and create a client Windows Form application that calls the Web Service. Let's keep things simple and use the GetServerTime() method as an example. This method returns a TimeDate value that contains the current time and date.
The first step is to add a new project to our existing solution. You do this by clicking on the Solution in the Solution Explorer and right clicking and Add Project. Then select a C#, Windows application and naming it WebDemoClient. Again I'll remove form1.cs and add a new form called WebDemoClient.cs to the project. When it's all said your solution project view now should look like Figure 8.
Figure 8 The solution contains both the Windows Form client and Web Service and allows debugging across both off them.
Visual Studio.Net Web References are proxy classes created on the client to connect to the Web Service running on the server. Inside of the IDE Web references automatically generate code and insert hidden files into your project. This is required because .Net is type safe and in order to compile code that uses the Web Service, the client has to know the method signature of each method that is called.
This is nice as it provides full IntelliSense support in the development environment. However it also means that you need to refresh the Web Reference whenever the Web Service interface changes.
The generated proxy class code is stored in the \WebReferences folder beneath the application directory. There is one source file class for each Web Reference you create by the name of the class you publish. Although you can look at the source you shouldnt change this code as it will be overwritten next time you update the Web Reference.
To use a Web Reference simply add the namespace of the Web Reference. By default itll be YourClientNameSpace.ServerClassNameSpace. Make sure you add this namespace to your list of namespaces (using in C# or imports in VB).
To create the form shown I added the labels, and a textbox named txtServerTime and a button with a name of cmdServerTime to the form created.
Form1 that is created by the Wizard is a special form called your startup form. By deleting it I also deleted the Main method which is the application's starting point. To get the app to run I need to add the following method to the form class:
public static void Main(string[] args)
{
Application.Run(new WebDemoClient());
}
A project may contain only a single Main method, which is called once when the entire application starts. This code basically runs the current form.
Adding a Web Reference
Next we need to add a reference to our Web Service. Go into the Solution Explorer and select the CodeClient project, then right click on the References item and select Add Web Reference. When you do, a dialog as shown in Figure 9 pops up. The dialog lets you point at either a WSDL or .DISCO (Web Service Discovery Service) file to locate the Web Service you want to create a reference for. If you pick the DISCO file for our examples you'll end up with two Web Services listed: FirstWebService and CodeWebService which are the two .ASMX files that live in that directory. Select CodeWebService and the reference will add to the project as a Web Reference called localhost or whatever the domain name of the server you worked with is. You should immediately rename this to a descriptive name that will become your namespace reference to reference the proxy object. Figure 10 shows what the project looks like after all of this has been done.
Now that we have the reference we can start to use the Web Service. First thing we need to add is a reference to the service in the class header:
using CodeWebService;
Next, we need to attach code to the button's click event. Just double click and enter:
private void cmdGetServerTime_Click(object sender, System.EventArgs e)
{
CodeWebService oService = new CodeWebService();
this.txtServerTime.Text = oService.GetServerTime().ToString();
}
If you wanted to call the AddNumbers and Helloworld methods you'd use:
CodeWebService oService = new CodeWebService();
decimal lnResult = oService.AddNumbers(10,23.12);
MessageBox.Show(oService.HelloWorld("Rick"));
That's it! Notice also that when you are typing this code that IntelliSense works while working against the oService object reference. That's because the proxy object is actually compiled into the project (take a look at \CodeClient\Web References\CodeWebService\CodeWebService.cs to see the source for this 'phantom' object that VS generates for you and compiles into your application. Check back with this after some of the next examples you'll find that the proxy can also generate class references that are used as return parameters. But I'm getting ahead of myself here more on this in a minute.
Useful Web Service Return Values
Ok, let's go back to the server side and our Web Service for a minute. Let's have some fun with the Web Service and do something mildly more useful than numbers that we could have calculated in our heads <s>. Let's add a method that returns data as part of a Web Service request. So far, I've only returned simple data types: A string, a date and a number. While that's useful in some situations you'll find that more often than not you need to return much more information than this. For example if you get a stock quote you wouldn't want to just get the latest price but also the last change info, the opening and closing price and so on. One way to do this is to use XML strings as result values and that works really well actually.
Returning XML is easy. Just about anything in .Net can return you an XML string so generating XML from data structures and even data is a snap. Or alternately you can generate your own XML either as strings or by using one of the XML data reader objects available in the .Net framework. Note that when you pass back XML strings those strings are encoded as, well, XML strings. This means they get escaped. For example check out this XML result generated by a Web Service method like this:
[WebMethod]
public string ReturnXML(string lcName)
{
return "<?xml version=\"1.0\"?>\r\n<data>" + lcName + "</data>";
}
which yields the following XML that travels over the wire:
<?xml version="1.0"?>
<string xmlns="http://tempuri.org/"><?xml version="1.0"?>
<data>Rick</data></string>
The result received by the client application will be correct, but because the XML traveling over the wire is wrapped in a string element the string has to be properly XML encoded. As you can imagine there's some overhead involved in this conversion to encoded XML and back.
Luckily .Net minimizes the need for you to pass around XML strings significantly by explicitly supporting return values and parameters that are objects. Many of .Net object types and your custom classes and structures can be easily persisted into XML and that XML becomes part of the WSDL schema and rides the SOAP payload as part of the XML document. In short, there's no encoding involved.
Let's start with passing some data back from our Web Service to the Windows Form application. The following Web Service method queries some data from the SQL Server Pubs data base and returns a DataSet object as a result value from the method call.
[WebMethod]
public System.Data.DataSet GetAuthorData(string lcID) {
if (lcID == "" )
lcID = "%";
DataSet ds = new DataSet();
string cConnection = "server=(local);provider=SQLOleDb;database=pubs;uid=sa;pwd=";
OleDbConnection oConn = new OleDbConnection(cConnection);
OleDbDataAdapter oAdapter = new OleDbDataAdapter();
oAdapter.SelectCommand =
new OleDbCommand("select * from Authors where au_id like '" + lcID + "%'",oConn);
try
{
oConn.Open();
}
catch(Exception e)
{
return null;
}
oAdapter.Fill(ds,"Authors");
oConn.Close();
return ds; }
This code creates a DataSet object by running a query against the Author table in the Pubs sample database on SQL Server. In order to use this you will also need to add the OleDb namespace at the top of the Web Service:
using System.Data.OleDb;
Alternately I could have also used the SqlClient namespace and replace all the OleDb references with SqlClient for a native collection to SQL Server which would be slightly faster. Creating DataSets requires a handful of lines of code that you have to get used to they are lengthy and require several related objects. A DataSet is a data container that acts like a self-contained, offline database. It can contain multiple cursors along with relational and validation rules that tell it how to behave.
But one of the coolest features of the DataSet is that .Net knows how to persist itself into XML and reassemble itself from XML. This means you can return a DataSet as a return value from a Web Service method and pick it up on the client side as we've done in the code sample above.
Figure 11 shows the author query form that consists of a listbox oAuthorList and an view pane that shows the detail data for the author with textboxes named txtName, txtAddress, and txtPhone. The Web Service returns the data as a DataSet to the client application and the client application holds on to that DataSet and binds it to the oAuthorList listbox. The DataSet contains all the data for both the list and the detail so once downloaded a public member of the form oDS holds on to the data in a disconnected fashion from the Web server. When the pointer is moved in the list the index is read from the list and corresponded into the row in the DataSet's Table object.
Here's the client code for the GetAuthors button (shown here in a separate method called from the button):
public void GetAuthorList()
{
CodeWebService oService = new CodeWebService();
ds = oService.GetAuthorData("%");
oAuthorList.Items.Clear();
for (int x=0;x < ds.Tables["Authors"].Rows.Count; x++) { oAuthorList.Items.Add(ds.Tables["Authors"].Rows[x]["au_lname"].ToString().Trim() +
", " +ds.Tables["Authors"].Rows[x]["au_fname"].ToString());
}
}
Again, that's it!!! Note that basically a single line of code is responsible for pulling the entire dataset down to the client side. The DataSet is now an object that has all the capabilities of the ADO.Net DataSet class. The reference to ds here is scoped to the class so the data set remains live until the form goes away or the DataSet is updated. To update the current author displayed the following code is used:
private void oAuthorList_SelectedIndexChanged(object sender, System.EventArgs e)
{
int lnIndex = oAuthorList.SelectedIndex;
DataRow loRow = ds.Tables["Authors"].Rows[lnIndex];
txtName.Text = loRow["au_lname"].ToString().Trim() + ", " +
loRow["au_fname"].ToString();
txtAddress.Text = loRow["address"].ToString() + "\r\n" +
loRow["city"].ToString() + " " +
loRow["state"] + ", " + loRow["zip"].ToString();
txtPhone.Text = loRow["phone"].ToString();
}
DataSets are a new feature of .Net and can be thought of as a set of offline Recordsets on steroids. DataSets can contain multiple cursors, which are created from separate queries. The data inside of a DataSet can contain schema information that provides information on things like primary keys and relationships between tables. Once a DataSet is created the data is disconnected from the database. DataSets manage data locally through a cursor engine the client application manipulates the dataset through array elements in various collections exposed in the DataSet.
DataSets can be persisted across the Web as XML easily you can pass them as return values from a Web Service method for example and pick up the DataSet on the other end as a fully functional reference. DataSets were designed with 'offline' usage in mind using update operations to resync with the database or backend.
DataSets can also be created dynamically or be created from an XML document. You can manipulate the DataSet and then save it in XML format, which can then be reloaded at a later point. By using XML in this fashion many simple data tasks can be accomplished without using a backend database at all.
You'll want to get familiar with the power of DataSets they can provide a great abstraction layer in your business objects to pass data around the business tiers.
Consider how powerful this is! You have a local copy of the data here to use on the client side that is only updated when we tell it to refresh.
DataSets are extremely powerful and let you handle updates and persistence too. I don't have enough space to cover this here in more detail, but you can also update the DataSet object and then send it back up to the server via a Web Service parameter. The DataSet can then reconnect with the database backend and update the data in the backend database! This is distributed data handling in its truest sense and it's implemented in an easy to use and elegant manner. If you start thinking of Web Services as a data distribution service you can start to see the real power that Web Services bring to applications.
Keep in mind that the simplicity that I've described here implies that the client and server applications both are running .Net. But even so, the data traveling over the wire is plain XML, so you can use a Web Service like this even if you're using non-.Net client applications that can parse and generate the XML to pass back and forth. But with .Net client to .Net server the ease of accomplishing this rather complex scenario has become very easy and totally transparent to the client application.
But wait there's more
Passing data around is very powerful, but .Net also supports easy transfer of objects over the wire! To show this example, I'll create a new class library BusObjects in my Web Service project and add two classes to it:
namespace BusObjects
{
using System;
using System.Data;
using System.Data.OleDb;
public class Customer
{
public string cLastName = "";
public string cFirstName = "";
public string cCompany = "";
public DateTime tEntered = DateTime.MinValue;
public bool lError = false;
public string cErrorMsg = "";
public Address oAddress;
protected string cConnectString =
"provider=SQLOleDb;server=(local);uid=sa;pwd=;database=WestWindWebStore";
public Customer()
{
oAddress = new Address();
}
public bool Load(string lcName)
{
DataSet ds = new DataSet();
OleDbConnection oConn =
new OleDbConnection("server=(local);provider=SQLOleDb;database=WestWindWebStore;uid=sa;pwd=");
OleDbDataAdapter oDSCommand = new OleDbDataAdapter();
oDSCommand.SelectCommand =
new OleDbCommand("select * from wws_customers where lastname like '" + lcName.Trim() + "'",
oConn);
try
{
oConn.Open();
oDSCommand.Fill(ds,"wws_customers");
}
catch(Exception e)
{
lError = true;
cErrorMsg = e.Message;
return false;
}
if (ds.Tables["wws_customers"].Rows.Count < 1)
{
return false;
}
DataRow loRow = ds.Tables["wws_customers"].Rows[0];
cLastName = loRow["lastname"].ToString();
cFirstName = loRow["firstname"].ToString();
cCompany = loRow["company"].ToString();
tEntered = (DateTime) loRow["entered"];
oAddress.cAddress = loRow["address"].ToString();
oAddress.cPhone = loRow["phone"].ToString();
oAddress.cEmail = loRow["email"].ToString();
return true;
}
public bool Load(int lnPk) {
overloaded method omitted for size
}
} // class Customer
public class Address
{
public string cAddress = "";
public string cPhone = "";
public string cEmail = "";
public Address()
{
}
} // Address
} // namespace
This simulated business object consists of a customer object that has various members that also contains an embedded Address member, which is a separate class. When the class loads the oAddress member is created creating this aggregate class. Using the Load method the data members are loaded from values taken from a custom database filling the customer and address member properties. Note that Load() is overloaded to either take a string parameter which is searched for as a name or a an integer PK.
To use this object in our Web Service I added these two methods:
[WebMethod]
public Customer GetCustomer(int lnPK)
{
Customer oCustomer = new Customer();
if (!oCustomer.Load(lnPK))
return null;
return oCustomer;
}
[WebMethod]
public Customer GetCustomerByName(string lcName)
{
Customer oCustomer = new Customer();
if (!oCustomer.Load(lcName))
return null;
return oCustomer;
}
Notice that I used different names for these methods rather than using overloaded parameter method signatures here. This is because Web Services may not use method overloading a WSDL error will occur if you multiple methods with the same name for a class. Instead you have to wrapper any overloaded methods and assign new names as I've done here.
As you can see the Web Service does minimal work here it simply instantiates the business object and runs the Load() method to retrieve the customer and returns an object reference. That's right we can simply return an object reference over the wire. If you run the sample page for this method you'd see an XML result like this:
<?xml version="1.0"?>
<Customer xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2000/10/XMLSchema" xmlns="http://tempuri.org/">
<cLastName>Strahl </cLastName>
<cFirstName>Rick </cFirstName>
<cCompany>West Wind Technologies </cCompany>
<tEntered>2001-05-07T09:18:32.0000</tEntered>
<lError>false</lError>
<cErrorMsg></cErrorMsg>
<oAddress>
<cAddress>32 Kaiea Place</cAddress>
<cPhone>(808) 579-8342</cPhone>
<cEmail>rstrahl@west-wind.com </cEmail>
</oAddress>
</Customer>
The entire object including the oAddress surrogate object are persisted into an XML result and all you had to do to get this functionality is return an object reference from your Web Service method call.
Now let's go back to the client project and refresh our Web Reference, then add the controls shown in Figure 12 to load some customer info.
Here's the code for the GetCustomer method of the form that is called from the button's Click() event:
public void GetCustomer(string lcName)
{
CodeWebService oService = new CodeWebService();
Customer oCust = new Customer();
oCust = oService.GetCustomerByName(lcName);
if (oCust == null)
{
MessageBox.Show("Customer not found","WebService Demos");
return;
}
txtCustName.Text = oCust.cFirstName.Trim() + " " + oCust.cLastName;
txtCustAddress.Text = oCust.oAddress.cAddress;
txtCustPhone.Text = oCust.oAddress.cPhone;
txtCustEmail.Text = oCust.oAddress.cEmail;
}
The query runs on the server and the server side business object populates its members. The member data is then passed down to the client side, which can make use of the object as if it were local. If you want to make changes to the data you could simply write data back into the members of the object and then call back to the Web Service with a Save() method call that passes the object back up as a parameter. The business object's Save() method could then simply write the updated data to the database.
Again look at how simple the client code is because we have the ability to directly access this business object retrieved from the server side. We get access to the top level object (oCustomer) and its surrogate object (oCustomer.oAddress). However, understand that what you're getting here is not really a fully self contained object but a persisted copy of it. Figure 13 shows the IntelliSense drop down for the oCust reference and notice that the Load() method is not part of the returned object only the properties were persisted down to the client.
Objects passed around via Web Services really are more like structures: They contain only the data not the actual methods. If you want to access the methods of these objects you need to explicitly wrap them into your Web Service. This also means that you have to manually parse properties out of a returned object and assign them to a local business object if you want to truly simulate object persistence from a client application to a server application. Microsoft will tell you that you should just keep all the business logic on the server and call Web Services to get at it, but I think that's unrealistic for many operations, especially in online/offline scenarios where data maybe stored locally and then synced up later. Still it's hard to complain at the simplicity that even this property persistence mechanism allows it's easy enough to create a method that loads the local object's properties with the values from the returned object.
If you want to understand how this works take a look at the Web Service proxy code again, and you'll see that the Proxy code class implements the returned object's class as part of the proxy code. But notice that the proxy class generated is void of any method signatures. In our example above two classes are actually created: Customer and Address since both objects are used and required.
Passing objects over the wire in this fashion is my favorite way to work with distributed applications because it allows you to take advantage of your business object framework to handle the mundane data access tasks. Instead your client code simply talks to properties of the business object and the data saving is completely left up to the server side code that writes to the database.
Namespaces are a very important feature in .Net that make it possible to separate code from different sources into separate entities to avoid name conflicts. Namespaces are something that the compiler uses to resolve naming conflicts and where to load modules from. Think of namespaces as include directives (without a precompiler) that let the compiler resolve imported symbol names. Reference are used by the linker to actually bind to the libraries when the code is compiled and written out to disk. The important thing to remember is that you need to have both namespace and reference set in order access external components! References typically refer to an assembly or single file that may contain many sub namespaces only the top level reference needs to be added to your project. Namespaces have to be declared explicitly in your source code for each namespace you access. Some exceptions to this are the System namespace, much of which is implicitly available.
Debugging
Now that we have two projects running it's a good time to see some of the cool debugging features that VS.Net provides. In the past debugging Web Service type client/server applications involved having to run multiple instances of development environments and switching back and forth. If one would hang it could take a while to recover and the process in general has always been very messy. In VS.Net debugging foer distributed applications gets much easier. You can simply set breakpoints in your client and server code then run the application. The debugger seamlessly steps from your client code into server code if you have breakpoints set in the server piece! Remote debugging features are also provided although I haven't had a chance to look at this yet for now just being able to debug an locally installed distributed application in a single development environment is a huge improvement.
Summary
Web Services are an integral part of the .Net platform and they make it easy to partition application logic into distributed tiers that can be shared by applications in your own company infrastructure or through the Web community at large. VS.Net makes building and consuming these Web Services very easy and I hope that this article has given you a good idea of how to build and access a Web Service with little effort. I haven't even covered all the features available in Web Services such as the asynchronous call support that allows you to use event based callbacks to retrieve results when they're finished. This article has focused on the basic functionality and how you put it all together. In the coming issues I'll show a practical example of a Web Service based application that puts these concepts I've introduced to use in a more realistic environment. Until then take a look at VS.Net Beta 2 and play around with the development environment and Web Services there's a lot to get used and the sooner you start the sooner you'll feel comfortable in that environment. Until next time
Resources
Source code for this article:
http://www.west-wind.com/presentations/dotnetwebservices/dotnetwebservices.zip
More info on .Net and Web Services:
Discussion areas:
microsoft.public.dotnet.framework.aspnet.webservices (msnews.microsoft.com)
wwSOAP for Visual FoxPro client (can call .Net Web Services):
http://www.west-wind.com/wwsoap.asp
Building SOAP Web Services with West Wind Web Connection article:
http://www.west-wind.com/presentations/xmlmessaging/soapwebservices.htm
Rick Strahl is president of West Wind Technologies on Maui, Hawaii. The company specializes in Web and distributed application development and tools with focus on Windows 2000 and Visual Studio. Rick is author of West Wind Web Connection, a powerful and widely used Web application framework for Visual FoxPro, West Wind HTML Help Builder and co-author of Visual WebBuilder. He's also a Microsoft Most Valuable Professional, and a frequent contributor to FoxPro magazines and books. He is co-publisher and co-editor of CoDe magazine, and his book, "Internet Applications with Visual FoxPro 6.0", is published by Hentzenwerke Publishing. For more information please visit: http://www.west-wind.com/.
White Papers Home |  White Papers | Message Board |  Search |  Products |  Purchase | News |   |