Introducing Cable: Type-Safe Web Apps in C# with NancyFx and Bridge.NET

Zaid Ajaj
16 min readAug 3, 2017

Introduction

A while ago, I filed a feature request on the Bridge.NET github repo. I wondered about whether supporting Remote Procedure Calls (RPC) is possible. This would allow for statically typed client-server communication between a Bridge.NET browser app (the client) and a web server, both written in C#. But Bridge.NET is only a compiler and an RPC implementation would tie the project to a specific web framework which obviously is not what the team is aiming for, their ambitious goal is to perfectly compile C# to Javascript to get the same results as if your C# code was compiled to .NET.

Later on, I decided to tackle the problem myself and came up with Cable: An RPC implementation for C# apps where the client being a Bridge.NET app, written in C# and compiled to JavaScript to run in the browser and the server is Nancy web server.

Besides an introduction to Cable, this article will be an in-depth guide to building type-safe web apps using Nancy and Bridge.NET. I will go through all the steps of setting up and bootstrapping the solution from the ground up. We will:

  • Set up a self-hosted Nancy server
  • Define shared types
  • Wire up Cable on the server with Nancy
  • Talk about the protocol Cable uses
  • Manage static content
  • Set up the Bridge.NET client app
  • Share domain models between the client and server
  • Use Cable on the client to call the server.
  • Discuss secure calls to server

I mainly assume you have intermediate-level of understanding C# and that you have some experience with web developement.

The code presented in this article is hosted on github.

Ready? Let’s dive in!

What is Cable? (TL;DR version)

Cable is a library that makes communication between your client and server type-safe by sharing types and interfaces between the two layers. On the server, you would define your server interfaces, these are normal interfaces that define your client-server interactions. In just a second, I will explain in-depth how to get started but essentially it goes like this, given an interface like the following:

Register an implementation of the interface inside a NancyModule:

From your Bridge.NET client app, create a “typed proxy for that interface and then you can call the server directly by just calling — and awaiting — a function of that interface:

No HTTP requests, no JSON conversions, just call the async function directly and work with results. Cable will know which route to call on the server. You can think of Cable as a very tiny layer on top of HTTP with automatic serialization and deserialization of data making communication robust, seamless and pleasant.

The library consists of three main packages:

  • Cable: a specialized JSON converter, runs on the server.
  • Cable.Nancy: Responsible mapping HTTP requests to methods on a Nancy server with automatic JSON conversion.
  • Cable.Bridge: JSON converter for the Bridge.NET client project. Acts the counter part converter of the Cable package. Also responsible for talking to the server.

Why Nancy?

Why choose the server side framework to be Nancy and not some other web framework like Asp.net Web Api? There are dozens of reasons I can think of why someone should write web services with Nancy and it deserves it’s own article to write about. Most important to me is that the framework, while being extremely simple and intuitive to work with, it does not trade simplicity with magic. The awesome open source community. It works cross-platform by default and it is very easy to customize and to do things your own way.

There is no reason why Web Api or other frameworks cannot be supported and it might be the next step, I just haven’t got the time to look into it properly.

However, I think that it should also work out of the box with Web Api if you use OWIN middleware and have Nancy plugged in your OWIN middleware pipeline. We will leave this hypothesis for another article (unless you want to give it a try yourself 😉).

Getting Started

We will build the server first, a self-hosted Nancy server.

Start by creating a new Console Application, I called it “ServerSide”:

Install Cable.Nancy from Nuget by either searching the Nuget Package Manager or using Package Manager Console:

Install-Package Cable.Nancy

Because Cable.Nancy depends on Nancy, Nancy (≥ v1.4.4) will automatically get pulled in as well. Another dependency is the Cable package, which provides the core functionality for serialization and deserialization of the JSON data.

The Cable package can be used stand-alone if you only want the JSON converter. This project here demonstrates using just the converter without HTTP abstractions.

To make the server self-hosted, you need another Nuget package: Nancy.Hosting.Self (≥ v1.4.1), install it and we are good to go.

Before writing any Nancy modules, let’s create the types we want to share between our client and server. We will put them in a separate file called SharedTypes.cs.

