Microsoft & .NETVisual BasicBuilding Distributed Apps? Use XML Web Services, Not Remoting (Mostly)

Building Distributed Apps? Use XML Web Services, Not Remoting (Mostly)

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

If you have been wondering whether to use XML Web services or .NET Remoting, here’s a simple answer: Use XML Web services most of the time. XML Web services are the same as marshal-by-value .NET Remoting, but using them is much easier than using Remoting from scratch.

Marshal-by-value remoting (XML Web services) means that you get XML-serialized data from your remote server. Sometimes, you may need dynamic event behavior or you may not want to move massive amounts of data and need a little more control. In this instance, you peel back the XML Web services layer and start building directly on top of .NET Remoting with marshal-by-reference objects and event sinks. Mostly not, though.

This article looks at the basic technologies that support XML Web services and provides a brief demonstration of how to produce and consume Web services.

First Things First

Before learning the definition of XML Web services, take a look at Web services from a conceptual perspective. What is a Web service? I’m glad you asked.

A Web service is a class, but not a low-level class. For example, a Web service might have a method such as AddItemToOrder, BookFlight, or RetrieveDrivingDirections, but a properly used Web service would not include things like SetCustomerName or CalculateAge. A Web service could include low-level operations, but Web services send XML data across an HTTP connection and serializing data and HTTP is not a comparatively cheap operation. Therefore, using Web services to set the equivalent of property values is a misuse of them.

Conceptually, a Web service is a natural façade. The term façade is an important architectural concept that represents high-level, interface-exposing capabilities. In the context of the UML, think of a façade as a Web service that supports use cases. (If you are not familiar with façades, pick up a copy of Erich Gamma and company’s book Design Patterns from Addison-Wesley.) In the meantime, think of a Web service as a place to expose high-level capabilities instead of low-level operations.

When you decide to use Web services, consider the Web service a provider of the primary use cases (a.k.a. macro features) of your system.

Consuming and Producing Web Services

A programmer can assume either the Web service producer or Web service consumer role. The skills for producing a Web service are slightly different from those for consuming Web services. When creating a Web service, you are a producer and must play that part. Once you’ve created the Web service, you are a consumer. The capabilities the Web service offers represent a black box, and your tasks as consumer are the same whether you produced the Web service or not.

Producing Web Services

The key to producing a useful Web service is knowing your audience. What are their objectives? What do you need from them to help them meet their goals, and what must you have from them to meet your goals?

It is also important to know that Web service consumers are generally not end users. Web services consumers are often business-to-business (b2b) customers. For example, Dell Computers might use Web services from UPS to offer package tracking. Dell Computers might produce Web services to offer financial information to Dell Financial Services. In the former scenario, Dell might ship your new PC (you’re getting a Dell, dude!) via UPS and enable you to track your PC through its Web site. Dell Financial Services might offer you credit, and Dell Computers can request payment through DFS’s Web services.

Hopefully, these analogies help you see what a good level of abstractioin for Web services is. Note that XML Web services do not technically enforce any particular level of abstraction; what I am suggesting is a suitable level of abstraction.

Now, how do you produce a Web service?

Producing a Web Service in VB.NET

Due in no small part to the .NET Framework, creating a Web service is no harder than creating a console application. From your perspective, you are just creating a project and adding some methods. Specific attributes defined by the .NET framework take care of adding all of the extra details you need to convert your ordinary class and assembly into a Web service. Interestingly enough, Visual Studio .NET’s project templates even take care of adding the attributes necessary to distinguish regular classes and methods from Web service classes and Web methods.

To create a plain vanilla Web service, take the following steps:

  1. Open Visual Studio .NET.
  2. Select File|New|Project.
  3. From the New Project dialog box, select the ASP.NET Web service template and click OK.

The IDTExtensibility2 interface supports VS.NET wizards. When you open an existing project template, a pre-defined wizard reads text files that were added to your file system when you installed VS.NET. These text files contain replaceable parameters, which are filled in to create project elements such as configuration files and source code files with attributes for XML Web services.

