BSON Support in ASP.NET Web API 2.1
by Mike Wasson
Web API 2.1 introduces support for BSON. This topic shows how to use BSON in your Web API controller (server side) and in a .NET client app.
What is BSON?
BSON is a binary serialization format. “BSON” stands for “Binary JSON”, but BSON and JSON are serialized very differently. BSON is “JSON-like”, because objects are represented as name-value pairs, similar to JSON. Unlike JSON, numeric data types are stored as bytes, not strings
BSON was designed to be lightweight, easy to scan, and fast to encode/decode.
- BSON is comparable in size to JSON. Depending on the data, a BSON payload may be smaller or larger than a JSON payload. For serializing binary data, such as an image file, BSON is smaller than JSON, because the binary data does is not base64-encoded.
- BSON documents are easy to scan because elements are prefixed with a length field, so a parser can skip elements without decoding them.
- Encoding and decoding are efficient, because numeric data types are stored as numbers, not strings.
Native clients, such as .NET client apps, can benefit from using BSON in place of text-based formats such as JSON or XML. For browser clients, you will probably want to stick with JSON, because JavaScript can directly convert the JSON payload.
Fortunately, Web API uses content negotiation, so your API can support both formats and let the client choose.
Enabling BSON on the Server
In your Web API configuration, add the BsonMediaTypeFormatter to the formatters collection.
[!code-csharpMain]
1: public static class WebApiConfig
2: {
3: public static void Register(HttpConfiguration config)
4: {
5: config.Formatters.Add(new BsonMediaTypeFormatter());
6:
7: // Other Web API configuration not shown...
8: }
9: }
Now if the client requests “application/bson”, Web API will use the BSON formatter.
To associate BSON with other media types, add them to the SupportedMediaTypes collection. The following code adds “application/vnd.contoso” to the supported media types:
[!code-csharpMain]
1: var bson = new BsonMediaTypeFormatter();
2: bson.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.contoso"));
3: config.Formatters.Add(bson);
Example HTTP Session
For this example, we’ll use the following model class plus a simple Web API controller:
[!code-csharpMain]
1: public class Book
2: {
3: public int Id { get; set; }
4: public string Title { get; set; }
5: public string Author { get; set; }
6: public decimal Price { get; set; }
7: public DateTime PublicationDate { get; set; }
8: }
9:
10: public class BooksController : ApiController
11: {
12: public IHttpActionResult GetBook(int id)
13: {
14: var book = new Book()
15: {
16: Id = id,
17: Author = "Charles Dickens",
18: Title = "Great Expectations",
19: Price = 9.95M,
20: PublicationDate = new DateTime(2014, 1, 20)
21: };
22:
23: return Ok(book);
24: }
25: }
A client might send the following HTTP request:
[!code-consoleMain]
1: GET http://localhost:15192/api/books/1 HTTP/1.1
2: User-Agent: Fiddler
3: Host: localhost:15192
4: Accept: application/bson
Here is the response:
[!code-consoleMain]
1: HTTP/1.1 200 OK
2: Content-Type: application/bson; charset=utf-8
3: Date: Fri, 17 Jan 2014 01:05:40 GMT
4: Content-Length: 111
5:
6: .....Id......Title.....Great Expectations..Author.....Charles Dickens..Price..........PublicationDate.........
Here I’ve replaced the binary data with “.” characters. The following screen shot from Fiddler shows the raw hex values.
Using BSON with HttpClient
.NET clients apps can use the BSON formatter with HttpClient. For more information about HttpClient, see Calling a Web API From a .NET Client.
The following code sends a GET request that accepts BSON, and then deserializes the BSON payload in the response.
[!code-csharpMain]
1: static async Task RunAsync()
2: {
3: using (HttpClient client = new HttpClient())
4: {
5: client.BaseAddress = new Uri("http://localhost");
6:
7: // Set the Accept header for BSON.
8: client.DefaultRequestHeaders.Accept.Clear();
9: client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/bson"));
10:
11: // Send GET request.
12: result = await client.GetAsync("api/books/1");
13: result.EnsureSuccessStatusCode();
14:
15: // Use BSON formatter to deserialize the result.
16: MediaTypeFormatter[] formatters = new MediaTypeFormatter[] {
17: new BsonMediaTypeFormatter()
18: };
19:
20: var book = await result.Content.ReadAsAsync<Book>(formatters);
21: }
22: }
To request BSON from the server, set the Accept header to “application/bson”:
[!code-csharpMain]
1: client.DefaultRequestHeaders.Accept.Clear();
2: client.DefaultRequestHeaders.Accept.Add(new
3: MediaTypeWithQualityHeaderValue("application/bson"));
To deserialize the response body, use the BsonMediaTypeFormatter. This formatter is not in the default formatters collection, so you have to specify it when you read the response body:
[!code-csharpMain]
1: MediaTypeFormatter[] formatters = new MediaTypeFormatter[] {
2: new BsonMediaTypeFormatter()
3: };
4:
5: var book = await result.Content.ReadAsAsync<Book>(formatters);
The next example shows how to send a POST request that contains BSON.
[!code-csharpMain]
1: static async Task RunAsync()
2: {
3: using (HttpClient client = new HttpClient())
4: {
5: client.BaseAddress = new Uri("http://localhost:15192");
6:
7: // Set the Accept header for BSON.
8: client.DefaultRequestHeaders.Accept.Clear();
9: client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/bson"));
10:
11: var book = new Book()
12: {
13: Author = "Jane Austen",
14: Title = "Emma",
15: Price = 9.95M,
16: PublicationDate = new DateTime(1815, 1, 1)
17: };
18:
19: // POST using the BSON formatter.
20: MediaTypeFormatter bsonFormatter = new BsonMediaTypeFormatter();
21: var result = await client.PostAsync("api/books", book, bsonFormatter);
22: result.EnsureSuccessStatusCode();
23: }
24: }
Much of this code is the same as the previous example. But in the PostAsync method, specify BsonMediaTypeFormatter as the formatter:
[!code-csharpMain]
1: MediaTypeFormatter bsonFormatter = new BsonMediaTypeFormatter();
2: var result = await client.PostAsync("api/books", book, bsonFormatter);
Serializing Top-Level Primitive Types
Every BSON document is a list of key/value pairs.The BSON specification does not define a syntax for serializing a single raw value, such as an integer or string.
To work around this limitation, the BsonMediaTypeFormatter treats primitive types as a special case. Before serializing, it converts the value into a key/value pair with the key “Value”. For example, suppose your API controller returns an integer:
[!code-csharpMain]
1: public class ValuesController : ApiController
2: {
3: public IHttpActionResult Get()
4: {
5: return Ok(42);
6: }
7: }
Before serializing, the BSON formatter converts this to the following key/value pair:
[!code-jsonMain]
1: { "Value": 42 }
When you deserialize, the formatter converts the data back to the original value. However, clients using a different BSON parser will need to handle this case, if your web API returns raw values. In general, you should consider returning structured data, rather than raw values.
Additional Resources
|