Architecture & DesignCombine Polymorphism and Web Services

Combine Polymorphism and Web Services

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

Happy New Year!

You may be aware of polymorphism, and you probably know something about Web services by now too. But, what about polymorphism across Web services? This article reviews polymorphism, demonstrates XML Web services, and most importantly, shows you how to combine polymorphism and Web services.

Polymorphism

Those very experienced with object-oriented programming (OOP) are comfortable with polymorphism, but not everyone is experienced with OOP. If you are in the former group, skip ahead to the section “XML Web Services.” If you are in the latter group, keep reading.

Before object-oriented languages, if one wanted to print different data types, one would write methods such as PrintInteger(int i), PrintString(string s), and PrintFloat(float f). That is, one had to differentiate the behavior and data type by name because a pre-OO language such as C wouldn’t permit one to write methods with the same name, even if their argument types were different.

The advent of C++ permitted method overloading—among other things. Thus, one could write Print(int i), Print(string s), and Print(float f), and based on the data type, the code would call the correct Print method. Method overloading is simply supported by a name-mangling scheme where the compiler creates unique names internally by replacing the names with bits and pieces from the name and argument list. So, while you code Print(1), the compiler may internally rename the Print method with a prefix derived from the parameter type so that Print(1) becomes i_Print(1).

Method overloading is a form of polymorphism. Name mangling is a mechanism that supports method overloading. More commonly, polymorphism is associated with inheritance. Inheritance is where a new class (called the subclass) gets part of its definition from the inherited class (called the superclass) and adds new information. If you overload methods in the same class, the data types must be different. If you overload methods in an inheritance relationship, methods in the subclass and superclass may have the same signature, and the mangler produces the same mangled name.

For instance, suppose a superclass defines a Print(int i) method and a subclass it inherits from also defines a Print(int i) method. Use polymorphism to call Child.Print(int) when you have an instance of Child but Parent.Print(int) when you have an instance of Parent. This is inheritance polymorphism: the same name and signature but different classes.

Inheritance polymorphism uses an additional mechanism in conjunction with name mangling. The compiler adds methods to an array of methods called a virtual methods table (VMT). Each class has an index into the VMT, so when Print(int) is called, the compiler is routed to the VMT for the Print methods and the class’s internal index (its Delta or ?) is used to index to a slot offset from the beginning of the VMT plus the Delta. This way, the correct implementation of the method is called. The compiler manages all VMT indexing and class Deltas.

In a nutshell, polymorphism enables you to define many method forms with very similar names, which yields nice name semantics. OOP compilers, in turn, will figure out which method you mean based on the class of the caller. One of polymorphism’s best benefits is that you no longer have to write this kind of code (using pseudo code here):

If type of arg is integer then
  PrintInteger(arg)
Else if type of arg is string then
  PrintString(arg)
Else if type of args is float then
  PrintFloat(arg)

You simply write:

Print(arg)

The compiler and its polymorphic mechanisms figure out the correct version of the print method to call by generating a methods-indexing scheme that is for practical purposes equivalent to the conditional code above.

For an authoritative description of the inner workings of OOP idioms from the language’s perspective, pick up Bjarne Stroustrup’s The C++ Programming Language from Addison Wesley. Many OOP languages use mechanisms very similar to the C++ implementation.

XML Web Services

If you are pretty comfortable with the mechanics of XML Web services and the motivation for using them, skip to the next section: “Supporting Polymorphism with Web Services.”

In the past ten years or so, distributed applications have become more prevalent. As is common with many kinds of engineering, software goes through a period of invention and then standardization. XML Web services are a standardization based on the open protocols of HTTP and XML. XML Web services do not belong solely to Microsoft, but Microsoft does offer an implementation of XML Web services based on the .NET Framework and its attributes.

The basic idea is that you write code to add the WebServiceAttribute to classes representing the Web service and the WebMethodAttribute to the methods in those classes representing Web methods—or methods you want to permit consumers to call. Microsoft technology uses reflection and code generation to generate proxy types and code that make calling these distributed services and methods easy. In addition to generating proxy code, the .NET Framework and Visual Studio include a wizard that stubs out a Web service and Web method for you.