The previously defined steps employ these template files and wizard to create a Web service project. If you uncomment the four lines of code included in the project template, you already have a complete Web service, albeit a do-nothing Hello World-style Web service.

Uncomment the code shown in Listing 1 and select Debug|Start to run the Web service. For now, ignore all of the code except what is in boldface near the end of the listing.

Listing 1: The basic sample Web service included with VS.NET’s template Web service is our friend HelloWorld.

Imports System.Web.Services

<System.Web.Services.WebService(Namespace := _
       "http://tempuri.org/WebService1/Service1")> _
Public Class Service1
    Inherits System.Web.Services.WebService

#Region " Web services Designer Generated Code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Web services Designer.
        InitializeComponent()

        'Add your own initialization code after the
        'InitializeComponent() call

    End Sub

    'Required by the Web services Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Web
    'services Designer
    'It can be modified using the Web services Designer.
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()>
        Private Sub InitializeComponent()
        components = New System.ComponentModel.Container()
    End Sub

    Protected Overloads Overrides Sub Dispose(ByVal disposing _
                                              As Boolean)
        'CODEGEN: This procedure is required by the Web services
        'Designer
        'Do not modify it using the code editor.
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

#End Region

    ' 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, ensure that the .asmx file is the
    ' start page and press F5.
    '
    <WebMethod()> _
    Public Function HelloWorld() As String
       Return "Hello World"
    End Function

End Class

When you run a Web service, VS.NET creates a default Web page. This page, generally just for testing purposes, offers a simple interface for adding parameters and invoking the Web methods. To test the Web service:

  1. Click the hyperlinks. You’ll see one for each WebMethod (see Figure 1).
  2. Provide any parameters, if required.
  3. Click the Invoke button (see Figure 2).

Figure 1: Each WebMethod is listed in the generated test page.

Figure 2: Click the Invoke method to test the selected WebMethod.

The result of invoking a Web method using the test interface is XML (see Figure 3). (This should be your first clue that Web services are not meant to be used in this manner, only tested.)

Figure 3: When you invoke a Web method using the test page, XML is returned.

I hope it is clear from the example that all a Web service and its Web methods are is a class and methods that convert the values passed to and from into XML text. The reason for this is that text and the World Wide Web are the oldest and best of friends. Text is passed easily across the Internet and XML makes that text self-describing.

If programmers had to write parsers to decipher all of the XML mumbo jumbo, XML Web services would represent only a partial solution. However, the .NET framework knows how to easily produce and consume XML Web services and convert XML back into usable code elements. This means that, in practice, you just write code to produce and consume XML Web services and leave the hard part of serialization and HTTP to the .NET Framework.

Consuming a Web Service

When you want to consume a Web service, you actually use some pretty advanced technologies. You use UDDI (Universal Discovery and Description Indexes), WSDL (whizdahl, or Web services Discover Language), and SPROXY. UDDI is a directory service that helps identify Web services. WSDL helps figure out what each service offers, and SPROXY uses the CodeDOM to generate proxy classes, which in turn makes it easy to consume the services a Web service offers. Collectively, these technologies mean all you as a Web service consumer have to do is write code to create instances of the classes and invoke methods offered by the service.

To consume the sample Web service with the Hello World Web method, take the following steps:

  1. Add any kind of project to a new solution or the solution containing the sample Web service project. It doesn’t matter which, but I added a console application to the same solution.
  2. Select the new project, and then select Project|Add Web Reference. This will open the Add Web Reference dialog (see Figure 4). This is your key to finding Web services on your PC or anywhere else.
  3. Select Web services on the local machine. If you want a Web service produced by someone else, select the UDDI Directory option (see Figure 4).
  4. Selecting the first option results in a search for .asmx files on the IIS Server on your PC. (I haven’t tried this with the Apache Web server, but one would hope the result is the same.)
  5. From the list of services, pick the one you just created and click the Add Reference button (see Figure 5).

