Building DotNet Core and Angular with Bazel

Published: Fri Apr 17 2020

In this post I will show how to create a unified Bazel build for an asp.net core backend and an Angular frontend. I will be using the latest versions of both Angular and Asp.net core.

Rules_dotnet

The .Net Bazel rules are still experimental, but it’s very encouraging to see how far rules_dotnet have come since I first looked at the rules. At this point you can actually build a fairly sophisticated application, but there are still a few rough edges to iron out.

Why Bazel

Most .Net developers are generally happy working in Visual Studio, so why should they consider Bazel? Well, at this point I would probably only use Bazel for POCs and personal projects. Mainly because the developer experience is not yet comparable to what you’re used to in Visual Studio. However, there are still a few benefits to consider.

There is always the performance benefit from doing incremental builds, but I think the ability to build projects consisting of multiple languages using the same build toolchain is more interesting. In my sample application we will show how to take advantage of this by combining .Net and Angular Typescript in the same Bazel build.

Build Setup

If you are interested in trying this out yourself, feel free to download my sample repo from Github.

The demo application consists of a client side Angular application served by a server side Asp.net core 3.1 application that functions as both web server and api server. Meaning, I serve the static index.html page from the .Net server, but I also expose a few web api endpoints that the Angular application can call.

Angular

The Angular application is written in TypeScript, so the first step is to transpile TypeScript to regular JavaScript using the ng_module rule as seen below:

ng_module( name = "src", srcs = glob(["*.ts"]), tsconfig = "//:tsconfig.json", deps = [ "//Friends/Client/Shared", "@npm//@angular/core", "@npm//@angular/router", "@npm//@angular/forms", ] )

Next I feed the JavaScript output into rollup_ bundle and terser_minified to create an optimized production bundle.

.Net

On the .Net side I use core_binary to create a .Net core binary as seen below:

core_binary( name = "server.exe", srcs = glob([ "**/*.cs" ]), deps = [ "@io_bazel_rules_dotnet//dotnet/stdlib.core:Microsoft.AspNetCore.App", "@io_bazel_rules_dotnet//dotnet/stdlib.core:libraryset", "//Model:Friend.dll" ], data = [ ":index.html", "//Friends/Client:bundle-es2015.min", ":vendor_min", ":site.css", ":bootstrap" ] )

I am passing in the client side dependencies to the server binary rule.

Core_binary creates the running executable, but there is also core_library for creating application modules. In my demo application I have created a single Friend module that I import in core_binary. See the code below:

core_library( name = "Friend.dll", srcs = [ "Friend.cs" ], deps = [ "@io_bazel_rules_dotnet//dotnet/stdlib.core:libraryset", ] )

The biggest pivot from Visual Studio based development is that Bazel has no concept of C# projects or even a solution. Instead all modules are made up of either a binary rule or a library rule. In the end each module is turned into a .dll file in the Bazel output folder.

The only thing I miss from not having C# projects is nuget integration. This leads me to one of the rough edges I mentioned earlier. Nuget integration is still a bit cumbersome since you have to run an external tool that will download nuget packages and wire them up as WORKSPACE rules in your project.

Follow me on twitter @MoreTechStories