首页    期刊浏览 2025年12月21日 星期日
登录注册

文章基本信息

  • 标题:Creating and Consuming Web Services
  • 作者:Robert P. Lipschutz
  • 期刊名称:ExtremeTech
  • 印刷版ISSN:1551-8167
  • 出版年度:2002
  • 卷号:April 2002
  • 出版社:Ziff Davis Media Inc.

Creating and Consuming Web Services

Robert P. Lipschutz

We pick up from the end of Part II, where we discussed creating, finding, and interrogating Web services. In this segment, we'll begin by walking through a real Web service development process to understand some of the nuances. We built a Price Comparison application using two Web service projects, a Web application (using Web Forms), and a Windows application. We expected to run into real problems if we built a real application. Admittedly, our application is not full-blown, but it did have enough complexity to show us many of the ins and outs of creating Web Services. Similar to what we indicated in Part II, you can install our project on your own development machine. (You should follow the installation instructions included in the Install.txt file.)

The Price Comparison user interface shown below illustrates the Windows user interface for the main part of our application.

OO programmers will usually tackle a problem by designing an object with the right methods and properties to handle the problem. Once the methods and properties are known, the programmer can move on to their implementation. So, not surprisingly, we started with a "User" object. We gave it properties and methods as shown below in Visio UML static structure diagram

Recall our Plane and PlaneDetails example from Part I, where we needed to separate our WebService from our serializable object. Since User is a serializable class for us, and therefore can't be a Web Service, our WebMethods must be placed in AuthService, our valid Web Service. So let's talk first about the AuthService Web Service.

We've already discussed how we create a Web Service, but let's bring in one more wrinkle here. Each Web Service needs a unique namespace as given by the namespace property within the WebService attribute.

<WebService(Namespace:="http://www.thing7.com/AuthService", _
        Description:="The AuthService web service provides " & _
        "the ability to manipulate and access information for " & _
        "the uses of the GrayMarket application.")> _
Public Class AuthService
    Inherits System.Web.Services.WebService

The Namespace property has a default value of "http://tempuri.org" and needs to be changed. This will not work with multiple Web Services. The result is that only one of the web services will show up. The suggested naming standard for Web Services uses your own unique URL, e.g. "http://www.thing7.com" with the Web Service name appended-- e.g. Namespace:="http://www.thing7.com/AuthService"

WebMethods Next we created a WebMethod called CheckUser in the AuthService class, a different method from the CheckUser function in our User object.

    <WebMethod(Description:="{description omitted for readability")> _
    Public Function CheckUser(ByVal Username As String, ByVal Password As String) _
            As User
        Return Thing7_GrayMarketWS.User.CheckUser(Username, Password)
    End Function

The WebMethod CheckUser in AuthService actually calls our CheckUser function in User. We've chosen to place all of the implementation in the User class where we check the database for a user. Keep this pattern in mind for your own projects. We won't show you the code here, as it is quite verbose database connection logic.

Serialization is the process of converting an object or other data type into a form that can be sent across a network (i.e. a data stream). Web Services handle serializing simple data types such as integers, strings, dates, etc. quite well. But you explicitly have to make objects serializable. There's a very nice article on serialization by Rockford Lhotka at Magenic Technologies on the MSDN site called "Object Serialization in Visual Basic .NET".

Notice in our case that our return value for the CheckUser() function is a User object as shown by the "As User" part of the function declaration. For an object to be returned from a WebMethod, it must be serializable.

Let's start with some specific information about what is and isn't serializable using Web Services. The answers lie in the SOAP specification. The SOAP protocol supports all the simple data types such as string, integer, single, double, date, and boolean, so functions that return values of these types are not a problem. Arrays are serializable. Some of the more complex data types such as Collection and Hashtable are not serializable. If you try to serialize an ArrayList, you'll actually get an Array on the consuming side. A very flexible object called DataSet is serializable and is helpful with complex data structures.

As mentioned, if you wish to return your own custom objects, you must declare the class serializable by placing the <Serializable()> attribute tag in front of the class.

