I’ve been working hard on reviewing the technical reviews of my book lately and I learned something new last night from my colleague and wise guru of the web services world Rob Bagby. Rob put in one of the comments in his review of my chapter on creating RESTful services to be consumed b Silverlight that I had mistakenly said that a RESTful service definition in a ServiceContract must be decorated with the WebGet or WebInvoke attribute. He corrected my error by explaining that an operation in a service contract that is exposed with the webHttpBinding need not be decorated with either of those attributes to be accessible from a HTTP call. He said that the operation would be accessible as a POST.

“Wow, really?”, I thought. I totally missed that. I guess I missed it because I have a practice of explicitly declaring attributes and settings instead of relying on implicit default values. I do this because its so much easier to see the meaning and intent of the code. But I had not realized that there was a default behavior in this case as Rob noted. So I cracked open my code for my book and I added an operation to my service contract interface like this:

C#

[OperationContract]
void Test2(Product p);

VB

<OperationContract> _
void Test2(Product p)

 

 

Then I added the implementation of the operation like this:

C#

public void Test2(Product p)
{
var y = p;
}

VB

Public Sub Test2(ByVal p As Product)
Dim y = p
End Sub

I just wanted to test this out so I kept it pretty basic. The method Test2 does not do anything with the request, I just wanted to see if the request worked and the Product was received via a POST. So this operation has no defined WebGet nor WebInvoke, yet is should work as a HTTP POST and pass the XML serialized Product to Test2 method. Here is the code I used to invoke it:

C#

// Serialize a Product to XML
Product product = new Product
{
ProductId = 301,
ProductName = "Another Thing",
UnitPrice = (decimal)19.99
};
DataContractSerializer serializer = new DataContractSerializer(typeof(Product));
MemoryStream stream = new MemoryStream();
serializer.WriteObject(stream, product);
string xmlSerializedProduct = 
              Encoding.UTF8.GetString(stream.ToArray(), 0, (int)stream.Length);
 
// POST the XML     to the Async Operation
WebClient wc = new WebClient();
wc.UploadStringCompleted += wc_UploadStringCompleted;
wc.Headers["Content-type"] = "application/xml";
wc.Encoding = Encoding.UTF8;
string urlString = string.Format("{0}/Test2", BaseUri);
wc.UploadStringAsync(new Uri(urlString), "POST", xmlSerializedProduct);

VB

' Serialize a Product to XML
Dim product As Product = New Product With { _
.ProductId = 301, .ProductName = "Another Thing", _
.UnitPrice = CDec(19.99)}
Dim serializer As New DataContractSerializer(GetType(Product))
Dim stream As New MemoryStream()
serializer.WriteObject(stream, product)
Dim xmlSerializedProduct As String = _
Encoding.UTF8.GetString(stream.ToArray(), 0, _
CInt(Fix(stream.Length)))
 
 
' POST the XML to the Async Operation
Dim wc As New WebClient()
wc.UploadStringCompleted += wc_UploadStringCompleted    
wc.Headers("Content-type") = "application/xml"
wc.Encoding = Encoding.UTF8
Dim urlString As String = String.Format("{0}/Test2", BaseUri)
wc.UploadStringAsync(New Uri(urlString), "POST", xmlSerializedProduct)

And you know what? It worked. Rob was right (of course). :)

I definitely recommend decorating the method with the attribute to make it perfectly clear what you intend the method to do. Plus, with the attribute you get to define the response message format, the request format, and the UriTemplate can be tweaked. its just pretty cool to learn something new :)

DotNetKicks Image