To create a Web service, run Visual Studio and select File|New|Project. Pick the ASP.NET Web service applet from the Templates list in the New Project dialog (see Figure 1).

Figure 1: Create your first Web service with point-and-click ease.

To run the sample Web service and Web method, uncomment the HelloWorld sample method that the project template wizard created and run the solution. For more information on producing, consuming, and using XML Web service tools in general, see previous VB Today columns, particularly Building Distributed Apps? Use XML Web Services, Not Remoting (Mostly) from December 2004.

Supporting Polymorphism for XML-Generated Proxy Classes

Now, turn your focus to the purpose of this article.

When you define parameters and return arguments for Web methods, a utility called Web Services Discovery Language (or whizdahl) invokes another tool called SPROXY. SPROXY uses reflection and the CodeDOM to figure out a definition for the types declared in your Web methods and then generates proxy classes for compound types. For example, if you have a class named Person, SPROXY will generate a Person class when a consumer uses the Web service. The benefit is that Web service producers don’t have to ship their proprietary code to consumers for consumers to use their code. SPROXY does the work for them. Using proxy code permits businesses to protect proprietary business rules while still selling access to the overlying features of those rules.

Here are examples of a Person class (see Listing 1) and a generated Person proxy (see Listing 2).

Listing 1. The Person Class Behind the Web Service

public class Person
{
private string name;
  public Person(){}

  public Person(string name)
  {
    this.name = name;
  }

  public string Name
  {
    get{ return name; }
    set{ name = value; }
  }

  public string GetUpperName()
  {
    return name.ToUpper();
  }

  public string UpperName
  {
    get{ return GetUpperName(); }
    set{}
  }
}

Listing 2. The Proxy Version of the Person Class as Generated by SPROXY

public class Person
{
/// <remarks/>
        public string Name;
}

As you can see, neither version contains any proprietary information. Web services don’t judge or even require that you have proprietary technology to preserve; preserving proprietary business rules is simply a byproduct, because consumers get a proxy class that for all intents and purposes is a structure with no methods.

What SPROXY does roughly is reflect the Web method’s types and convert public properties to public fields. This is sufficient for sending and receiving data between client and server via the Web service.

In addition, things such as typed collections are converted to typed arrays. For instance, a PersonCollection that inherits from System.Collections.CollectionBase (see my book Visual Basic .NET Power Coding from Addison Wesley for more on typed collections) would be proxy-generated as an array of Person, or Person(). Here is the rub: What if the Person class were an abstract class and the PersonCollection was intended to be used as a collection of anything derived from Person, such as Employee or Customer? Without some special help, XML Web services would generate a Person proxy class but would not know anything about Employee or Customer (see Listing 3). This technically means that if you returned an array of Employees to satisfy the Person collection, the consumer application would compile but blow up at runtime.

Listing 3. Contains a Definition for a PersonCollection and Person, Employee, and Customer Classes

using System;
using System.Reflection;
using System.Diagnostics;
using System.Xml.Serialization;

namespace BusinessCollections
{
  [Serializable()]
  public class PersonCollection : System.Collections.CollectionBase
  {
    public static PersonCollection CreateNew()
    {
      PersonCollection persons = new PersonCollection();
      persons.Add(new Person("Paul"));
      persons.Add(new Customer("David", "Cadyville"));
      persons.Add(new Employee("Kathy", 50000M));
      
      return persons;
    }

    public Person this[int index]
    {
      get{ return (Person)List[index]; }
      set{ List[index] = value; }
    }

    public int Add(Person value)
    {
      return List.Add(value);
    }

    public PersonCollection Select(Type type)
    {
      PersonCollection persons = new PersonCollection();
      foreach(Person p in List)
      {
        if( p.GetType().Equals(type))
          persons.Add(p);
      }

      return persons;
    }

