aspnet

InterfaceRpc 2.0

This past week my Interface RPC library hit version 2.0 and I want to take a minute to go over how I got there.

I used the first version for a few services that my company depends on. It was running in production just fine, for a while. After a couple months I found an issue however. The issue was with how exceptions were handled by my code which in-turn caused an additional exception. The result of this caused threads to hang and eventually starve the system. It was a simple fix and services continued to run fine.

I ran into a second problem a few months later. This one, however, was much more serious and caused a service to hang until restarted. I knew what method was being called to cause it but that's about it. I didn't do much digging other than that because I didn't want to maintain the HttpListener implementation of this library any more. It was a stop-gap for how I really wanted it to work anyways...

Ideally I wanted to host the service on ASP.NET Core and plug into that ecosystem of things, like; depenency injection, configuration, and security.

So that's what I did. Instead of fixing a bug I chucked most of my code, refactored what was left (A LOT), and made a 2.0 I felt a whole lot better about.

I've had 2.0 running in production for months now with no issues. This past week a co-worker tidy'd up one last thing which pushed me over the edge in removing the pre-release moniker and published the official 2.0 nuget package! Let's take a look.

Service Setup

This is done during Startup within the Configure method. The RPC service is essentially now middleware in the ASP.NET pipeline.

public void Configure(IApplicationBuilder app)
{
    app.UseRpcService<IDemoService>(options => {...});
}

The options you can configure are

  • Prefix - hosts the service under a virtual subdirectory. This is useful for hosting multiple services in the same app.
  • AuthorizationScope - either None, Required, or AdHoc (default). If AdHoc then authorization will only be checked when your method is decorated with AuthorizeAttribute.
  • ServiceFactory - a function that returns the instance of the interface. You can grab this from ASP.NET's DI container by calling app.ApplicationServices.GetService<IDemoService>()
  • AuthorizationHandler - a function that returns true for authorized. See below for an example.

Here we are looking for a security token in the Authorization header to determine if a user is authorized to access the service.

options.AuthorizationHandler = (methodName, instance, context) =>
{
    if(!context.Request.Headers.ContainsKey("Authorization"))
    {
        return false;
    }
    if(!context.Request.Headers.TryGetValue("Authorization", out StringValues authHeader))
    {
        return false;
    }
    var bearerCredentials = authHeader.ToString().Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[1];
    var tokenHandler = new JwtSecurityTokenHandler();

    var validationParameters = new TokenValidationParameters
    {
        IssuerSigningKey = _signingCredentialStore.GetSigningCredentialsAsync().GetAwaiter().GetResult().Key,
        ValidAudience = "demo-service",
        ValidIssuer = "https://sso.domain.com"
    };

    var user = tokenHandler.ValidateToken(bearerCredentials, validationParameters, out SecurityToken securityToken);
    var token = securityToken as JwtSecurityToken;
    return token != null && token.ValidTo >= DateTime.Now;
};

_signingCredentialStore is defined in the Startup class as private static ISigningCredentialStore _signingCredentialStore; and is set in the ConfigureServices method by calling serviceProvider.GetService<ISigningCredentialStore>()

IDemoService is also configured in ConfigureServices just like any other implementation would.

services.AddTransient<IDemoService, DemoService>();

Creating a Client

The client code hasn't changed much.

var client = RpcClient<IDemoService>.Create(new RpcClientOptions
{
    BaseAddress = "https://demo.domain.com",
    SetAuthorizationHeaderAction = () =>
    {
        return new RpcClientAuthorizationHeader
        {
            Credentials = "access token",
            Type = "Bearer"
        };
    }
});

SetAuthorizationHeaderAction will be called before every request.

Wrapping Up

Everything else pretty much works the same as before. The service responds to POST requests where the method name in the URL translates to a method on the interface. Serialization can be any type defined in the SerializerDotNet library (JSON and Protobuf currently).

One thing that is not apparent is that the service and client both handle async methods.

I hope you find this useful and choose to use it for your next project! If you'd like to contribute you can find the source on GitHub.

Running ASP.NET on Ubuntu

I wanted to run .NET on linux since I’ve seen people talking about it so much recently. I couldn’t find a start-to-finish tutorial though so I am attempting to do that here.

  1. In VMWare Workstation (you can use VirtualBox just the same) create a VM and install the latest version of Ubuntu desktop.
  2. Once installed and you login, update everything that needs updating in the Ubutntu Software Center and reboot
  3. Type everything in the sub-lists below as commands in a terminal…
  4. Install Mono: 1. sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
  5. echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list
  6. sudo apt-get update
  7. sudo apt-get install mono-complete
  8. Install libuv: 1. sudo apt-get install automake libtool curl
  9. curl -sSL https://github.com/libuv/libuv/archive/v1.4.2.tar.gz | sudo tar zxfv - -C /usr/local/src
  10. cd /usr/local/src/libuv-1.4.2
  11. sudo sh autogen.sh
  12. sudo ./configure
  13. sudo make
  14. sudo make install
  15. sudo rm -rf /usr/local/src/libuv-1.4.2 && cd ~/
  16. sudo ldconfig
  17. Install the .NET Version Manager (DNVM): 1. curl -sSL https://raw.githubusercontent.com/aspnet/Home/dev/dnvminstall.sh | DNX_BRANCH=dev sh && source ~/.dnx/dnvm/dnvm.sh
  18. Install the .NET Execution Environment (DNX): 1. dnvm upgrade
  19. More installs for the next part: 1. sudo apt-get update
  20. sudo apt-get upgrade
  21. sudo apt-get install build-essential openssl libssl-dev curl git
  22. Install NVM: 1. git clone git://github.com/creationix/nvm.git ~/.nvm
  23. To load NVM whenever a terminal is opened: 1. echo '[[ -s "$HOME/.nvm/nvm.sh" ]] && source "$HOME/.nvm/nvm.sh"' >> ~/.bash_profile
  24. Start NVM in the current terminal: 1. . ~/.nvm/nvm.sh
  25. Install Node.js (0.12.6 is the latest version as of this post, you should replace this with whatever the current version is when you install it): 1. nvm install v0.12.6
  26. nvm alias default 0.12.6
  27. Install Yeoman (Yo) and the scaffolding template for ASP.NET projects: 1. npm install -g yo generator-aspnet
  28. Generate an empty ASP.NET project. A wizard will come up and ask you what type of project you want to create. I created a Simple website and named it “MyFirstDotNetAppOnLinux”: 1. yo aspnet
  29. Switch to the new directory/template that was created: 1. cd MyFirstDotNetAppOnLinux
  30. dnu restore
  31. Start Web Server: 1. dnx . kestrel
  32. Open Firefox and go to http://localhost:5000
  33. To kill the web server hit Ctrl+Z then enter kill %1

Here are the articles I referenced when putting together this start-to-finish guide:

Don’t forget to install Visual Studio Code so you can edit your project in Ubuntu!