Building HTTP-Based Web Services with .NET

There is a sense out there that it is really easy to expose a web service through the .NET framework if it uses SOAP but not if it uses HTTP. This is not entirely untrue, because there are some bugs in Microsoft's implementation of the HTTP binding for WSDL. Nevertheless it is relatively esay to work around these and at the same time you can take more control over your web service's behaviour and performance.

I want to clear up the misapprehension that SOAP provides the type system for web services messages in the .NET Framework. That is not true. XML Schema provides the type system for individual XML messages, and WSDL describes complete services. SOAP is hardly relevant.

Microsoft's "wsdl.exe" tool will convert WSDL code into C#. It can actually generate HTTP-based proxies but they will sometimes be quite limited. For instance, Microsoft's standard Web services tools will not generate an HTTP wrapper for services that allow complex types as input, even though HTTP has no problem with complex input (using POST). Microsoft's web service generation tools also have poor support for many sophisticated HTTP techniques. Rather than support these features they push you toward using HTTP in inappropriate ways (i.e. doing get-like things over SOAP RPC).

Although I encourage you to use "wsdl.exe" where it works, I want to describe how you would use the .NET Framework when it doesn't do what you need for true HTTP web services

Our example application will use a book store vocabulary that happens to ship with the .NET Framework. Here is an example book store document:

<bookstore>
  <book genre="autobiography" publicationdate="1981" ISBN="1-861003-11-0">
    <title>The Autobiography of Benjamin Franklin</title>
    <author>
      <first-name>Benjamin</first-name>
      <last-name>Franklin</last-name>
    </author>
    <price>8.99</price>
  </book>
  <book genre="novel" publicationdate="1967" ISBN="0-201-63361-2">
    <title>The Confidence Man</title>
    <author>
      <first-name>Herman</first-name>
      <last-name>Melville</last-name>
    </author>
    <price>11.99</price>
  </book>
</bookstore>

Microsoft also provides a W3C XML Schema definition that you can use to describe bookStores. That's important because .NET's XML reading capabilities revolve around XML Schema. SOAP is not important for working with XML in the .NET Framework.

For instance to generate a .NET class that wraps some particular XML vocabulary, you just run this command

xsd /classes books.xsd

This will generate a set of C# classes representing these books. These classes are the key to working with the "books.xml" XML vocabulary. Here's a handy C# function that can deserialize any XML that has an appropriate handler:

puobject parseXML(Stream str, Type type){
    XmlSerializer serializer = new XmlSerializer(type);
    XmlReader reader = new XmlTextReader(str);
    return serializer.Deserialize(reader); 
}

For instance if someone sent you a bookStore document over the Web, you could use the following code to parse it into an object:

    Stream requestStream = con.Request.InputStream;
    Type type = typeof(bookstoreType); 
    bookstoreType set = (bookstoreType)parseXML(requestStream, type);

For instance here is a function that finds the most expensive book in a bookStore list that has been sent from the client:

bookType findExpensive(System.Web.HttpContext con){
    Stream requestStream = con.Request.InputStream;
    Type type = typeof(bookstoreType); 
    bookstoreType set = (bookstoreType)parseXML(requestStream, type);
    bookType []bookList = set.book;
    decimal max=0;
    bookType winner=null;
    for(int i=0; i<bookList.Length;i++){
        bookType current = bookList[i];
        if(current.price>max){
            max = current.price;
            winner = current;
        }
    }
    return winner;
}

Here's how you might use that function in an ASP.NET page:

<?xml version="1.0"?>
<title><%=findExpensive(Context).title%></title>

Voila! You've got a web service that both accepts and generates XML!

If you do not use WSDL, and use straight HTTP calls, the client-side of this application looks like this:

static void postData(bookstoreType store){
        XmlSerializer serializer = new 
              XmlSerializer(typeof(bookstoreType));
        WebRequest req = WebRequest.Create("http://localhost:8000/foo.aspx");
        req.Method = "POST";
        req.ContentType = "text/xml";
        Stream reqstream = req.GetRequestStream();
        serializer.Serialize( reqstream, store );
        reqstream.Close();
        Stream respstream = req.GetResponse().GetResponseStream();
        booktitleType title = (booktitleType)parseXML(respstream, typeof(booktitleType));
        Console.WriteLine(title.Text[0]);
        respstream.Close();
    }

You could also use WSDL and then this would all compress down to basically a constructor and a single method call.

Here is the complete code for an ASP.NET-based Web Service.

	 <%@ Page Language="C#" ContentType="text/xml"%>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Xml.Serialization" %>
<script language="C#" runat="server">

object parseXML(Stream str, Type type){
    XmlSerializer serializer = new XmlSerializer(type);
    XmlReader reader = new XmlTextReader(str);
    return serializer.Deserialize(reader); 
}

bookType findExpensive(System.Web.HttpContext con){
    Stream requestStream = con.Request.InputStream;
    Type type = typeof(bookstoreType); 
    bookstoreType set = (bookstoreType)parseXML(requestStream, type);
    bookType []bookList = set.book;
    decimal max=0;
    bookType winner=null;
    for(int i=0; i<bookList.Length;i++){
        bookType current = bookList[i];
        if(current.price>max){
            max = current.price;
            winner = current;
        }
    }
    return winner;
}

</script><?xml version="1.0"?>
<title><%=findExpensive(Context).title%></title>