In a previous post I talked about how to send raw JSON to a web API and consume it easily. This is a non-obvious process because ASP.NET Web API is optimized for sending and receiving arbitrary CLR object that then get serialized by the formatters in the request/response pipeline. However, sometimes you just want to have more direct control over the format that is returned in your response. This post talks about some ways you can regain that control.
Serialize your JSON document
If the only thing you want to do is take a take plain old JSON and return it, then you can on rely on the fact that the default JsonMediaTypeFormatter
knows how to serialize JToken
objects.
public class JsonController : ApiController
{
public JToken Get()
{
JToken json = JObject.Parse("{ 'firstname' : 'Jason', 'lastname' : 'Voorhees' }");
return json;
}
}
If you want a bit more control over the returned message then you can a peel off a layer of convenience and return a HttpReponseMessage
with a HttpContent
object.
Derive from HttpContent for greater control
The HTTP object model that is used by ASP.NET Web API is quite different than many other web frameworks because it makes an explicit differentiation between the response message and the payload body that is contained in the response message. The HttpContent class is designed as an abstract base class for the purpose of providing a standard interface to any kind of payload body.
Out of the box there are a number of specialized HttpContent classes: StringContent, ByteArrayContent, FormUrlEncodedContent, ObjectContent and a number of others. However, it is fairly straightforward to create our own derived classes to deliver different kinds of payloads.
To return JSON content you can create a JsonContent
class that looks something like this,
public class JsonContent : HttpContent
{
private readonly JToken _value;
public JsonContent(JToken value)
{
_value = value;
Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
protected override Task SerializeToStreamAsync(Stream stream,
TransportContext context)
{
var jw = new JsonTextWriter(new StreamWriter(stream))
{
Formatting = Formatting.Indented
};
_value.WriteTo(jw);
jw.Flush();
return Task.FromResult<object>(null);
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
}
This JsonContent
class can then be used like this,
public class JsonContentController : ApiController
{
public HttpResponseMessage Get()
{
JToken json = JObject.Parse("{ 'firstname' : 'Jason', 'lastname' : 'Voorhees' }");
return new HttpResponseMessage()
{
Content = new JsonContent(json)
};
}
}
This approach provides the benefit of allowing you to either manipulate the HttpResponseMessage
in the controller action, or if there are other HttpContent.Headers
that you wish to set then you can do that in JsonContent
class.
Making a request for this resource would look like this,
> GET /JsonContent HTTP/1.1
<br> User-Agent: curl/7.28.1
<br> Host: 127.0.0.1:1001
<br> Accept: */*
<br> HTTP/1.1 200 OK
<br> Content-Length: 43
<br> Content-Type: application/json; charset=utf-8
<br> Server: Microsoft-HTTPAPI/2.0
<br> Date: Wed, 11 Jun 2014 21:27:50 GMT
<br> {"firstname":"Jason","lastname":"Voorhees"}
Wrapping HttpContent for Additional Transformations
Another nice side-effect of using HttpContent
classes is that they can be used as wrappers to perform transformations on content. These wrappers can be layered and will just automatically work whether the content is buffered or stream directly over the network. For example, the following is possible,
public HttpResponseMessage Get()
{
JToken json = JObject.Parse("{ 'property' : 'value' }");
return new HttpResponseMessage()
{
Content = new EncryptedContent(new CompressedContent(new JsonContent(json)))
};
}
This would create the stream of JSON content, compress it, encrypt it and set all the appropriate headers.
Separation of concerns promotes reuse
By focusing on the payload as an element independent of the response message it becomes easier to re-use the specialized content classes in for different resources. In the APIs I have built I have had success creating many other content classes like:
- XmlContent
- ImageContent
- EmbeddedResourceContent
- FileContent
- CsvContent
- ODataContent
- AtomContent
- HalContent
- CollectionJsonContent
- and numerous others.
Image credit : Jason https://flic.kr/p/gQEp5X
Image credit : Knobs and switches https://flic.kr/p/2YJC3Y