I wanted to post my experiences with WebInvoke using generic DataContract's as parameters, because I found so little information on the subject via MSDN and throughout the interweb. Specifically, on how to properly formulate a non WCF client XML POST request in this scenario. I will not be focusing on how to host and configure the service (creating REST based services and endpoints in WCF), since I found plenty of information available in that area via blogs and msdn documentation.
Lets start with the following service contract and corresponding data contract types:
[DataContract(Namespace = "http://chriskirby.net/examples/blog/data")]
public class BlogPost
{
[DataMember]
public string Title;
[DataMember]
public string Body;
[CollectionDataContract(Name = "Tags", ItemName = "Tag")]
public string[] Tags;
[DataMember]
public DateTime PubDate;
}
[DataContract(Name = "BlogCreateRequestOf{0}", Namespace = "http://chriskirby.net/examples/blog")]
public class BlogCreateRequest<BlogDataType>
{
[DataMember(Order = 1)]
public string Username;
[DataMember(Order = 2)]
public string Password;
[DataMember(Order = 3)]
public BlogDataType Data;
}
[ServiceContract(Name = "ExampleBlogService", Namespace = "http://chriskirby.net/examples/blog")]
public interface IBlogService
{
[OperationContract]
[WebInvoke(Method = "POST",
BodyStyle = WebMessageBodyStyle.Bare,
RequestFormat = WebMessageFormat.Xml,
ResponseFormat = WebMessageFormat.Xml,
UriTemplate = "/CreateBlogPost")]
bool CreateBlogPost(BlogCreateRequest<BlogPost> request);
} As you can see above, the operation CreateBlogPost on the service contract IBlogService returns a boolean and accepts the type BlogCreateRequest<BlogPost> as its input. To the outside world via wsld or IMetadataExchange, the operation CreateBlogPost accepts schema type BlogCreateRequestOfBlogPost with a default namespace of http://chriskirby.net/examples/blog, however, the Data property is of schema type BlogPost with a namespace of http://chriskirby.net/examples/blog/data. To formulate the request properly, you need to make sure each node in the xml request is of the correct namespace, otherwise the DataContractSerializer will not recognize you request on deserialization, and WCF will return a 400 error (Bad Request). I've also used several notable DataContract and DataMember properties in order to control how the type looks to the outside world (serialized). On the BlogCreateRequest generic class I specified the name of BlogCreateRequestOf{0}, which in this case translates to BlogCreateRequestOfBlogPost. If I chose not so specify the name using the placeholder, the DataContractSerializer would serialize the type with a name like BlogCreateRequestOfBlogPost4sdop, where the suffix is comprised of a series of random characters to ensure that the type has a unique name. However, I know that my service will not use any other types by that name, so I can safely specify it for ease of used to the client. I also used the Order property, which is pretty self explanatory, it simply ensures that the serializer will use the same order I specified in my class definition. Finally, I decorated the Tags property on BlogPost with the CollectionDataContract attribute which allows me to specify how I want the collection formatted by serializer, e.g. <Tags><Tag/><Tag/></Tags>.
Formulating the request:
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = false;
settings.OmitXmlDeclaration = true;
settings.CloseOutput = true;
settings.ConformanceLevel = ConformanceLevel.Document;
settings.Encoding = Encoding.UTF8;
StringBuilder fileStringBuilder = new StringBuilder();
XmlWriter writer = XmlTextWriter.Create(fileStringBuilder, settings);
writer.WriteRaw(@"<?xml version=""1.0"" encoding=""utf-8""?>");
writer.WriteStartElement("BlogCreateRequestOfBlogPost ", "http://chriskirby.net/examples/blog");
writer.WriteElementString("Username", "TestUser");
writer.WriteElementString("Password", "TestPassword");
writer.WriteStartElement("Data");
writer.WriteElementString("Title", "http://chriskirby.net/examples/blog/data", "XML Post to WCF REST Service");
writer.WriteElementString("Body", "http://chriskirby.net/examples/blog/data", "Blah Blah Blah");
writer.WriteStartElement("Tags", "http://chriskirby.net/examples/blog/data");
writer.WriteElementString("Tag", "http://chriskirby.net/examples/blog/data", ".Net");
writer.WriteElementString("Tag", "http://chriskirby.net/examples/blog/data", "WCF");
writer.WriteEndElement();
writer.WriteElementString("PubDate", "http://chriskirby.net/examples/blog/data", "01/18/2008");
writer.WriteEndDocument();
WebClient webClient = new WebClient();
webClient.Headers.Add("Content-Type", "application/xml; charset=utf-8");
string response = webClient.UploadString("https://chriskirby.net/services/blog/CreateBlogPost", "POST", fileStringBuilder.ToString()); The tricky part here is really just understanding how the DataContractSerializer works... which now, after hours of debugging, I now have a much better understanding of. The key points of understanding for me were discovering how to assign the correct namespace to the corresponding node as well as figuring out how the serializer organized generic types, specifically my generic type CreateBlogRequest<>. During your first glace at the service and data contract code block, you may have thought as id did in that the Data property/element would be in the http://chriskirby.net/examples/blog/data namespace, since it is in fact of type BlogPost...however, the Data property is first an foremost a part of the BlogCreateRequest type, so it must be a part of the class namespace and not the namespace of its generically assigned type. My second, and ultimately incorrect thought, was that the structure would look like <Data><BlogPost><Title/>....</>. But after careful thought, that would mean Data would be of a type which had BlogPost as a member, which is not the case here. Its also worth noting there are several other ways of formatting the xml request to get the same result. The most common would be to declare both namespaces in the root element and assign them prefixes, you would then use those prefixes to denote the namespace in each element in the document.
So there you have it...The simple and sometimes complicated power of WCF. I look forward to many more headaches adventures in the near future.