Posts
202
Comments
1112
Trackbacks
51
Combine WCF MediaTypeProcessors with a Custom Razor Host

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:

contact-xml

 

An Accept header of “application/json” results in json:

contact-json

 

An Accept header of “image/png” results in an image (made possible by the PngProcessor):

contact-png

 

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:

   1:  <html>
   2:      <body>
   3:          <p>Name: @Model.Name</p>
   4:          <p>Email: @Model.Email</p>
   5:          <p>Address: @Model.Address</p>
   6:          <p>City: @Model.City</p>
   7:          <p>State: @Model.State</p>
   8:          <p>Zip: @Model.Zip</p>
   9:          <p>Twitter: @Model.Twitter</p>
  10:      </body>
  11:  </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:

   1:  public abstract class TemplateBase
   2:  {
   3:      public StringBuilder Buffer { get; set; }
   4:      public StringWriter Writer { get; set; }
   5:   
   6:      public TemplateBase()
   7:      {
   8:          Buffer = new StringBuilder();
   9:          Writer = new StringWriter(Buffer);
  10:      }
  11:   
  12:      public abstract void Execute();
  13:   
  14:      public virtual void Write(object value)
  15:      {
  16:          WriteLiteral(value);
  17:      }
  18:   
  19:      public virtual void WriteLiteral(object value)
  20:      {
  21:          Buffer.Append(value);
  22:      }
  23:   
  24:      public dynamic Model { get; set; }
  25:  }

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.

   1:  public class RazorHtmlProcessor : MediaTypeProcessor
   2:  {
   3:      public RazorHtmlProcessor(HttpOperationDescription operation, MediaTypeProcessorMode mode)
   4:          : base(operation, mode)
   5:      {
   6:      }
   7:   
   8:      public override IEnumerable<string> SupportedMediaTypes
   9:      {
  10:          get { yield return "text/html"; }
  11:      }
  12:   
  13:      public override void WriteToStream(object instance, System.IO.Stream stream, Microsoft.Http.HttpRequestMessage request)
  14:      {
  15:          var templateManager = new TemplateEngine();
  16:          var currentTemplate = templateManager.CreateTemplate(instance.GetType());
  17:   
  18:          // set the model for the template
  19:          currentTemplate.Model = instance;
  20:          currentTemplate.Execute();
  21:          using (var streamWriter = new StreamWriter(stream))
  22:          {
  23:              streamWriter.Write(currentTemplate.Buffer.ToString());
  24:          }
  25:          currentTemplate.Buffer.Clear();
  26:      }
  27:   
  28:      public override object ReadFromStream(System.IO.Stream stream, Microsoft.Http.HttpRequestMessage request)
  29:      {
  30:          throw new NotImplementedException();
  31:      }
  32:  }

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:

contact-html

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!

posted on Wednesday, November 17, 2010 8:34 AM Print
Comments
Gravatar
# re: Combine WCF MediaTypeProcessors with a Custom Razor Host
Raghuraman
11/17/2010 2:04 PM
Steve,

Again way far ahead of the Curve !!!

Indeed a excellent feature and the simplicity with WCF is awesome.

Thank you for this simplified write up. Very useful as always.

Regards,

KRK
Gravatar
# re: Combine WCF MediaTypeProcessors with a Custom Razor Host
Glenn Block
11/18/2010 2:01 AM
Uh thanks, I need to go to the hospital now as I just blew a gasket in my brain. :-)

This is really awesome stuff Steve. Thanks for all your work. I was just talking to my boss about the lack of blog posts. This rocks!
Gravatar
# re: Combine WCF MediaTypeProcessors with a Custom Razor Host
Justin
11/27/2010 7:59 AM
Thanks for summarising this .NET stuff for us Steve. I don't use .NET alot, and MS seem to be always doing a lot to the framework, so it's nice to have a place where I can catch up on it, in a non-sales pithy, brief way!
Justin
Gravatar
# Error while copying content to a stream.
Mahesh Kshirsagar
2/9/2011 2:44 PM
Hi,

I am getting this error using above sample.
I am using different class (with just 2 properties, FirstName and LastName) instead of ContactResource class as shown in the exaample.

ErrorDescription:
{"Error while copying content to a stream."}

Exact Location is in class MediaTypeProcessor.cs at

this.WriteToStream(instance, s, request);
s.Position = 0;


Until execution reaches in RazorHtmlProcessor.cs
at

// 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();

everything is fine. i.e.CurrentTemplate.Buffer.Tostring actually has correct HTML output.

But then we execution reached in class HttpMessageEncoderFactory.cs
at this location
byte[] messageBytes = response.Content.ReadAsByteArray();

I start getting this error.

Is it just preview thing or am I missing something?

Thanks,
Mahesh.
Gravatar
# re: Combine WCF MediaTypeProcessors with a Custom Razor Host
Steve
2/13/2011 6:14 AM
@Mahesh - Hard to know without seeing your sample. Did you change any of the code other than the 2 properties?
Gravatar
# re: Combine WCF MediaTypeProcessors with a Custom Razor Host
Mahesh
2/14/2011 3:33 PM
No changes(except structuring the app in my way)

I have uploaded the code at -
http://cid-5eeb04636706e5c7.office.live.com/self.aspx/Public
Gravatar
# re: Combine WCF MediaTypeProcessors with a Custom Razor Host
Steve
3/3/2011 3:22 AM
@Mahesh - You are using a different version of the Microsoft.ServiceModel.WebHttp assembly than I am. The ContactManagerConfiguration that I have inherits from the HostConfiguration class. The configuration class you have inherits from HttpHostConfiguration. Plus, keep in mind these are early bits and the WCF team *might* still have a bug or two.
Gravatar
# re: Combine WCF MediaTypeProcessors with a Custom Razor Host
Mohan Sundaram
6/1/2011 4:41 AM
Very interesting post, Steve.

But have you tried fetching the HTML from a browser, rather than Fiddler? This works for some browsers but not for others, for instance Chrome.

The reason being the Content-type from the service is set as application/xml. I've fixed this by fiddling with settings in IIS, but do you know how the service itself can be coaxed to provide text/html?

Thanks
Gravatar
# re: Combine WCF MediaTypeProcessors with a Custom Razor Host
Steve
6/8/2011 1:15 AM
@Mohan - Chrome sends at Accept header of: "Accept: application/xml,application/xhtml+xml,text/html" so this should work just fine. Also, in the example above I set my html processor to only respond to "text/html" but you can give a list of values that your media type processor can respond to.

Post Comment

Title *
Name *
Email
Comment *  
 

View Steve Michelotti's profile on LinkedIn

profile for Steve Michelotti at Stack Overflow, Q&A for professional and enthusiast programmers




Google My Blog

Tag Cloud