There is a bunch of cool stuff coming out of Microsoft right now. As I've previously blogged, WCF Web APIs is one of them. One of the cool things that was shown by Glenn Block at PDC a couple of weeks ago was Media Type Processors. Media Type Processors provide a way to allow the consumer of your service to be able to specify the format they want on their response simply by setting the Accept header on the request (and thereby allowing your service to conform to HTTP standards). Out of the box, WCF will support xml, json, and OData. However, at PDC, they also demonstrated building a PngProcessor which would return a png image from WCF by setting the Accept header to "image/png". Yes, you heard that right – WCF returning an image!
In the PDC demo, a Contact Manager application was used to show that you could request the same URI for a given contact but get back different representations for that resource depending on the requested media type. The WCF code for the service knows nothing of formats – it is only concerned the logic for returning the appropriate resource. The concerns for the formatting is totally encapsulated in the media type processors. For example, (using Fiddler) and executing a GET request for "/contact/4", a default request returns XML:
An Accept header of "application/json" results in json:
An Accept header of "image/png" results in an image (made possible by the PngProcessor):
All for the same URI.
Wouldn't it also be nice to be able to return HTML if a consumer specified an Accept header of "text/html"? Wouldn't it be nice to just hook this into the WCF pipeline? And wouldn't it also be nice to be able to use the Razor view engine even though we're using WCF and not MVC 3? Andrew Nurse recently had a blog post which gave details on how to host Razor outside of ASP.NET. This was also demonstrated at PDC. (If you don't think that is cool, you might have to check to make sure you still have a pulse)
I want to be able to use a Razor template that looks something like this:
<html> <body> <p>Name: @Model.Name</p> <p>Email: @Model.Email</p> <p>Address: @Model.Address</p> <p>City: @Model.City</p> <p>State: @Model.State</p> <p>Zip: @Model.Zip</p> <p>Twitter: @Model.Twitter</p> </body> </html>
Now I need some way to invoke that Razor template. But I also need to be able to specify a model. In order to do that, I'll make a slight change to the TemplateBase class that Andrew Nurse provided in his sample:
public abstract class TemplateBase { public StringBuilder Buffer { get; set; } public StringWriter Writer { get; set; } public TemplateBase() { Buffer = new StringBuilder(); Writer = new StringWriter(Buffer); } public abstract void Execute(); public virtual void Write(object value) { WriteLiteral(value); } public virtual void WriteLiteral(object value) { Buffer.Append(value); } public dynamic Model { get; set; } }
The only change I've made is that, on line #24, I added a property for the Model which I typed as dynamic. That is what makes it possible for me to simply refer to @Model in the razor template I showed above. So the final step is that I need a way to pass in that model to my template. Well, with the WCF bits available from CodePlex, this is now quite easy to do. Ultimately, I need to create my own HTML Processor to hook into the pipeline.
public class RazorHtmlProcessor : MediaTypeProcessor { public RazorHtmlProcessor(HttpOperationDescription operation, MediaTypeProcessorMode mode) : base(operation, mode) { } public override IEnumerable<string> SupportedMediaTypes { get { yield return "text/html"; } } public override void WriteToStream(object instance, System.IO.Stream stream, Microsoft.Http.HttpRequestMessage request) { var templateManager = new TemplateEngine(); var currentTemplate = templateManager.CreateTemplate(instance.GetType()); // set the model for the template currentTemplate.Model = instance; currentTemplate.Execute(); using (var streamWriter = new StreamWriter(stream)) { streamWriter.Write(currentTemplate.Buffer.ToString()); } currentTemplate.Buffer.Clear(); } public override object ReadFromStream(System.IO.Stream stream, Microsoft.Http.HttpRequestMessage request) { throw new NotImplementedException(); } }
Notice that all I need to do on line #10 is to specify which media types I can respond to. Then I simply have a stream I can write directly to in the WriteToStream() method. This allows me to now request "text/html" and you can see I now get HTML returned from WCF:
I've excluded the code of the TemplateEngine in this post in the interest of brevity. It closely matches the sample by Andrew Nurse – I just had to make a few tweaks to enable the ability to bind to dynamic types as well as to be able to read the Contact.cshtml file from disk. My complete code sample can be downloaded here (the contact manager parts are copy/paste from the sample available on the WCF CodePlex site). It is far from Production-ready (e.g., it's currently hard-coded to read only the Contact.cshtml file but could easily be extended UPDATE: I've updated code so engine now dynamically loads template based on type; for example, for a Contact object, it will find Contact.cshtml, borrowing the concept from MVC EditorTemplates) but this gives you a glimpse of the types of things that are now possible when combining the WCF HTTP library with a custom-hosted Razor view engine!