I use asp.net mvc as both my html website (default view engine) and my service endpoint. The service endpoint is used by my WPF and Silverlight clients by injecting "content-type=text/xml" into the header of a WebClient request (see ScottGu's post on consuming a service in SL which inspired this approach). I found somewhere on the net, some code that overrides the OnActionExecuted event like this:
public class JsonOrXml : ActionFilterAttribute
{
private static UTF8Encoding UTF8 = new UTF8Encoding(false);
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// setup the request, view and data
HttpRequestBase request = filterContext.RequestContext.HttpContext.Request;
ViewResult view = (ViewResult)(filterContext.Result);
var data = view.ViewData.Model;
String contentType = request.ContentType ?? string.Empty;
// JSON
if (contentType.Contains("application/json") || (string)view.ViewData["FORMAT"] == "json")
{
filterContext.Result = new JsonResult
{
Data = data
};
}
// POX
else if (contentType.Contains("text/xml") || (string)view.ViewData["FORMAT"] == "xml")
{
// MemoryStream to encapsulate as UTF-8 (default UTF-16)
// http://stackoverflow.com/questions/427725/
//
// MemoryStream also used for atomicity but not here
// http://stackoverflow.com/questions/486843/
//using (MemoryStream stream = new MemoryStream(500))
//{
// using (var xmlWriter =
// XmlTextWriter.Create(stream,
// new XmlWriterSettings()
// {
// OmitXmlDeclaration = false,
// Encoding = UTF8,
// Indent = true
// }))
// {
// new XmlSerializer(data.GetType()).Serialize(xmlWriter, data);
// }
// filterContext.Result = new ContentResult
// {
// ContentType = "text/xml",
// Content = UTF8.GetString(stream.ToArray()),
// ContentEncoding = UTF8
// };
//}
XmlDeclaration xmlDecl = new XmlDocument().CreateXmlDeclaration("1.0", "UTF-8", "yes");
filterContext.Result = new ContentResult
{
ContentType = "text/xml",
Content = xmlDecl.OuterXml + data.ToString(),
ContentEncoding = UTF8
};
}
}
}
So, the commented out piece is the code that I found - see the stackoverflow links for where I got it :)
I overrode the ToString() method on all of my business objects to return string representing how the business object would like to represent itself as xml. WCF accomplishes this via attributes, but I wanted a cleaner solution that didn't rely on reflection AND I didn't want to have both a website project and a WCF project - the problem with two projects is that it was hard to keep them both in sync with respect to functionality - I would get requests like "why doesn't the service allow me to filter my results like the website does?"
I'm very interested in other's feedback on this approach :)
Here's an example of a business object:
public class ContentFile : Entity
{
public ContentBook BelongsToBook { get; set; }
public string FileName { get; set; }
public XElement FileXml { get; set; }
public Binary FileData { get; set; }
public List<ContentFile> Versions { get; set; }
public List<ContentNode> ContentNodes { get; set; }
public override string ToString()
{
return this.ToString(SaveOptions.DisableFormatting);
}
public string ToString(SaveOptions options)
{
XElement xml = XElement.Parse("<contentFile id=\"" + Id.ToString() + "" + "\" />");
xml.Add(new XElement("fileName", FileName));
xml.Add(new XElement("fileStructure", FileXml));
xml.Add(base.ToString(options));
return xml.ToString(options);
}
}