Assuming we are building a web service to manage data of students, we want to be able to do things like fetching all students from server, or searching for a specific student by name:

The IStudentService interface is very important. Cable.Nancy uses the type information of this interface to generate the routes. Methods of these interfaces are expected to be of the shape:

Task<Something> MethodName(zero or more parameters);

As you can see, all of the interface functions return Task of something, because it is assumed you will await them on the client. Behind the scenes, the Bridge.NET client will be making asynchronous HTTP calls to the server.

In another file called StudentService.cs , we will implement the IStudentService interface, simply like so:

It is however possible to use synchronous functions as well such as

Something MethodName(zero or more parameters);

but when the client calls them, it will use a synchronous HTTP request and will block the UI thread on the browser which is definitely not recommended.

Cable and Nancy

Ok, now that we have the definitions and the implementation, we can wire things up with Nancy.

I will create an empty NancyModule that has a dependency on IStudentService like the following block of code, inside the constructor, we call NancyServer.RegisterRoutesFor from Cable.Nancy, passing in the current NancyModule (the this parameter), the second parameter is the concrete implementation of the interface provided through the constructor.

That’s all there is to it for wiring Nancy and Cable. Same logic follows if you had more interfaces.

All that’s left is to start the server using a NancyHost. The file Program.cs now becomes:

Build and run the app, you should see this:

Server is up and running!

What is happening at this point?

When the application starts, Nancy scans the assembly for (public) NancyModules, registers them and their routes and computes how to construct them and their dependencies (basic dependency injection).

Cable only comes into play when the following line of code runs:

NancyServer.RegisterRoutesFor(this, service);

It will create routes that map HTTP calls to methods of the provided service. By default, when Cable is generating routes for a service implementing an interface, say ISomething , for each method of that interface, a POST route of path /ISomething/{method.Name} is created with a route handler.

That route handler expects an array of JSON objects in the request body corresponding with the parameters array of the method (multiple parameters are supported). Notice that because of this choice, overloading methods is not possible. When the JSON data is deserialized to concrete .Net objects, the method in subject is dynamically invoked passing in the deserialized data as the parameters of that method. After that, the result of the method is serialized and sent back as JSON to the client.

Notice that Cable doesn’t do anything to initialize the instance of StudentService , it leaves that responsibility to Nancy. This way, if your service implementation itself has other dependencies, they will also be resolved too by means of dependency injection.

Internally, the Cable (de)serializer is a specialized JSON converter that uses a protocol both client and server understand, it embeds meta-data of the types along with the serialized data so that the Bridge.NET client understands how to deserialize the JSON back to concrete objects.

Normally, you don’t have to think about or bother with the protocol but to understand how it works and to convince yourself for now that the routes are actually created and waiting to be invoked, you can make a POST request to

http://localhost:8080/IStudentService/GetAllStudents

with JSON body content:

{ "Type": "Array", "Value": [] }

like the following, using Postman:

Since the method GetAllStudents doesn’t have any parameters, we just send an empty array. We get the results back, the output of GetAllStudents(), serialized to JSON:

For the sake of the argument, let’s invoke the other route for the TryFindStudentByName method. This method has one parameter of type string so that is what we send in the request body. We make a POST request to this URL:

http://localhost:8080/IStudentService/TryFindStudentByName

Like this:

We get the results back:

And indeed we get the data back for a student named “John”.

Sure, the meta-data adds a little bit to the JSON response size. This has to do with the fact that Reflection with Bridge.NET is not yet perfect (as of v16.0) so I decided to put the data I need in the JSON and have full control on the client-side regarding how I deserialize the objects.

However, I can think of many ways to optimize the JSON size even more. For example, changing the “Type” and “Value” strings to just “t” and “v” or changing the names of types to just numbers, for example “DateTime” would become 1. Due to the very limited time I can spend on open source projects, I turned my focus on making the JSON converter handle as many types as possible, for example to support converting Queues and Stacks (currently work in progress).

Of course, it would be really awesome if you want help out on this project and make it even better. The code is out in the open and is 100% free. PR’s are always welcome!