<Serializable()> Public Class User

The "" attribute allows the read-write properties of the class to be serialized, so that it can be used as a return value to a WebMethod, or used as an input parameter of a WebMethod. As mentioned earlier, read-only properties are not serialized nor are write-only properties. These limitations can reduce the elegance of the resulting code and requires that you protect properties with separate code on the server side.

We found some interesting ways to get around some of the serialization issues in Web Services. For example, as mentioned, the collection and ArrayList objects are not serializable. If you try to serialize a collection, you'll get an error. If you try to serialize an ArrayList, you actually get an array back. We really wanted to use an ArrayList for our application and did not want to deal with Arrays with the messy resizing code that accompanies them. So here's how we handled that issue...

We have a WebMethod called GetCategoryList() in our application that returns a list of product categories such as computers, printers, monitors, etc. Since we could not return an ArrayList, we instead returned our own custom object called ArrayListWrapper. This allows us to have a public property Coll that permitted us to access the ArrayList contained within.

    <WebMethod(Description:="{description omitted for readability")> _
    Public Function GetCategoryList() As ArrayListWrapper
    ' Code was omitted for readability.

' ArrayListWrapper was created to mimic ArrayList functionality
<Serializable()> Public Class ArrayListWrapper
    Private mColl As ArrayList

    Public Property Coll() As ArrayList
        Get
            Return mColl
        End Get
        Set(ByVal Value As ArrayList)
            mColl = Value
        End Set
    End Property

    Public Sub Clear()
        mColl = New ArrayList()
    End Sub

    Public Sub Add(ByVal Item As Object)
        mColl.Add(Item)
    End Sub
End Class

This also allowed us on the client side to access this object easily without all of the issues with arrays. If you like array programming, by all means, use that technique. We don't!

We declared a private field member called CatList in our frmBrowse code and then used the CatList ArrayListWrapper.

    Private CatList As GrayMarketWebService.ArrayListWrapper

    CatList = SrvPriceCompare.GetCategoryList

    ' list the categories
    For I = 0 To CatList.Coll.GetLength(0) - 1
        Cat = CType(CatList.Coll.GetValue(I), GrayMarketWebService.IdValue)

        Me.lstCategories.Items.Add(Cat.Id & " - " & Cat.Value)
    Next

Constructors are used in object oriented languages like VB .NET to create new instances of objects. The default constructor is indicated by the subroutine name "New", takes no parameters, has no executable code, and creates a default instance of an object. For example, in our User Class the default constructor appears as follows:

    Public Sub New()

    End Sub

Often developers create non-default or parameterized constructors that construct an object based on a set of input parameters. For example, we used a non-default constructor for our User object that took several parameters such as UserId, Username, Password, Firstname, Lastname, and Email.

    Public Sub New(ByVal UserId As String, ByVal Username As String, _
            ByVal Password As String, ByVal Firstname As String, _
            ByVal Lastname As String, ByVal Email As String)
        mUserId = UserId
        mUsername = Username
        mPassword = Password
        mFirstname = Firstname
        mLastname = Lastname
        mEmail = Email
    End Sub

Default constructors must be present in all serializable objects that will be returned from a WebMethod. The default constructor is implicit in VB .NET. Non-default constructors are not available to applications consuming the Web Service. Along the same lines, if you have code in the default constructor, this code will not be executed if the object is constructed on the consuming side. This could be of concern if you want to employ some business logic on the consuming side of you application. And this again reinforces the theme of Web Services, in that you can not enforce server-side code on the consuming client.

You can have a non-default constructor in a serializable object, but it will only be available to other code in the same project. If there is a non-default constructor in a serializable object, you need to explicitly include the default constructor.

One way that we found to get around not having access to constructors is to have a WebMethod construct the object for you. For example:

<WebMethod(Description:="Neat work around to enable parameterized " & _
        "constructor-like functionality")> _
