Поделиться через


Overloading in WebMethods, part I

Overloading and Web Services? Don't Do it!  

Somebody recently asked about the use of Overloaded method names in webservice interfaces. The idea would be to expose a single webmethod that accepts different parameter lists. 

Now, I am aware of the MessageName property of the WebMethod Attribute.

But I generally don't like the idea. 

First, some background. If you haven't seen this article on MSDN, entitled "An Introduction to the Web Services Architecture and Its Specifications"  , updated October 2004, you should check it out. It covers some of the basic principles and philosophy of Web services and is a great foundation.

Applying the idea of overloading to webmethods is like mixing butter and motor oil. You can do it, but the result is confusing and useless. There's a good platform-neutral article on webservices.org that discusses the message-oriented vs. RPC model. One excerpt:

From the point of view of a consumer, binding directly to a Web Service, increases the danger that we begin to view services as objects. This is a mistake - treating the exchange of messages with a service as being equivalent to a local object invocation is inherently incorrect. [ from https://www.webservices.org/index.php/ws/content/view/full/39565 ]

The emphasis in the above is mine. The point is that services and OO must be considered independently. If not, many dangers await you!

So how to build a webservice that accepts different inputs? 

Remember the Fundamentals: Message Orientation

Keep in mind that web services are inherently message-oriented. We are not shipping objects on the wire. The metaphor is: we are shipping messages. On the sending side, the messages can be (and usually are) generated from instances of objects. Once the messages arrive at their destination, again they may be used to instantiate objects. But on the wire, it's messages all the way down.

Given that, here's the way I would do it: construct a W3C XML Schema describing the datatypes for the messages being sent and received. Then, generate types for that Schema, specific to each platform. Then, optionally fill, or not, the fields in those messages. Simple.

In more detail, Let's suppose you have a .NET and a Java system inter-connecting via webservices. A request message gets passed from requester to receiver - it does not matter which one is which. The message might include an order ID, a priority, some authentication information, and a conversation ID. Optionally a Response Message gets passed back. [in one-way web service requests, there is no response message]. But most people don't think or design in XSD. If you're like me, you prototype your interfaces in code. In C# code you might code something like this series of classes:

public enum PriorityLevel {
Normal=0,
High=1,
Urgent=2,
Lazy=3
}

// this is the request message
public class RequestMessage1 {
public int orderID;
public PriorityLevel priority;
public string OnBehalfOf;
public int conversationID;
}

public class Order {
public int ID;
public int customerID;
// public OrderItems[] i;
// etc
}

public enum Confidence {
High,
Normal,
Low

}

// this is the response message
public class ResponseMessage {
public Order o;
public Confidence c;
// etc
}

By compiling this C# code into a DLL, then running xsd.exe on the resulting DLL, you can generate an XSD that describes these types. It looks like this:

<xs:schema elementFormDefault="qualified"
xmlns:xs="https://www.w3.org/2001/XMLSchema">
<xs:element name="PriorityLevel" type="PriorityLevel" />
<xs:simpleType name="PriorityLevel">
<xs:restriction base="xs:string">
<xs:enumeration value="Normal" />
<xs:enumeration value="High" />
<xs:enumeration value="Urgent" />
<xs:enumeration value="Lazy" />
</xs:restriction>
</xs:simpleType>
<xs:element name="RequestMessage1" nillable="true" type="RequestMessage1"
/>
<xs:complexType name="RequestMessage1">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="orderID" type="xs:int"
/>
<xs:element minOccurs="1" maxOccurs="1" name="priority"
type="PriorityLevel" />
<xs:element minOccurs="0" maxOccurs="1" name="OnBehalfOf"
type="xs:string" />
<xs:element minOccurs="1" maxOccurs="1" name="conversationID"
type="xs:int" />
</xs:sequence>
</xs:complexType>
<xs:element name="Order" nillable="true" type="Order" />
<xs:complexType name="Order">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="ID" type="xs:int" />
<xs:element minOccurs="1" maxOccurs="1" name="customerID"
type="xs:int" />
</xs:sequence>
</xs:complexType>
<xs:element name="Confidence" type="Confidence" />
<xs:simpleType name="Confidence">
<xs:restriction base="xs:string">
<xs:enumeration value="High" />
<xs:enumeration value="Normal" />
<xs:enumeration value="Low" />
</xs:restriction>
</xs:simpleType>
<xs:element name="ResponseMessage" nillable="true" type="ResponseMessage"
/>
<xs:complexType name="ResponseMessage">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="o" type="Order" />
<xs:element minOccurs="1" maxOccurs="1" name="c" type="Confidence" />
</xs:sequence>
</xs:complexType>
</xs:schema>

You can now modify, adjust, tweak, or extend this XSD. [The alternative is to simply write XSD from the beginning, without prototyping the messages in C#. If you are fluent in XSD or have tools like XmlSpy, then go for it! ]

At some point, you're happy with your schema definition. Take this schema and then generate platform-specific types with it. Also, import this XSD into the web service interface definition (WSDL). [ Link on importing: https://devresource.hp.com/drc/slide_presentations/schemaWSDL/index.jsp ]

Then generate proxy classes (stubs) for the client from this WSDL. Also generate server-side skeleton classes for the server from the same WSDL. What?! You've never heard of the /server switch to the wsdl.exe utility? Well, now you know! There are equivalent utilities in various Java-based webservices stacks, like AXIS, GLUE, and WASP. This approach of designing the datatypes and interface and deriving or generating the implementation skeleton from those definitions is called "WSDL first" or sometimes "contract first" design, and is critical for interop among heterogeneous systems.
https://www.google.com/search?hl=en&num=30&q="contract+first"+"web+services"

Consider just the RequestMessage, it looks like this in (generated) C# code:

public class RequestMessage1 {
public int orderID;
public PriorityLevel priority;
public string OnBehalfOf;
public int conversationID;
}

(NB: the actual code will be attributed with XmlSerialization attributes).

The requester application can then instantiate the RequestMessage1, optionally fill or set the fields as necessary, and then invoke the service. Like so:

  // get a proxy to the remote webservice
MyNamespace.MyWebService svc= new MyNamespace.MyWebService () ;

// instantiate and fill the request
MyNamespace.RequestMessage1 m = MyNamespace.RequestMessage1 () ;
m.orderID= 42;
if (some condition) m.priority= PriorityLevel.High;
if (a conversation exists) m.conversationID= 172;

// invoke (implicitly passing the request message)
MyNamespace.ResponseMessage1 r = svc.MyMethod(m);

The request and response messages are always the same type, but the fields within them may be optionally set, or not. On the server side, the code can behave differently depending on the content of the incoming request message. This isn't overloading at all, is it? But it does allow different behavior depending on different incoming messages. These messages can be as complex as you like; and you can construct them so that only the properties that are set are sent across the wire.

-Dino

Comments

  • Anonymous
    November 05, 2004
    Part II, wherein I take back everything I said in part I.