Managing Static Content

This section assumes we don’t use any view rendering engines like Razor or The SuperSimpleViewEngine. We don’t need any ;)

Building the client app requires not only the Javascript file you get generated from your C# project, but also many other types of files like the html, css and image files that your server must serve. There are many ways to serve these files and if you want to use your own convention, that’s totally fine. The solution I will present to you here is a bit unconventional. For convenience, I want to put the static files inside the ServerSide project along with the C# source files. The structure of the project becomes something like this:

Project structure

As you can see, you need a client directory. Inside of that you will need an index.html file that you would serve at the root of the server, i.e. when the user navigates to:

http://localhost:8080

Finally we put the JavaScript files in that js directory and this directory will be the output directory of the Bridge.NET Client project.

Now you need a NancyModule that handles these files. I created the file Resources.cs , very simple, it goes like this:

The function FindClientAsset will look the project folder. Since the output folder is either inside ProjectDirectory/bin/Release or ProjectDirectory/bin/Debug I just look up the parent directory of the current directory (where the assembly is) twice to get to the ProjectDirectory and from then look for files inside the client directory.

The Resources module has now two handlers:

  • Get[“/”] : will handle the root path “/” and return the contents of the index.html file.
  • Get[“/js/{file}”] : Will handle any request to the path /js/{file} where the server will look for that file inside the js directory within the client directory. The server then returns the contents of that file as a JavaScript text (MIME-type = text/javascript).

To test this real quick, change the contents of index.html to:

Build and run the server again and navigate with your browser to http://localhost:8080 , you should see the following:

Alright! The root path is working. let’s now test serving a JavaScript file. Include a script tag inside your index.html to point to a JavaScript file inside the js directory named test.js like so:

<script src="js/test.js"></script>

Because it might be confusing, I want to point out that this link is not pointing to the relative directory js of the index.html directly but rather it is pointing to the server at http://localhost:8080/js/test.js where the server will look for that JavaScript file and return it.

Alright, create the file test.js inside the js directory with the content:

Do not restart the server, just refresh the page and take a look at your browser console:

Although we will not be using css or images for this project, but if you want to serve css files, you follow the same logic for serving JavaScript files. Assuming you have a directory called css inside client and you want to serve files from there, you would write the following handler inside the Resources module:

So far so good, we are ready to take on the client project.

Building the client app

I will follow the Nuget Installation way of setting up the Bridge.NET project.

Start by adding a new Class Library to your solution, I called it “ClientSide”:

Remove your Class1.cs file and create a new empty Code File called Program.cs with the following code:

