I was searching all over the Internet, and yet I could not find any information about this seemingly simple task. Suppose you have an arbitrary string
that you would like to return from a WCF (Windows Communication Foundation) service.
You can do one of the following on the service contract:
- Create a method/operation with a
string
return type - Create a method/operation with a
Stream
return type in order to have full control over how serialization is performed. You can find a good explanation @ this link.
However, you may find yourself in a situation where you cannot return a Stream
type because you are sending the response somewhere deep inside the WCF stack and you only have the System.ServiceModel.Message
type to work with. Here is the documentation of the Message
class I am talking about.
Aside: If you have ever worked with RIA (Rich Internet Applications) such as Flash or Silverlight, you will most likely be familiar with cross-domain policy files, such as crossdomain.xml (Flash) or clientaccesspolicy.xml (Silverlight). I am not going to go into why they are needed, but needless to say, I had to return one of these files depending on the request made from the client. Both of these files contain perfectly valid XML.
If you look carefully at the Message.CreateMessage
method, you will see it has many overloads. At first, I tried this:
Message reply = Message.CreateMessage(MessageVersion.None, "",
XmlReader.Create(new MemoryStream(Encoding.UTF8.GetBytes("[XML goes here]"))));
This works fine as long as the XML you are passing to XmlReader
does not contain DTD definitions. For some security reason, having a DTD in the XML causes an exception on the XmlReader.Create
call. I was able to avoid an exception by setting the ProhibitDtd
property on XmlReaderSettings
to false
, but WCF decided I was performing evil deeds, and independently decided to emit my XML without the DTD definition.
If you look at the overloads of Message.CreateMessage
, you will quickly realize that you must serialize as XML. In my case, the DTD in the crossdomain.xml caused the headache, but what if you want to pass an arbitrary string
such as "Hello World
" back from the service as-is, what do you do then? Anything you may try will cause the default DataContractSerializer
to kick in and serialize your object. The catch is, you do not actually want serialization.
Here is the code that you need in order to return arbitrary text using WCF's Message
class.
public class TextBodyWriter : BodyWriter
{
byte[] messageBytes;
public TextBodyWriter(string message)
: base(true)
{
this.messageBytes = Encoding.UTF8.GetBytes(message);
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Binary");
writer.WriteBase64(this.messageBytes, 0, this.messageBytes.Length);
writer.WriteEndElement();
}
}
string response = "Hello World!";
Message reply = Message.CreateMessage(MessageVersion.None, null,
new TextBodyWriter(response));
reply.Properties[WebBodyFormatMessageProperty.Name] = new WebBodyFormatMessageProperty(
WebContentFormat.Raw);
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";