Public Function ReturnNewUser(ByVal UserId As String, _
        ByVal Username As String, ByVal Password As String, _
        ByVal Firstname As String, ByVal Lastname As String, _
        ByVal Email As String) As User
    Return New User(UserId, Username, Password, Firstname, Lastname, Email)
End Function

In addition to the serialization issues we faced, we also found some interesting tidbits around overloading. Overloading allows many methods with the same name and return type to exist with different input parameters. During our development, we were not aware of the overloading problems, so we proceeded to write overloaded functions.

    <WebMethod(Description:="{description omitted for readability}")> _
    Public Function GetProductList(ByVal CategoryId As Integer) As ArrayListWrapper
        Return GetProductByCategoryId(CategoryId)
    End Function

    <WebMethod(Description:="{description omitted for readability}", _
    Public Function GetProductList(ByVal Category As IdValue) As ArrayListWrapper
        Return GetProductByCategoryId(Category.Id)
    End Function

Visual Studio.NET would allow us to write overloaded functions and compile them with no errors or warnings. But when we attempted to add a Web Reference to the Web Service, we started to see problems. The error message below was displayed.

We would have liked to know about the problem at development time rather than when we tried to consume the service. In any event, with some help from Mike Amundsen at EraServer.net, we solved the problem. It turns out you cannot just simply overload methods as you would when working locally with an object

The problem is that the VisualStudio.NET tries to name the two SOAP messages the same since the SOAP message name comes from the function name. The SOAP message is the name used in the XML SOAP protocol for the method. The problem is that SOAP cannot handle duplicate message names representing different things. However, the VS.NET IDE provides a nice workaround through the use of the "MessageName" parameter on the WebMethod attribute. This parameter changes the message name in the SOAP XML protocol so there is no conflict. We suggest that you make this name verbose so that other people that are not using VS.NET can interpret it correctly. Our example below shows how to use the MessageName parameter.

    <WebMethod(Description:="{description omitted for readability}")> _
            MessageName:="GetProductListByCategoryId")> _
    Public Function GetProductList(ByVal CategoryId As Integer) As ArrayListWrapper
        Return GetProductByCategoryId(CategoryId)
    End Function

    <WebMethod(Description:="{description omitted for readability}", _
            MessageName:="GetProductListByCategory")> _
    Public Function GetProductList(ByVal Category As IdValue) As ArrayListWrapper
        Return GetProductByCategoryId(Category.Id)
    End Function

We were happy to have overloading as part of our Web Services arsenal.

In programming, there's nothing worse than an error that goes something like this, "Something is wrong with your code." Although Microsoft does a pretty good job overall, when in comes to Web Services and Web References, we found a lot of ambiguous errors that often frustrated us when trying to solve a problem.

For example, the following situations gave rise to ambiguous errors. Creating a Web Reference to a Web Service with a problem (e.g. overloaded function without using the MessageName property) Updating a Web Reference to a Web Service with a problem Forgetting to update the Web Reference to a Web Service whose interface has changed and then running the code

For example, here is the error for cases 1 and 2.

So do you have any thoughts on what the problem is? Neither did we. And what about the following error we received when we forgot to update our Web Reference before running the code?

While this one gives you a little more information it is still misleading. If you weed through it you will find the statement "The XML element named 'HelloWorld' from namespace 'http://tempuri.org/' references a method and a type. Change the method's message name using the attribute or change the type's root element using the XmlRootAttribute." You might be lead to believe that you have a datatype called "HelloWorld". But, no that is not the case. The problem is that we changed code that changed the interface to the Web Service but never updated the Web Reference.

To see a better-formatted version of this message, right-click on the Web Service where there is a problem, and then click "View in Browser" as seen below.

We've reached the end of Part III. In our final chapter coming soon, we'll cover Web Services platform considerations, plus deploying and publishing Web Services.

Copyright © 2002 Ziff Davis Media Inc. All Rights Reserved. Originally appearing in ExtremeTech.

联系我们|关于我们|网站声明
国家哲学社会科学文献中心版权所有