Streaming MessagePack API with Svelte and Asp .Net Core

Published: 07/26/2020

In this article I will show how to use MessagePack to stream binary responses to a Svelte application from a .Net Core backend.

MessagePack

First of all, what is MessagePack?

MessagePack is an efficient binary serialization format for exchanging data between applications. In this article I will show how to exchange binary payloads between a Svelte JavaScript application and a .Net Core backend api.

MessagePack vs. Json

Most web applications rely on json as the serialization format when talking to backend apis. The main difference between json and MessagePack is that MessagePack is a binary format that tends to lead to smaller responses than the text based json format. However as we will see later in the article, this isn’t always the case.

Demo Application

For the purposes of this article I have created simple Svelte application for rendering a list of items fetched from a .Net Core api.

.Net Core

Let’s start by looking at the .Net Core Api.

First we will look at the Controller

public class Friend { public string FirstName {get; set;} public string LastName {get; set;} public string MiddleName {get; set;} public string Profession {get; set;} public string Title {get; set;} public int Id {get; set;} public int Age {get; set;} public bool IsActive {get; set;} public Address Address {get; set;} public Phone Phone {get; set;} public Fax Fax {get; set;} } public class Address { public int Zip {get; set;} public string StreetAddress {get; set;} public int AptNumber {get; set;} public string City {get; set;} } public class Phone { public string Number {get; set;} public string AreaCode {get; set;} public bool IsCellPhone {get; set;} public string CountryCode {get; set;} } public class Fax { public string Number {get; set;} public string AreaCode {get; set;} public string CountryCode {get; set;} } [Route ("")] public class HomeController : Controller { [Route ("")] [Route ("/friends")] public Ienumerable<Friend> Friends () { var friends = new List<Friend>(); for(var i = 0; i < 1000; i++) { var phone = new Phone() { IsCellPhone = true, Number = (i * 10000).ToString(), AreaCode = i.ToString(), CountryCode = i.ToString() }; var fax = new Fax() { Number = (i * 10000).ToString(), AreaCode = i.ToString(), CountryCode = i.ToString() }; var address = new Address() {StreetAddress = Guid.NewGuid().ToString(), Zip = i, AptNumber = i, City = Guid.NewGuid().ToString()}; friends.Add(new Friend() { FirstName = Guid.NewGuid().ToString(), LastName = Guid.NewGuid().ToString(), MiddleName = Guid.NewGuid().ToString(), Address = address, Title = Guid.NewGuid().ToString(), Phone = phone, Fax = fax, Profession = Guid.NewGuid().ToString(), Id = i, Age = 40 }); } return friends; } }

As you can see, this is a standard .Net controller with a simple object model for describing a friend with a phone number, address, etc.

Next we will look at the required middleware in Startup.cs to enable MessagePack.

services.AddMvc().AddMvcOptions(option => { option.OutputFormatters.Add(new MessagePackOutputFormatter(ContractlessStandardResolver.Options)); option.InputFormatters.Add(new MessagePackInputFormatter(ContractlessStandardResolver.Options)); });

All we have to do is add a pair of new output and input formatters to wire up support for MessagePack in our api. These new formatters are added to the default set of formatters, which already includes json.

One thing I really like about the design of .Net Core apis is that everything is driven by content negotiation. Instead of hard coding a format in the controller, we instead trigger the appropriate formatter based on content headers in the request. This means the same controller method from the previous code sample will support both json and MessagePack without any code changes. Just send a request with content type application/json if you prefer json. All you need to do to change the format to MessagePack is pass application/x-msgpack.

Svelte

The Svelte application is mostly standard Svelte, except the part dealing with binary MessagePack payloads. Json doesn’t require anything special to deserialize api responses to JavaScript objects since json is supported natively by the browser. MessagePack on the other hand is a custom format, so we need to bring in a library to help us with deserialization.

I have included the complete Svelte component below:

<h1>{ greeting }</h1> {#each friends as friend} <div><span>{ friend.FirstName }</span> <span>{ friend.LastName }</span></div> {/each} <script> import { onMount } from 'svelte'; const greeting = "Hello from Svelte and .Net"; let friends = []; let allFriends = []; onMount(async () =$gt; { const response = await Get('application/x-msgpack'); for await (const friend of MessagePack.decodeArrayStream(response.body)) { allFriends = [friend, ...friends]; friends = allFriends.slice(0, 20); } }) function Get(contentType) { return fetch('/friends', { headers: { 'Content-Type': contentType, 'Accept': contentType } }); } </script>

I am using the fetch api to make http requests, but notice that I am passing in application/x-msgpack as my Accept and Content-Type headers. This will instruct my .Net Core api to respond with a binary MessagePack response. The binary response is then fed into decodeArrayStream to deserialize it into a regular JavaScript object.

Payload Sizes

Because MessagePack offers a more compact format than standard json you will see a difference in payload size between the two formats. For small results the difference is negligible, but as the size increases you start to notice a difference.

At 1000 response items the difference in size is 495kb vs. 576kb in favor of MessagePack.

This is a difference of 81k, which is substantial, but this is pre compression. What happens if we add content compression to the api response?

In my experiment I observed that Gzip or Brotli compression would both completely remove the previous gain from the more compact MessagePack binary format. I even saw a few cases where json weighed in slightly smaller than MessagePack. From this I conclude that there is no benefit from switching from json to MessagePack for payload size alone, unless of course your web server is unable to do on the fly content compression.

Streaming

Streaming is where you really start to see benefits from MessagePack. Unlike json, MessagePack supports streaming deserialization, which means we don’t have to wait for the entire api payload to come back from the server. As a result we can implement a streaming UI where we display the records as we receive them.

In my next experiment I am returning a list of 100k items, a dataset that adds up to more than 20MB worth of data after compression. On a throttled 3G connection it takes about 1.9 minutes to receive the entire payload. However since I can stream out results as I receive them, the UI is quite responsive. For every chunk received from the streaming api I am updating the Svelte UI to display the latest 20 records.

The source is available on Github in case you are interested in checking out.

Follow me on twitter @MoreTechStories