    public void Dump()
    {
      
      foreach(Person person in this)
      {
        Debug.WriteLine(string.Format("Type: {0}",
                                      person.GetType().FullName));
        PropertyInfo[] properties = person.GetType().GetProperties();
        foreach(PropertyInfo p in properties)
        {
          try
          {
            Debug.WriteLine(string.Format("Name: {0}, Value: {1}",
              p.Name, p.GetValue(person, null)));
          }
          catch
          {
            Debug.WriteLine(string.Format("Name: {0}, Value: {1}",
                                          p.Name, "unknown"));
          }
        }
      }
    }
  }

  
  public class Person
  {
    private string name;

    public Person(){}

    public Person(string name)
    {
      this.name = name;
    }

    public string Name
    {
      get{ return name; }
      set{ name = value; }
    }

    public string GetUpperName()
    {
      return name.ToUpper();
    }

    public string UpperName
    {
      get{ return GetUpperName(); }
      set{}
    }

  }

  public class Employee : Person
  {
    private decimal salary;

    public Employee() : base(){}
    public Employee(string name) : base(name){}
    public Employee(string name, decimal salary) : base(name)
    {
      this.salary = salary;
    }

    public decimal Salary
    {
      get{ return salary; }
      set{ salary = value; }
    }
  }

  public class Customer : Person
  {
    private string city;

    public Customer() : base(){}
    public Customer(string name) : base(name){}
    public Customer(string name, string city) : base(name)
    {
      this.city = city;
    }

    public string City
    {
      get{ return city; }
      set{ city = value; }
    }
  }
}

The important part of Listing 3 is that Employee and Customer are derived from Person, but the Web service knows only about person collections. Also, note that PersonCollection creates an instance of Person, Employee, and Customer in the static method PersonCollection.CreateNew for demonstration purposes.

If you were to write a WebMethod that returned an instance of a PersonCollection called GetPerson (see Listing 4), SPROXY would generate only one proxy class, Person, and the return type would become Person().

Listing 4: A WebMethod to Return an Instance of the PersonCollection

[WebMethod]
public PersonCollection GetPeople()
{
  return PersonCollection.CreateNew();
}

If Listing 4 is all you offer the consumers, their code will compile but they will get a runtime SoapException when the Person array returned from the Web method is initialized. Ultimately, because you defined other types of Person-derived classes, you need to make those types available to your Web service consumers too.

Now that you understand all of the elements of the problem, the fix is easy. Use the XmlIncludeAttribute defined in System.Xml.Serialization to specify the other types that the consumer will need proxy classes for as well. Use the XmlIncludeAttribute on the class header itself, initializing it with the Type object for each additional type for which proxies are needed. (By convention, drop the Attribute suffix when applying attributes.) Listing 5 shows the class header of the Web service (the class containing GetPeople) in bold font with an XmlIncludeAttribute for each of Person, Customer, and Employee.

Listing 5: Use XmlInclude to Ensure Child-types Are Defined for Web Service Consumers.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using BusinessCollections;
using System.Xml.Serialization;

namespace Service
{
  /// <summary>
  /// Summary description for Service1.
  /// </summary>
  [XmlInclude(typeof(Customer)),
  XmlInclude(typeof(Person)), XmlInclude(typeof(Employee))]
  public class Service1 : System.Web.Services.WebService
  {
    public Service1()
    {
      //CODEGEN: This call is required by the ASP.NET Web services
      //Designer
      InitializeComponent();
    }

    [Component Designer generated code]
    // 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 PersonCollection GetPeople()
    {
      return PersonCollection.CreateNew();
    }
 
  }
}

To test the Web service and Web method, create a console application. Select Project|Add Web Reference, and add the Web service above. Declare an instance of the Person() array and invoke the Web method.

What Have You Learned?

You have just examined polymorphism in the context of XML Web services. You learned that proxy types don’t include methods, and you also learned that child types are not necessarily generated for Web service consumers, which can break inheritance relationships if you are not careful.

If you include child types specifically as arguments to Web Methods, SPROXY will create those types for consumers. If not, the XmlIncludeAttribute will instruct the XML Web services tools to generate additional types as specified in the XmlIncludeAttribute.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET and is a Microsoft MVP. 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