To turn this class library into a Bridge.NET project, you need to install the Bridge nuget package, v16.0.0 as of the time of writing. For readers from the future, install latest stable. Since v16.0.0-beta, there were tremendous improvements made to the compiler especially concerning boxing, reflection and above all: source maps support (ability to debug C# source files in the browser).

When the nuget package is installed, a new file is created in your project: bridge.json this file is where you put your global configurations of the Bridge.NET project.

We need to change two options. First, the output directory of generated JavaScript files. We want to generate these files in the client/js directory inside the ServerSide project. Second, we want to combine the scripts into one JavaScript file using the combineScripts option. To do this, change these options in the bridge.json file to the following:

"output": "../ServerSide/client/js",
"combineScripts": true

Without furthur ado, while your server is running, build your ClientSide project (right click -> build) and take a look at your client/js directory:

Nice! we got our JavaScript generated in the right place. There are also two html files that generated: index.html and index.min.html. You can ignore these because we have our own index.html or you can disable html generation using this option. The only thing left is to reference the JavaScript file from our index.html file using a script tag:

<script src="js/ClientSide.min.js"></script>

ClientSide.min.js file is just the minified version of ClientSide.js. Unless you want to debug the generated JavaScript directly when your code uses magical unicorns, just go for the minified version.

Refresh the page in your browser and you should see this:

Alright! We got our generated JavaScript running as you can see from this special Bridge Console. Time to share the types with the client and talk to our server.

Sharing domain models

When the nuget package Bridge was installed, it turned the project into a Bridge.NET project. This is a different type of a class library because it does not use the default mscorlib.dll implementation (the System.* references) but rather, it uses it’s own implementation that is running as JavaScript. This implies that a Bridge.NET class library cannot reference another assembly using the default mscorlib. The same goes the other way around, normal class libraries cannot reference Bridge.NET assemblies, because the references to System.* will conflict. So how do we share types between projects then?

File Links to the rescue!

You don’t have to reference a project, just reference the files directly. This means you can use one file used by both projects. Remember the SharedTypes.cs file in the ServerSide project? Here is how you link it, from your ClientSide:

Right click -> Add -> Existing item

Look for the SharedTypes.cs file and Add as Link:

When you add it, Visual Studio will put a little icon to the file telling you this file is linked:

A link to the file is a reference to the actual physical file, this means you only edit the file on one place the changes are reflected for both projects. Your domain models are now available from you client.

Talking to the Server from the Client

This is the easiest part. First install Cable.Bridge into your ClientSide project:

Install-Package Cable.Bridge

Turn your Program.cs into the following:

The function BridgeClient.Resolve<T>() returns a typed proxy. A typed proxy is “simply” an object literal, made in such a way that when a method on the proxy is called, it delegates the work to the server with automatic serialization of the input method parameters and automatic deserialization of the results you get back from the server.

Rebuild the ClientSide project and refresh your page to see this actually working:

Nice, isn’t it? The server was called and returned JSON that was automatically deserialized. You can take a look at your browser console to inspect the underlying HTTP request:

You don’t have to resolve a new typed proxy every time you want to call the server. Just keep a static reference somewhere and only initialize it when your app starts, thus not when defining the static reference. Then you can re-use that static reference anywhere.

Even if you do resolve a typed proxy every time, it has no performance impact because these proxies are cached.

Securing requests to server

Very often, you want to make authenticated requests to the server. These are requests with the user information attached to them, maybe the authorization token in the header information of in a cookie. Manipulating header data and other parts of the internal HTTP requests is not possible through the public API of Cable. This has to do with the fact that Cable is an abstraction over HTTP. Exposing the underlying mechanism and HTTP requests would make it a leaky abstraction. So how do you secure your requests?

You put the authorization data, usually an auth token, in the your request body through appropriate types. Here we talking about stateless authentication where an authentication token is sent on every request.

Using Json Web Token is ideal for this situation.

To model this scenario using the type system, you start with a method that authenticates the user and returns a string: the authorization token.

the method Authenticate returns the token that you would re-use on subsequent requests. The data of these subsequent requests can be modelled as a generic class:

where AuthToken would be populated with the token you got from the Authenticate method. On the server, you would check for the validity of the auth token of the request.

You might ask: “How would you let the user know that he or she are not authorized if I can’t return a 403 status code?”

You model such scenario’s with an appropriate type. Don’t think of HTTP when using Cable, just types and functions.

One model of a generic server response would be the following class:

The Error property doesn’t have to a string, it can be a very detailed object representing what the error actually was and the client would report to the user accordingly.

Of course, you could have modelled your client-server interactions differently but essentially you model them with types and functions, not HTTP primitives.

Exception Handling

You might wonder: “What happens when an exception on the server is thrown?”

You can simply catch it from the client. Only the exception message is delegated back to the client and is re-thrown using a generic System.Exception . This means you don’t get to see the stack trace of what happened on the server, just the stack trace on the client up to the point where the exception was thrown. Let me show you, add a new method to your IStudentService:

Task<bool> CatchMeIfYouCan();

with implementation:

then from the client, await the function within a try-catch block:

with results:

That’s it. There is not much you can customize with exceptions for now, but providing flexibility for them is definitely on my TO-DO list.

The End

I think it is high time I stop myself from making this super long article even longer. If you have made it thus far, kudos to you! I enjoyed writing the article and I hope that you will consider this wonderful stack of Nancy, Bridge.NET and Cable for your next awesome app. Don’t forget to hit that 💚 button and to share.

The code used in this article is published here on github.

If you have any questions, I usually hang out on one these gitter channels: Bridge and Fable. Happy coding!

--

--