Figure 4: The Add Web Reference dialog helps you find local Web services or those anywhere in the world.

Figure 5: Add a reference to the desired Web service.

After the last step, you are ready to invoke the Web methods exposed by this service.

Essentially, adding a Web reference means that WSDL, in conjunction with SPROXY, will generate some code. This code will include a namespace and a wrapper class. The wrapper class will condense the code needed to invoke each Web method into a simple method call. SPROXY also uses Reflection to create local versions of composite returned types. For example, if the consumed Web method returns a Customer class, SPROXY will generate a flattened, properties-only version of that class. In generated types, properties are flattened into public fields and other elements are removed. What you end up with is basically a structure, but types defined by the Web services producer also are effectively available in your consumer code. Without the proxy classes, your code would not recognize types defined by the Web service and would not compile.

Some developers mistakenly deploy the assemblies that contain the description of objects returned by Web methods to client machines. This is a critical mistake. By deploying the assemblies on client machines, customers can use free tools such as Anakrino or Reflector to disassemble and decompile these assemblies and learn everything about your business. Don’t distribute these assemblies. Instead, permit consumers—whether internal or external—to see only the SPROXY-generated composite types. The effect is two-fold: Consumers can’t disassemble your business code and producers are reminded to create Web services that provide macro-level behaviors that satisfy consumer needs.

Invoking an Imported Web Method

After importing a Web service, invoking its Web methods is the same whether the Web method accepts or returns values and whether or not these values are simple or composite types. Number and complexity do not matter at this point. From the consumer’s perspective, it is just a matter of creating objects and calling methods.

Note: If you are writing software for an internal customer and performance is an issue, you might want to bypass Web services altogether. Because optimization is a task best performed near the end of development, Web services are better suited to external or distributed consumers as opposed to internal, local consumers.

As is true with any namespace and class, you need to have access to the assembly and its namespaces before you can use what it offers. The namespace for a Web service is generally some variation on the host name. For example, if you have IIS installed on your workstation, create a Web service on your workstation and import that Web service. WSDL/SPROXY will generate a namespace such as localhost or localhost(n). These variations in name are used because WSDL/SPROXY creates a unique folder in your project for the Web service, and this folder contains .map, .vb, .disco, and .wsdl files.

Files with a .map extension indicate the name and location of all of the related files. The .vb file is actually the generated source code. (Click the Show All Files toolbar button in the Server Explorer to see the .vb source code file.) The .vb source file contains wrappers for synchronous and asynchronous calls to the Web service and generated definitions for composite types if they are relevant to the Web service. You can explore the .vb file and even modify it if you like, but generally that is not wise. Listing 2 shows the generated source code for the referenced HelloWorld Web method.

Listing 2: All of this complicated-looking code is generated by SPROXY, which is invoked by the WSDL utility.

'------------------------------------------------------------------
' <autogenerated>
'     This code was generated by a tool.
'     Runtime Version: 1.1.4322.573
'
'     Changes to this file may cause incorrect behavior and will
'     be lost if the code is regenerated.
' </autogenerated>
'------------------------------------------------------------------

Option Strict Off
Option Explicit On

Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization

