WebApi works with a number of media type formatters for XML and JSON, and then serves the output in the format specified by the accept header sent by the browser. So in some browsers a standard call to a WebApi method will return XML, while in other browsers a similar call (but with different accept header) will return JSON. This may be good in some cases, but I haven't yet had a scenario where I actually needed to return some XML, so I generally find this behavior very annoying.
Therefore I usually try to force the output to always be JSON, so for some time I have used my custom JSON media type formatter, and then replaced the existing formatters with my own.
The class it self works great - it does what it is supposed to do, and it even wraps the JSON response if a JSONP callback method is provided in the query string. What hasn't worked great is the way I have registered my custom formatter. It is not something I will consider pretty code, so let us just skip showing that part here.
Recently when browsing through the source code of Umbraco, I discovered the AngularJsonOnlyConfigurationAttribute class. The description of the class says:
Applying this attribute to any webapi controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention.
So this class is really an attribute, and when specified on a Web API controller, Web API will call the Initialize method of the class, which then will remove the existing media type formatters, and add Umbraco's custom formatter for Angular/JSON. This attribute seems to do pretty much what I originally wanted.
But since the attribute and media type formatter are made specifically for Angular, which differs a little from the regular JSON syntax, the attribute is not something that I can use directly. However since the attribute is basically just removing existing media type formatters, and then adding Umbraco's custom Angular JSON media type formatter, and I already have my own formatter, I can just copy the code for the attribute class, and change it to use my own formatter.
This gives me two classes - the attribute class and my custom formatter:
JsonOnlyConfigurationAttribute
using System;
using System.Linq;
using System.Net.Http.Formatting;
using System.Web.Http.Controllers;
namespace Skybrud.WebApi.Json {
public class JsonOnlyConfigurationAttribute : Attribute, IControllerConfiguration {
public virtual void Initialize(HttpControllerSettings settings, HttpControllerDescriptor descriptor) {
var toRemove = settings.Formatters.Where(t => t is JsonMediaTypeFormatter || t is XmlMediaTypeFormatter).ToList();
foreach (var r in toRemove) {
settings.Formatters.Remove(r);
}
settings.Formatters.Add(new SkybrudJsonMediaTypeFormatter());
}
}
}
SkybrudJsonMediaTypeFormatter
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web;
namespace Skybrud.WebApi.Json {
public class SkybrudJsonMediaTypeFormatter : JsonMediaTypeFormatter {
private string _callbackQueryParameter;
public SkybrudJsonMediaTypeFormatter() {
SupportedMediaTypes.Add(DefaultMediaType);
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
}
public string CallbackQueryParameter {
get { return _callbackQueryParameter ?? "callback"; }
set { _callbackQueryParameter = value; }
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext context) {
string callback;
if (IsJsonpRequest(out callback)) {
return Task.Factory.StartNew(() => {
StreamWriter writer = new StreamWriter(stream);
writer.Write(callback + "(");
writer.Flush();
base.WriteToStreamAsync(type, value, stream, content, context).Wait();
writer.Write(")");
writer.Flush();
});
}
return base.WriteToStreamAsync(type, value, stream, content, context);
}
private bool IsJsonpRequest(out string callback) {
callback = null;
if (HttpContext.Current.Request.HttpMethod != "GET") return false;
callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
return !String.IsNullOrEmpty(callback);
}
}
}
And then my Web API controller can look something like:
using Skybrud.WebApi.Json;
using Umbraco.Web.WebApi;
namespace WebApplication1.Controllers {
[JsonOnlyConfiguration]
public class JsonTestController : UmbracoApiController {
public object GetTest() {
return new {
meta = new {
code = 200
},
data = "Yay! We have some JSON!"
};
}
}
}
This will ensure that the output is always JSON, so no need to use a tool or browser plugin to make sure the proper accept header is sent to the server. Just specify the attribute, and you're good to go ;)
I have released the two classes as a tiny NuGet package. Installing this package will add a single DLL to your bin-folder, which may be totally okay. But if you already have a Visual Studio project for your Umbraco site, you might as well just add the two classes to that project.