'
'This source code was auto-generated by Microsoft.VSDesigner,
'Version 1.1.4322.573.
'
Namespace localhost2
    
    '<remarks/>
    <System.Diagnostics.DebuggerStepThroughAttribute(),  _
     System.ComponentModel.DesignerCategoryAttribute("code"),  _
     System.Web.Services.WebServiceBindingAttribute(  _
          Name:="Service1Soap", [Namespace]:=  _
          "http://tempuri.org/WebService1/Service1")> _
    Public Class Service1
        Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
        
        '<remarks/>
        Public Sub New()
            MyBase.New
            Me.Url = "http://localhost/WebService1/Service1.asmx"
        End Sub
        
        '<remarks/>
        <System.Web.Services.Protocols.SoapDocumentMethodAttribute(
             "http://tempuri.org/WebService1/Service1/HelloWorld",
             RequestNamespace:="http://tempuri.org/WebService1/Service1",
             ResponseNamespace:="http://tempuri.org/WebService1/Service1",
             Use:=System.Web.Services.Description.SoapBindingUse.Literal,
             ParameterStyle:=  _
                  System.Web.Services.Protocols.  _
                  SoapParameterStyle.Wrapped)>  _
        Public Function HelloWorld() As String
            Dim results() As Object = Me.Invoke("HelloWorld", _
                                                New Object(-1) {})
            Return CType(results(0),String)
        End Function
        
        '<remarks/>
        Public Function BeginHelloWorld(ByVal callback _
               As System.AsyncCallback, ByVal asyncState _
               As Object) As System.IAsyncResult
            Return Me.BeginInvoke("HelloWorld", New Object(-1) {}, _
                                  callback, asyncState)
        End Function
        
        '<remarks/>
        Public Function EndHelloWorld(ByVal asyncResult _
               As System.IAsyncResult) As String
            Dim results() As Object = Me.EndInvoke(asyncResult)
            Return CType(results(0),String)
        End Function
    End Class
End Namespace

The XML Web services technology in .NET manages all of this code for you. All you have to do is treat the Web service like a black box and interact with its external interface. Listing 3 shows how easy it is to call the HelloWorld Web method.

Listing 3: Call a Web service’s methods like any other method, by creating an instance of the class and invoking the method.

Module Module1

    Sub Main()
        Dim Service As localhost2.Service1 = New localhost2.Service1
        Console.WriteLine(Service.HelloWorld())
        Console.WriteLine("press enter")
        Console.ReadLine()
    End Sub

End Module

In the code, you declare an instance of the Web service, including the namespace. By default, the Web service is named Service1. Like any other object, I need to create an instance of the class and then simply invoke the class’s behaviors.

Advanced Web Service Information

To do even more advanced things with Web services, you need to know more. The example invoked the Web service synchronously. This means the next line of code doesn’t execute until the Web method returns. If you have a long-running process, you may want to invoke the Web method asynchronously. Asynchronous invocation permits your code to go and do other things without waiting for the Web service to return.

To use asynchronous Web services, you need to be comfortable with creating and using delegates. You also need to remember that the asynchronous Web method returned comes back on a separate thread from the invoking thread. You need to be comfortable with the concept of marshalling data across threads by using the Invoke method and more delegates.

Also, you can pass and return composite types across Web services, but by default the client application works with a flattened, properties-only version of the composite type, except in the case of ADO.NET DataSets. You also can typecast a flattened Web service-defined proxy class to a class with methods, but this means deploying your business classes to consumers or consumers creating their own business classes. Deploying business classes is not recommended.

Finally, you could use Dotfuscator to obfuscate—scramble the MSIL into gibberish—to prevent consumers from using Anakrino or Reflector to decompile your business classes, but again, you may not always be able to get your business classes deployed to consumer machines. Hence, it is better to design applications that use Web services in such a manner as to not need to deploy business classes.

XML Web Services Will Do

XML Web services in .NET are built on top of .NET Remoting. For all intents and purposes, a Web service is marshal-by-value .NET Remoting. The rumor mill also suggests that .NET Remoting may be completely concealed by XML Web services in the future, making advanced features of Remoting like event sinks available using Web services.

I hope you are more comfortable with XML Web services. Now, when you have to choose between Remoting and Web services, you are prepared to make a decision. Most of the time when you create distributed applications, XML Web services will do.

Biography

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his book Visual Basic .NET Power Coding from Addison-Wesley, and his upcoming book UML DeMystified from McGraw-Hill/Osborne (Spring 2005). Paul is also the founder and chief architect for Software Conceptions, Inc, founded 1990. He is available to help design and build software worldwide. You may contact him for consulting opportunities or technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.

Copyright © 2004 by Paul Kimmel. All Rights Reserved.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories